Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase type coverage in patterns #361

Merged
merged 30 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
059edee
Typing patterns
josiah-wolf-oberholtzer Jan 21, 2024
701f9ac
Typing enums
josiah-wolf-oberholtzer Jan 21, 2024
1079406
Typing patterns
josiah-wolf-oberholtzer Jan 21, 2024
f899bea
Typing enums
josiah-wolf-oberholtzer Jan 21, 2024
fc6b3df
Typing SynthDefs
josiah-wolf-oberholtzer Jan 21, 2024
fe0cacf
Typing patterns
josiah-wolf-oberholtzer Jan 21, 2024
17b236c
Typing patterns
josiah-wolf-oberholtzer Jan 21, 2024
cfc6d7a
Typing patterns
josiah-wolf-oberholtzer Jan 23, 2024
cf4bbfa
Typing patterns
josiah-wolf-oberholtzer Jan 23, 2024
a789d91
Typing patterns
josiah-wolf-oberholtzer Jan 23, 2024
c4f5939
Update pattern tests
josiah-wolf-oberholtzer Jan 23, 2024
1cfca0a
Add mypy-strict target
josiah-wolf-oberholtzer Jan 23, 2024
18585b6
More typing
josiah-wolf-oberholtzer Jan 23, 2024
9a66e12
Typing utils, renaming functions
josiah-wolf-oberholtzer Jan 31, 2024
059c7cd
Typing events and iterable utils
josiah-wolf-oberholtzer Jan 31, 2024
c1ffc8a
Fixup allocators
josiah-wolf-oberholtzer Jan 31, 2024
1f203d8
Fixup RNG
josiah-wolf-oberholtzer Jan 31, 2024
5eab46c
Fixup pattern playing
josiah-wolf-oberholtzer Jan 31, 2024
f5d5780
Typing extensions
josiah-wolf-oberholtzer Jan 31, 2024
afcb07d
Typing ugens
josiah-wolf-oberholtzer Jan 31, 2024
36553d2
Use typing.Iterable
josiah-wolf-oberholtzer Jan 31, 2024
e2d7773
Use typing.Sequence
josiah-wolf-oberholtzer Jan 31, 2024
bd34d29
Use typing.Sequence
josiah-wolf-oberholtzer Jan 31, 2024
34d6ed0
Use typing.Tuple
josiah-wolf-oberholtzer Jan 31, 2024
8d8782d
Show locals
josiah-wolf-oberholtzer Jan 31, 2024
dd80672
Typing patterns
josiah-wolf-oberholtzer Feb 17, 2024
fa75b68
Typing patterns
josiah-wolf-oberholtzer Feb 17, 2024
cc3672c
Typing patterns
josiah-wolf-oberholtzer Feb 17, 2024
2c309c4
Fixup Makefile help message
josiah-wolf-oberholtzer Feb 17, 2024
9faad6f
Bump uqbar@v0.7.2 -> v0.7.3
josiah-wolf-oberholtzer Feb 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ jobs:
lame
fi
- name: Run Pytest
run: pytest --cov supriya
run: pytest --cov supriya --showlocals
timeout-minutes: 15
- name: Check for stray processes (Non-Windows)
if: ${{ matrix.os != 'windows-latest' }}
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ mypy: ## Type-check via mypy
mypy-cov: ## Type-check via mypy with coverage reported to ./mypycov/
mypy --html-report ./mypycov/ ${project}/

mypy-strict: ## Type-check via mypy strictly
mypy --strict ${project}/

pytest: ## Unit test via pytest
rm -Rf htmlcov/
pytest ${testPaths} --cov=supriya
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ classifiers = [
"Topic :: Multimedia :: Sound/Audio :: Sound Synthesis",
]
dependencies = [
"platformdirs >= 3.9.1",
"uqbar >= 0.7.2",
"platformdirs >= 4.0.0",
"uqbar >= 0.7.3",
]
description = "A Python API for SuperCollider"
dynamic = ["version"]
Expand Down
24 changes: 13 additions & 11 deletions supriya/contexts/allocators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import dataclasses
import threading
from typing import Set
from typing import Optional, Set

