Skip to content

Commit

Permalink
Merge pull request #1292 from cuthbertLab/note_mypy
Browse files Browse the repository at this point in the history
mypy on Stream
  • Loading branch information
mscuthbert authored May 5, 2022
2 parents 6703ff7 + 530921e commit 56dc576
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 63 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pythonpylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@ jobs:
- name: Type-check certain modules with mypy
run: |
mypy --follow-imports=silent music21/capella music21/common music21/corpus music21/features music21/figuredBass music21/humdrum music21/ipython21 music21/languageExcerpts music21/lily music21/mei music21/metadata music21/musedata music21/noteworthy music21/omr music21/romanText music21/test music21/vexflow
mypy --follow-imports=silent music21/stream/base.py music21/stream/core.py music21/stream/enums.py music21/stream/filters.py
mypy --follow-imports=silent music21/articulations.py music21/bar.py music21/base.py music21/beam.py music21/clef.py music21/configure.py music21/defaults.py music21/derivation.py music21/duration.py music21/dynamics.py music21/editorial.py music21/environment.py music21/exceptions21.py music21/expressions.py music21/freezeThaw.py music21/harmony.py music21/instrument.py music21/interval.py music21/key.py music21/layout.py music21/note.py music21/percussion.py music21/pitch.py music21/prebase.py music21/repeat.py music21/roman.py music21/serial.py music21/sieve.py music21/sites.py music21/sorting.py music21/spanner.py music21/style.py music21/tablature.py music21/tempo.py music21/text.py music21/tie.py music21/tinyNotation.py music21/variant.py music21/voiceLeading.py music21/volpiano.py music21/volume.py
4 changes: 3 additions & 1 deletion music21/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,9 @@ def clearCache(self, **keywords):
New in v.6 -- exposes previously hidden functionality.
'''
self._cache = {}
# do not replace with self._cache.clear() -- leaves terrible
# state for shallow copies.
self._cache: Dict[str, Any] = {}

@overload
def getOffsetBySite(
Expand Down
3 changes: 3 additions & 0 deletions music21/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
OffsetQLIn = Union[int, float, Fraction]

StreamType = TypeVar('StreamType', bound='music21.stream.Stream')
StreamType2 = TypeVar('StreamType2', bound='music21.stream.Stream')
M21ObjType = TypeVar('M21ObjType', bound='music21.base.Music21Object')
M21ObjType2 = TypeVar('M21ObjType2', bound='music21.base.Music21Object') # when you need another

ClassListType = Union[str, Iterable[str], Type[M21ObjType], Iterable[Type[M21ObjType]]]
StepName = Literal['C', 'D', 'E', 'F', 'G', 'A', 'B']
14 changes: 10 additions & 4 deletions music21/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,6 @@ def matrix(self):
...
>>> [str(e.pitch) for e in s37[0]]
['C', 'B', 'G', 'G#', 'E-', 'C#', 'D', 'B-', 'F#', 'F', 'E', 'A']
'''
# note: do not want to return a TwelveToneRow() type, as this will
# add again the same pitches to the elements list twice.
Expand All @@ -718,8 +716,8 @@ def matrix(self):
i = 0
for row in matrix:
i += 1
rowObject = copy.copy(self)
rowObject.elements = []
rowObject = self.__class__()
rowObject.mergeAttributes(self)
rowObject.id = 'row-' + str(i)
for p in row: # iterate over pitch class values
n = note.Note()
Expand Down Expand Up @@ -1115,6 +1113,14 @@ def __init__(self, composer=None, opus=None, title=None, row=None):
self.opus = opus
self.title = title

def mergeAttributes(self, other):
super().mergeAttributes(other)
if not isinstance(other, HistoricalTwelveToneRow):
return
self.composer = other.composer
self.opus = other.opus
self.title = other.title

def _reprInternal(self):
return f'{self.composer} {self.opus} {self.title}'