from ..utils import Interval, IntervalTree

Expand Down Expand Up @@ -52,7 +52,9 @@ class BlockAllocator:

### INITIALIZER ###

def __init__(self, heap_maximum=None, heap_minimum=0):
def __init__(
self, heap_maximum: Optional[int] = None, heap_minimum: int = 0
) -> None:
self._free_heap = IntervalTree(accelerated=True)
self._heap_maximum = heap_maximum
self._heap_minimum = heap_minimum
Expand All @@ -78,7 +80,7 @@ def __setstate__(self, state):

### PUBLIC METHODS ###

def allocate(self, desired_block_size=1):
def allocate(self, desired_block_size: int = 1) -> Optional[int]:
desired_block_size = int(desired_block_size)
assert 0 < desired_block_size
block_id = None
Expand All @@ -103,11 +105,9 @@ def allocate(self, desired_block_size=1):
used_block = dataclasses.replace(free_block, used=True)
self._used_heap.add(used_block)
block_id = used_block.start_offset
if block_id is None:
return block_id
return int(block_id)
return int(block_id) if block_id is not None else None

def allocate_at(self, index=None, desired_block_size=1):
def allocate_at(self, index: int, desired_block_size: int = 1) -> Optional[int]:
index = int(index)
desired_block_size = int(desired_block_size)
block_id = None
Expand Down Expand Up @@ -140,7 +140,7 @@ def allocate_at(self, index=None, desired_block_size=1):
return block_id
return int(block_id)

def free(self, block_id):
def free(self, block_id: int) -> None:
block_id = int(block_id)
with self._lock:
cursor = self._used_heap.get_moment_at(block_id)
Expand Down Expand Up @@ -174,14 +174,14 @@ def free(self, block_id):
### PUBLIC PROPERTIES ###

@property
def heap_maximum(self):
def heap_maximum(self) -> Optional[int]:
"""
Maximum allocatable index.
"""
return self._heap_maximum

@property
def heap_minimum(self):
def heap_minimum(self) -> int:
"""
Minimum allocatable index.
"""
Expand Down Expand Up @@ -224,7 +224,9 @@ class NodeIdAllocator:

### INITIALIZER ###