Expand Down
106 changes: 60 additions & 46 deletions music21/stream/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
from fractions import Fraction
from math import isclose
from typing import (Dict, Iterable, List, Optional, Set, Tuple, cast,
TypeVar, Type, Union, Generic, Literal, overload)
TypeVar, Type, Union, Generic, Literal, overload,
Sequence, TYPE_CHECKING)

from music21 import base

Expand Down Expand Up @@ -67,7 +68,7 @@

from music21.common.numberTools import opFrac
from music21.common.enums import GatherSpanners, OffsetSpecial
from music21.common.types import StreamType, M21ObjType, OffsetQL
from music21.common.types import StreamType, M21ObjType, OffsetQL, OffsetQLSpecial

from music21 import environment

Expand Down Expand Up @@ -269,15 +270,16 @@ class Stream(core.StreamCoreMixin, base.Music21Object, Generic[M21ObjType]):
musicxml <supports attribute="new-page"> tag) and only if this is
the outermost Stream being shown.
''',
'restrictClass': '''
All elements in the stream are required to be of this class
or a subclass of that class. Currently not enforced. Used
for type-checking.
''',
# 'restrictClass': '''
# All elements in the stream are required to be of this class
# or a subclass of that class. Currently not enforced. Used
# for type-checking.
# ''',
}