def __init__(self, client_id: int = 0, initial_node_id: int = 1000, locked=True):
def __init__(
self, client_id: int = 0, initial_node_id: int = 1000, locked=True
) -> None:
if client_id > 31:
raise ValueError
self._initial_node_id = initial_node_id
Expand Down
26 changes: 17 additions & 9 deletions supriya/contexts/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
RootNode,
Synth,
)
from .errors import ContextError, InvalidCalculationRate, MomentClosed
from .errors import AllocationError, ContextError, InvalidCalculationRate, MomentClosed
from .requests import (
AllocateBuffer,
AllocateReadBuffer,
Expand Down Expand Up @@ -265,18 +265,26 @@ def _allocate_id(
count: int = 1,
permanent: bool = False,
) -> int:
id_: Optional[int] = None
if type_ is Node:
if permanent:
return self._node_id_allocator.allocate_permanent_node_id()
return self._node_id_allocator.allocate_node_id()
if type_ is Buffer:
return self._buffer_allocator.allocate(count)
if type_ is Bus:
id_ = self._node_id_allocator.allocate_permanent_node_id()
else:
id_ = self._node_id_allocator.allocate_node_id()
elif type_ is Buffer:
id_ = self._buffer_allocator.allocate(count)
elif type_ is Bus:
if calculation_rate is CalculationRate.AUDIO:
return self._audio_bus_allocator.allocate(count)
id_ = self._audio_bus_allocator.allocate(count)
elif calculation_rate is CalculationRate.CONTROL:
return self._control_bus_allocator.allocate(count)
raise ValueError
id_ = self._control_bus_allocator.allocate(count)
else:
raise ValueError(calculation_rate)
else:
raise ValueError(type_)
if id_ is None:
raise AllocationError
return id_

@staticmethod
def _apply_completions(
Expand Down
4 changes: 4 additions & 0 deletions supriya/contexts/errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
class AllocationError(Exception):
pass


class ContextError(Exception):
pass

Expand Down
36 changes: 19 additions & 17 deletions supriya/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from collections.abc import Sequence
from typing import cast

from uqbar.enums import IntEnumeration, StrictEnumeration

Expand Down Expand Up @@ -86,7 +87,7 @@ class CalculationRate(IntEnumeration):
IR = 0

@classmethod
def from_expr(cls, expr):
def from_expr(cls, expr) -> "CalculationRate":
"""
Gets calculation-rate.

Expand Down Expand Up @@ -128,13 +129,13 @@ def from_expr(cls, expr):
from .ugens import OutputProxy, UGen

if isinstance(expr, (int, float)) and not isinstance(expr, cls):
return CalculationRate.SCALAR
return cast(CalculationRate, CalculationRate.SCALAR)
elif isinstance(expr, (OutputProxy, UGen)):
return expr.calculation_rate
elif isinstance(expr, Parameter):
name = expr.parameter_rate.name
if name == "TRIGGER":
return CalculationRate.CONTROL
return cast(CalculationRate, CalculationRate.CONTROL)
return CalculationRate.from_expr(name)
elif isinstance(expr, str):
return super().from_expr(expr)
Expand All @@ -145,7 +146,7 @@ def from_expr(cls, expr):
return super().from_expr(expr)

@property
def token(self):
def token(self) -> str:
if self == CalculationRate.SCALAR:
return "ir"
elif self == CalculationRate.CONTROL:
Expand Down Expand Up @@ -211,19 +212,20 @@ class NodeAction(IntEnumeration):
NODE_QUERIED = 5

@classmethod
def from_expr(cls, address):
def from_expr(cls, address) -> "NodeAction":
if isinstance(address, cls):
return address
addresses = {
"/n_end": cls.NODE_REMOVED,
"/n_go": cls.NODE_CREATED,
"/n_info": cls.NODE_QUERIED,
"/n_move": cls.NODE_MOVED,
"/n_off": cls.NODE_DEACTIVATED,
"/n_on": cls.NODE_ACTIVATED,
}
action = addresses[address]
return action
return cast(
NodeAction,
{
"/n_end": cls.NODE_REMOVED,
"/n_go": cls.NODE_CREATED,
"/n_info": cls.NODE_QUERIED,
"/n_move": cls.NODE_MOVED,
"/n_off": cls.NODE_DEACTIVATED,
"/n_on": cls.NODE_ACTIVATED,
}[address],
)


class ParameterRate(IntEnumeration):
Expand Down Expand Up @@ -313,7 +315,7 @@ class RequestId(IntEnumeration):
VERSION = 64

@property
def request_name(self):
def request_name(self) -> str:
return RequestName.from_expr(self.name)


Expand Down Expand Up @@ -389,7 +391,7 @@ class RequestName(StrictEnumeration):
VERSION = "/version"

@property
def request_id(self):
def request_id(self) -> str:
return RequestId.from_expr(self.name)


Expand Down
3 changes: 2 additions & 1 deletion supriya/ext/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import subprocess
from pathlib import Path

import uqbar.io


def websafe_audio(output_path):
def websafe_audio(output_path: Path) -> Path:
# Convert to MP3 if possible for smaller file sizes:
if uqbar.io.find_executable("lame"):
new_output_path = output_path.with_suffix(".mp3")
Expand Down
4 changes: 2 additions & 2 deletions supriya/osc.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from uqbar.objects import get_repr

from .utils import group_iterable_by_count
from .utils import group_by_count

osc_protocol_logger = logging.getLogger(__name__)
osc_in_logger = logging.getLogger("supriya.osc.in")
Expand Down Expand Up @@ -1095,7 +1095,7 @@ def format_datagram(datagram):
line = "{: >4} ".format(index)
hex_blocks = []
ascii_block = ""
for chunk in group_iterable_by_count(chunk, 4):
for chunk in group_by_count(chunk, 4):
hex_block = []
for byte in chunk:
char = int(byte)
Expand Down
50 changes: 30 additions & 20 deletions supriya/patterns/eventpatterns.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Type
from uuid import uuid4
from typing import Any, Dict, Generator, Optional, Type
from uuid import UUID, uuid4

from uqbar.objects import new

Expand All @@ -12,11 +12,15 @@ class EventPattern(Pattern):
Akin to SuperCollider's Pbind.
"""

def __init__(self, event_type: Type[Event] = NoteEvent, **patterns) -> None:
def __init__(
self, event_type: Type[NoteEvent] = NoteEvent, **patterns: Pattern
) -> None:
self._event_type = event_type
self._patterns = patterns

def _iterate(self, state=None):
def _iterate(
self, state: Optional[Dict[str, UUID]] = None
) -> Generator[Event, bool, None]:
patterns = self._prepare_patterns()
iterator_pairs = sorted(patterns.items())
while True:
Expand All @@ -29,16 +33,16 @@ def _iterate(self, state=None):
if (yield self.event_type(uuid4(), **event)):
return

def _prepare_patterns(self):
patterns = self._patterns.copy()
for name, pattern in sorted(patterns.items()):
def _prepare_patterns(self) -> Dict[str, Generator[Any, bool, None]]:
generators: Dict[str, Generator[Any, bool, None]] = {}
for name, pattern in sorted(self._patterns.items()):
if not isinstance(pattern, Pattern):
pattern = SequencePattern([pattern], iterations=None)
patterns[name] = iter(pattern)
return patterns
generators[name] = iter(pattern)
return generators

@property
def event_type(self) -> Type[Event]:
def event_type(self) -> Type[NoteEvent]:
return self._event_type

@property
Expand All @@ -54,7 +58,9 @@ class MonoEventPattern(EventPattern):
Akin to SuperCollider's Pmono.
"""

def _iterate(self, state=None):
def _iterate(
self, state: Optional[Dict[str, UUID]] = None
) -> Generator[Event, bool, None]:
id_ = uuid4()
patterns = self._prepare_patterns()
iterator_pairs = sorted(patterns.items())
Expand All @@ -81,11 +87,13 @@ class UpdatePattern(Pattern):
Akin to SuperCollider's Pbindf.
"""

def __init__(self, pattern: Pattern, **patterns) -> None:
def __init__(self, pattern: Pattern[Event], **patterns: Pattern) -> None:
self._pattern = pattern
self._patterns = patterns

def _iterate(self, state=None):
def _iterate(
self, state: Optional[Dict[str, UUID]] = None
) -> Generator[Event, bool, None]:
event_iterator = iter(self._pattern)
iterator_pairs = sorted(self._prepare_patterns().items())
while True:
Expand All @@ -103,13 +111,13 @@ def _iterate(self, state=None):
if (yield event):
return

def _prepare_patterns(self):
patterns = self._patterns.copy()
for name, pattern in sorted(patterns.items()):
def _prepare_patterns(self) -> Dict[str, Generator[Any, bool, None]]:
generators: Dict[str, Generator[Any, bool, None]] = {}
for name, pattern in sorted(self._patterns.items()):
if not isinstance(pattern, Pattern):
pattern = SequencePattern([pattern], iterations=None)
patterns[name] = iter(pattern)
return patterns
generators[name] = iter(pattern)
return generators

@property
def is_infinite(self) -> bool:
Expand All @@ -124,10 +132,12 @@ class ChainPattern(Pattern):
Akin to SuperCollider's Pchain.
"""

def __init__(self, *patterns: Pattern) -> None:
def __init__(self, *patterns: Pattern[Event]) -> None:
self._patterns = tuple(patterns)

def _iterate(self, state=None):
def _iterate(
self, state: Optional[Dict[str, UUID]] = None
) -> Generator[Event, bool, None]:
patterns = [iter(_) for _ in self._patterns]
while True:
try:
Expand Down
Loading
Loading