def __init__(self, givenElements=None, *args,
restrictClass: Type[M21ObjType] = base.Music21Object,
def __init__(self,
givenElements=None,
*args,
# restrictClass: Type[M21ObjType] = base.Music21Object,
**keywords):
base.Music21Object.__init__(self, **keywords)
core.StreamCoreMixin.__init__(self)
Expand All @@ -287,8 +289,8 @@ def __init__(self, givenElements=None, *args,

self.autoSort = True

# all elements in the stream need to be of this class.
self.restrictClass = restrictClass
# # all elements in the stream need to be of this class.
# self.restrictClass = restrictClass

# these should become part of style or something else...
self.definesExplicitSystemBreaks = False
Expand Down Expand Up @@ -333,8 +335,10 @@ def _reprInternal(self) -> str:
if self.id is not None:
if self.id != id(self) and str(self.id) != str(id(self)):
return str(self.id)
else:
elif isinstance(self.id, int):
return hex(self.id)
else: # pragma: no cover
return ''
else: # pragma: no cover
return ''

Expand Down Expand Up @@ -390,7 +394,8 @@ def __iter__(self) -> iterator.StreamIterator[M21ObjType]:
specialized :class:`music21.stream.StreamIterator` class, which
adds necessary Stream-specific features.
'''
return iterator.StreamIterator[M21ObjType](self)
return cast(iterator.StreamIterator[M21ObjType],
iterator.StreamIterator(self))

def iter(self) -> iterator.StreamIterator[M21ObjType]:
'''
Expand Down Expand Up @@ -423,7 +428,7 @@ def __getitem__(
self,
k: Type[ChangedM21ObjType]
) -> iterator.RecursiveIterator[ChangedM21ObjType]:
x: iterator.RecursiveIterator[ChangedM21ObjType] = self.recurse()
x = cast(iterator.RecursiveIterator[ChangedM21ObjType], self.recurse())
return x # dummy code

def __getitem__(self,
Expand Down Expand Up @@ -582,17 +587,17 @@ def __getitem__(self,
)
# setting active site as cautionary measure
self.coreSelfActiveSite(match)
return match
return cast(M21ObjType, match)

elif isinstance(k, slice): # get a slice of index values
# manually inserting elements is critical to setting the element
# locations
searchElements = self._elements
searchElements: List[base.Music21Object] = self._elements
if (k.start is not None and k.start < 0) or (k.stop is not None and k.stop < 0):
# Must use .elements property to incorporate end elements
searchElements = self.elements
searchElements = list(self.elements)

return searchElements[k]
return cast(M21ObjType, searchElements[k])

elif isinstance(k, type) and issubclass(k, base.Music21Object):
return self.recurse().getElementsByClass(k)
Expand Down Expand Up @@ -694,7 +699,7 @@ def __contains__(self, el):
return False

@property
def elements(self) -> Tuple[base.Music21Object]:
def elements(self) -> Tuple[M21ObjType, ...]:
'''
.elements is a Tuple representing the elements contained in the Stream.

Expand Down Expand Up @@ -743,11 +748,11 @@ def elements(self) -> Tuple[base.Music21Object]:
# coreElementsChanged has been called
if not self.isSorted and self.autoSort:
self.sort() # will set isSorted to True
self._cache['elements'] = self._elements + self._endElements
self._cache['elements'] = cast(List[M21ObjType], self._elements + self._endElements)
return tuple(self._cache['elements'])

@elements.setter
def elements(self, value: Union['Stream', Iterable[base.Music21Object]]):
def elements(self, value: Union[Stream, Iterable[base.Music21Object]]):
'''
Sets this stream's elements to the elements in another stream (just give
the stream, not the stream's .elements), or to a list of elements.
Expand All @@ -767,17 +772,15 @@ def elements(self, value: Union['Stream', Iterable[base.Music21Object]]):
are we going to get the new stream's elements' offsets from? why
from their active sites! So don't do this!
'''
if (not common.isListLike(value)
and hasattr(value, 'isStream')
and value.isStream):
self._offsetDict: Dict[int, Tuple[OffsetQLSpecial, base.Music21Object]] = {}
if isinstance(value, Stream):
# set from a Stream. Best way to do it
self._offsetDict = {}
self._elements = list(value._elements) # copy list.
self._elements: List[base.Music21Object] = list(value._elements) # copy list.
for e in self._elements:
self.coreSetElementOffset(e, value.elementOffset(e), addElement=True)
e.sites.add(self)
self.coreSelfActiveSite(e)
self._endElements = list(value._endElements)
self._endElements: List[base.Music21Object] = list(value._endElements)
for e in self._endElements:
self.coreSetElementOffset(e,
value.elementOffset(e, returnSpecial=True),
Expand All @@ -788,7 +791,6 @@ def elements(self, value: Union['Stream', Iterable[base.Music21Object]]):
# replace the complete elements list
self._elements = list(value)
self._endElements = []
self._offsetDict = {}
for e in self._elements:
self.coreSetElementOffset(e, e.offset, addElement=True)
e.sites.add(self)
Expand Down Expand Up @@ -851,7 +853,7 @@ def __delitem__(self, k):
del self._elements[k]
self.coreElementsChanged()

def __add__(self: T, other: 'Stream') -> T:
def __add__(self: StreamType, other: 'Stream') -> StreamType:
'''
Add, or concatenate, two Streams.

Expand Down Expand Up @@ -1187,11 +1189,11 @@ def staffLines(self, newStaffLines: int):
.getElementsByOffset(0.0)
.getElementsByClass(layout.StaffLayout)
)
if not staffLayouts:
firstLayout = staffLayouts.first()
if not firstLayout:
sl: layout.StaffLayout = layout.StaffLayout(staffLines=newStaffLines)
self.insert(0.0, sl)
else:
firstLayout = staffLayouts.first()
firstLayout.staffLines = newStaffLines

def clear(self) -> None:
Expand All @@ -1212,7 +1214,7 @@ def clear(self) -> None:
>>> m.number
3
'''
self.elements = []
self.elements = ()

def cloneEmpty(self: StreamType, derivationMethod: Optional[str] = None) -> StreamType:
'''
Expand Down Expand Up @@ -1243,7 +1245,7 @@ def cloneEmpty(self: StreamType, derivationMethod: Optional[str] = None) -> Stre
returnObj.mergeAttributes(self) # get groups, optional id
return returnObj

def mergeAttributes(self, other: 'Stream'):
def mergeAttributes(self, other):
'''
Merge relevant attributes from the Other stream into this one.

Expand All @@ -1261,6 +1263,8 @@ def mergeAttributes(self, other: 'Stream'):
0
'''
super().mergeAttributes(other)
if not isinstance(other, Stream):
return

for attr in ('autoSort', 'isSorted', 'definesExplicitSystemBreaks',
'definesExplicitPageBreaks', '_atSoundingPitch', '_mutable'):
Expand Down Expand Up @@ -1391,7 +1395,7 @@ def mergeElements(self, other, classFilterList=None):
# self.storeAtEnd(e)
self.coreElementsChanged()

def index(self, el: M21ObjType) -> int:
def index(self, el: base.Music21Object) -> int:
'''
Return the first matched index for
the specified object.
Expand Down Expand Up @@ -1445,7 +1449,7 @@ def index(self, el: M21ObjType) -> int:
raise StreamException(f'cannot find object ({el}) in Stream')

def remove(self,
targetOrList: Union[base.Music21Object, List[base.Music21Object]],
targetOrList: Union[base.Music21Object, Sequence[base.Music21Object]],
*,
shiftOffsets=False,
recurse=False):
Expand Down Expand Up @@ -1587,16 +1591,23 @@ def remove(self,
raise StreamException(
'Cannot do both shiftOffsets and recurse search at the same time...yet')

targetList: List[base.Music21Object]
if not common.isListLike(targetOrList):
if TYPE_CHECKING:
assert isinstance(targetOrList, base.Music21Object)
targetList = [targetOrList]
elif len(targetOrList) > 1:
elif isinstance(targetOrList, Sequence) and len(targetOrList) > 1:
if TYPE_CHECKING:
assert not isinstance(targetOrList, base.Music21Object)
try:
targetList = sorted(targetOrList, key=self.elementOffset)
targetList = list(sorted(targetOrList, key=self.elementOffset))
except sites.SitesException:
# will not be found if recursing, it's not such a big deal...
targetList = targetOrList
targetList = list(targetOrList)
else:
targetList = targetOrList
if TYPE_CHECKING:
assert not isinstance(targetOrList, base.Music21Object)
targetList = list(targetOrList)

shiftDur = 0.0 # for shiftOffsets

Expand Down Expand Up @@ -1894,7 +1905,7 @@ def _replaceSpannerBundleForDeepcopy(self, new):
def setElementOffset(
self,
element: base.Music21Object,
offset: Union[int, float, Fraction, str],
offset: Union[int, float, Fraction, OffsetSpecial],
):
'''
Sets the Offset for an element that is already in a given stream.
Expand Down Expand Up @@ -2712,7 +2723,9 @@ def insertAndShift(self, offsetOrItemOrList, itemOrNone=None):
# --------------------------------------------------------------------------
# searching and replacing routines

def setDerivationMethod(self, derivationMethod, recurse=False) -> None:
def setDerivationMethod(self,
derivationMethod: str,
recurse=False) -> None:
'''
Sets the .derivation.method for each element in the Stream
if it has a .derivation object.
Expand All @@ -2734,10 +2747,10 @@ def setDerivationMethod(self, derivationMethod, recurse=False) -> None:
>>> s2.recurse().notes[-1].derivation
<Derivation of <music21.note.Note F> from <music21.note.Note F> via '__deepcopy__'>
'''
if recurse:
sIter = self.recurse()
else:
if not recurse:
sIter = self.iter()
else:
sIter = self.recurse()

for el in sIter:
if el.derivation is not None:
Expand Down Expand Up @@ -2991,7 +3004,8 @@ def processContainer(container: Stream):
if isinstance(complexObj, note.Rest) and complexObj.fullMeasure in (True, 'always'):
continue
if isinstance(complexObj, note.Rest) and complexObj.fullMeasure == 'auto':
if container.isMeasure and (complexObj.duration == container.barDuration):
if (isinstance(container, Measure)
and (complexObj.duration == container.barDuration)):
continue
elif ('Voice' in container.classes
and container.activeSite
Expand Down
Loading

0 comments on commit 56dc576

Please sign in to comment.