Repository: cuthbertLab/music21
Pull request #1447 (2022-10-04 23:55:02)

use more v3.10 features. TypeGuards
Use v3.10 features
Add typeguards to isIterable isNum
common.classTools: isInt (int not True, False),  holdsType (does it hold a particular type?)

coveralls: 
[![Coverage Status](https://coveralls.io/builds/53014399/badge)](https://coveralls.io/builds/53014399)

Coverage decreased (-0.002%) to 93.035% when pulling **a07c4afd9e0c4ea478f5ec2a891315647ec00f18 on more-3.10-cleanup** into **5417b3ce6415ab016a39564e21e29799387263e9 on master**.


Patch:
diff --git a/music21/abcFormat/translate.py b/music21/abcFormat/translate.py
index f50a1e3e9..e3c0e71ca 100644
--- a/music21/abcFormat/translate.py
+++ b/music21/abcFormat/translate.py
@@ -799,11 +799,10 @@ class Test(unittest.TestCase):
         assert s is not None
 
         # s.show()
-#         self.assertEqual(len(s.parts), 3)
-#         self.assertEqual(len(s.parts[0].notesAndRests), 6)
-#         self.assertEqual(len(s.parts[1].notesAndRests), 20)
-#         self.assertEqual(len(s.parts[2].notesAndRests), 6)
-#
+        # self.assertEqual(len(s.parts), 3)
+        # self.assertEqual(len(s.parts[0].notesAndRests), 6)
+        # self.assertEqual(len(s.parts[1].notesAndRests), 20)
+        # self.assertEqual(len(s.parts[2].notesAndRests), 6)
         # s.show()
         # s.show('midi')
 
diff --git a/music21/articulations.py b/music21/articulations.py
index 0e3737c86..0b8c849e5 100644
--- a/music21/articulations.py
+++ b/music21/articulations.py
@@ -668,23 +668,23 @@ class Test(unittest.TestCase):
         self.assertEqual(a.bendAlter, None)
 
 
-#     def testArticulationEquality(self):
-#         a1 = Accent()
-#         a2 = Accent()
-#         a3 = StrongAccent()
-#         a4 = StrongAccent()
-#
-#         self.assertEqual(a1, a2)
-#         self.assertEqual(a3, a4)
-#
-#         # in order lists
-#         self.assertEqual([a1, a3], [a2, a4])
-#
-#         self.assertEqual(set([a1, a3]), set([a1, a3]))
-#         self.assertEqual(set([a1, a3]), set([a3, a1]))
-#
-#         # comparison of sets of different objects do not pass
-#         # self.assertEqual(list(set([a1, a3])), list(set([a2, a4])))
+    # def testArticulationEquality(self):
+    #     a1 = Accent()
+    #     a2 = Accent()
+    #     a3 = StrongAccent()
+    #     a4 = StrongAccent()
+    #
+    #     self.assertEqual(a1, a2)
+    #     self.assertEqual(a3, a4)
+    #
+    #     # in order lists
+    #     self.assertEqual([a1, a3], [a2, a4])
+    #
+    #     self.assertEqual(set([a1, a3]), set([a1, a3]))
+    #     self.assertEqual(set([a1, a3]), set([a3, a1]))
+    #
+    #     # comparison of sets of different objects do not pass
+    #     # self.assertEqual(list(set([a1, a3])), list(set([a2, a4])))
 
 
 # ------------------------------------------------------------------------------
diff --git a/music21/base.py b/music21/base.py
index 1ea198005..fe1f6613c 100644
--- a/music21/base.py
+++ b/music21/base.py
@@ -40,7 +40,6 @@ from __future__ import annotations
 import builtins
 from collections.abc import Generator, Iterable
 import copy
-import fractions  # for type annotation only
 from importlib.util import find_spec
 import typing as t
 from typing import overload  # Pycharm can't do alias
@@ -57,7 +56,7 @@ from music21.common.types import OffsetQL, OffsetQLIn
 from music21 import defaults
 from music21.derivation import Derivation
 from music21.duration import Duration, DurationException
-from music21.editorial import Editorial
+from music21.editorial import Editorial  # import class directly to not conflict with property.
 from music21 import environment
 from music21 import exceptions21
 from music21 import prebase
@@ -68,6 +67,7 @@ from music21.sorting import SortTuple, ZeroSortTupleLow, ZeroSortTupleHigh
 from music21 import tie
 
 if TYPE_CHECKING:
+    import fractions
     from music21 import meter
     from music21 import stream
     from music21 import spanner
@@ -685,8 +685,6 @@ class Music21Object(prebase.ProtoM21Object):
 
     @editorial.setter
     def editorial(self, ed: Editorial):
-        # Dev note: because the property "editorial" shadows module editorial,
-        # typing has to be in quotes.
         self._editorial = ed
 
     @property
@@ -743,8 +741,6 @@ class Music21Object(prebase.ProtoM21Object):
 
     @style.setter
     def style(self, newStyle: Style | None):
-        # Dev note: because property style shadows module style,
-        # typing has to be in quotes.
         self._style = newStyle
 
     # convenience.
diff --git a/music21/braille/basic.py b/music21/braille/basic.py
index 2667265e4..e4c612e3a 100644
--- a/music21/braille/basic.py
+++ b/music21/braille/basic.py
@@ -11,9 +11,9 @@
 # ------------------------------------------------------------------------------
 from __future__ import annotations
 
+import typing as t
 import unittest
 
-# from music21 import articulations
 from music21 import articulations
 from music21 import clef
 from music21 import duration
@@ -22,12 +22,14 @@ from music21 import exceptions21
 from music21 import expressions
 from music21 import interval
 from music21 import note
-from music21 import tempo  # for typing
 
 from music21.braille import lookup
 from music21.common import stringTools
 
-# Add aliases to lookup tables ONLY if it will be used in many different contexts
+if t.TYPE_CHECKING:
+    from music21 import tempo
+
+# Add aliases to lookup tables ONLY if it will be used in many contexts
 # if it is used in only one function, make the alias there.
 alphabet = lookup.alphabet
 ascii_chars = lookup.ascii_chars
@@ -680,7 +682,7 @@ def noteToBraille(
     # Note: beamStatus is a helper that we hope to remove
     # when moving all the translation features to an object class.
     #
-    # Currently does not allow parallelization of parsing.
+    # Currently, does not allow parallelization of parsing.
     music21Note.editorial.brailleEnglish = []
 
     for keyword in TEMPORARY_ATTRIBUTES:
diff --git a/music21/clef.py b/music21/clef.py
index 3c1c2b723..778461d70 100644
--- a/music21/clef.py
+++ b/music21/clef.py
@@ -25,7 +25,7 @@ import unittest
 from music21 import base
 from music21 import exceptions21
 from music21 import environment
-from music21 import pitch  # for typing only
+from music21 import pitch
 from music21 import style
 
 
diff --git a/music21/common/classTools.py b/music21/common/classTools.py
index b4ae8644a..ea9fbb5b8 100644
--- a/music21/common/classTools.py
+++ b/music21/common/classTools.py
@@ -11,24 +11,43 @@
 # ------------------------------------------------------------------------------
 from __future__ import annotations
 
+from collections.abc import Iterable, Collection
 import contextlib
+import numbers
 import typing as t
 
-# from music21 import exceptions21
 __all__ = [
-    'isNum', 'isListLike', 'isIterable', 'classToClassStr', 'getClassSet',
+    'holdsType',
+    'isNum', 'isInt', 'isListLike', 'isIterable', 'classToClassStr', 'getClassSet',
     'tempAttribute', 'saveAttributes',
 ]
 
 
-def isNum(usrData: t.Any) -> bool:
+_T = t.TypeVar('_T')
+
+
+def isInt(usrData: t.Any) -> t.TypeGuard[int]:
+    '''
+    Check if usrData is an integer and not True or False.
+
+    >>> common.isInt(3)
+    True
+    >>> common.isInt(False)
+    False
+    >>> common.isInt(2.0)
+    False
+    '''
+    return isinstance(usrData, int) and usrData is not True and usrData is not False
+
+
+def isNum(usrData: t.Any) -> t.TypeGuard[numbers.Rational]:
     '''
     check if usrData is a number (float, int, long, Decimal),
-    return boolean
+    return boolean and if True casts the value as a Rational number
 
-    unlike `isinstance(usrData, Number)` does not return True for `True, False`.
+    unlike `isinstance(usrData, Rational)` does not return True for `True, False`.
 
-    Does not use `isinstance(usrData, Number)` which is 2-6 times slower
+    Does not use `isinstance(usrData, Rational)` which is 2-6 times slower
     than calling this function (except in the case of Fraction, when
     it's 6 times faster, but that's rarer).  (6 times slower on Py3.4, now
     only 2x slower in Python 3.10)
@@ -66,7 +85,7 @@ def isNum(usrData: t.Any) -> bool:
         return False
 
 
-def isListLike(usrData: t.Any) -> bool:
+def isListLike(usrData: t.Any) -> t.TypeGuard[list | tuple]:
     '''
     Returns True if is a List or Tuple or their subclasses.
 
@@ -91,10 +110,10 @@ def isListLike(usrData: t.Any) -> bool:
     return isinstance(usrData, (list, tuple))
 
 
-def isIterable(usrData: t.Any) -> bool:
+def isIterable(usrData: t.Any) -> t.TypeGuard[Iterable]:
     '''
     Returns True if is the object can be iter'd over
-    and is NOT a string
+    and is NOT a string.  Marks it as an Iterable for type checking.
 
     >>> common.isIterable([5, 10])
     True
@@ -126,6 +145,64 @@ def isIterable(usrData: t.Any) -> bool:
     return False
 
 
+def holdsType(usrData: t.Any, checkType: type[_T]) -> t.TypeGuard[Collection[_T]]:
+    '''
+    Returns True if usrData is a Collection of type checkType.
+
+    This reads an item from usrData, so don't use it on something
+    where iterating destroys the type.
+
+    >>> y = [1, 2, 3]
+    >>> common.classTools.holdsType(y, int)
+    True
+    >>> common.classTools.holdsType(5, int)
+    False
+    >>> common.classTools.holdsType(['hello'], str)
+    True
+
+    Empty iterators hold the type:
+
+    >>> common.classTools.holdsType([], float)
+    True
+
+    Note that a mixed collection holds whatever is first
+
+    >>> common.classTools.holdsType((4, 'hello'), int)
+    True
+    >>> common.classTools.holdsType((4, 'hello'), str)
+    False
+
+    Works on sets with arbitrary order:
+
+    >>> common.classTools.holdsType({2, 10}, int)
+    True
+
+    Intelligent collections will not have their position affected.
+
+    >>> m = stream.Measure([note.Note('C'), note.Rest()])
+    >>> common.classTools.holdsType(m, note.GeneralNote)
+    True
+    >>> next(iter(m))
+    <music21.note.Note C>
+
+    >>> r = range(1, 100)
+    >>> common.classTools.holdsType(r, int)
+    True
+    >>> next(iter(r))
+    1
+
+    New in v9.
+    '''
+    if not isIterable(usrData):
+        return False
+    try:
+        first = next(iter(usrData))
+        return isinstance(first, checkType)
+    except StopIteration:
+        return True
+
+
+
 def classToClassStr(classObj: type) -> str:
     '''Convert a class object to a class string.
 
@@ -157,6 +234,8 @@ def getClassSet(instance, classNameTuple=None):
     True
     >>> 'object' in cs
     True
+    >>> note.Note in cs
+    False
 
     To save time (this IS a performance-critical operation), classNameTuple
     can be passed a tuple of names such as ('Pitch', 'object') that
diff --git a/music21/common/numberTools.py b/music21/common/numberTools.py
index 55f916d4e..0f9d5faad 100644
--- a/music21/common/numberTools.py
+++ b/music21/common/numberTools.py
@@ -819,7 +819,7 @@ def lcm(filterList: Iterable[int]) -> int:
     common.lcm({3, 5, 6})
     30
 
-    Deprecated in v9 since Python 3.9 is the minimum version
+    Deprecated in v9 since Python 3.10 is the minimum version
     and math.lcm works in C and is faster
     '''
     def _lcm(a, b):
diff --git a/music21/duration.py b/music21/duration.py
index 02b7bde21..eecd0196c 100644
--- a/music21/duration.py
+++ b/music21/duration.py
@@ -1095,6 +1095,19 @@ class Tuplet(prebase.ProtoM21Object):
                 # type ignore until https://github.com/python/mypy/issues/3004 resolved
                 self.durationActual = durationActual  # type: ignore
 
+        # # crashes mypy-0.982 -- fixed in 0.999 dev.
+        # match durationActual:
+        #     case None:
+        #         pass
+        #     case str(durAct):
+        #         self.durationActual = durationTupleFromTypeDots(durAct, 0)
+        #     case (str(durActType), int(dots)):
+        #         self.durationActual = durationTupleFromTypeDots(durActType, dots)
+        #     case _:
+        #         # type ignore until https://github.com/python/mypy/issues/3004 resolved
+        #         self.durationActual = durationActual  # type: ignore
+
+
         # normal is the space that would normally be occupied by the tuplet span
         self.numberNotesNormal: int = numberNotesNormal
 
@@ -1108,6 +1121,18 @@ class Tuplet(prebase.ProtoM21Object):
                 # type ignore until https://github.com/python/mypy/issues/3004 resolved
                 self.durationNormal = durationNormal  # type: ignore
 
+        # # crashes mypy-0.982 -- fixed in 0.999 dev.
+        # match durationNormal:
+        #     case None:
+        #         pass
+        #     case str(durNor):
+        #         self.durationNormal = durationTupleFromTypeDots(durNor, 0)
+        #     case(str(durNorType), int(dots)):
+        #         self.durationNormal = durationTupleFromTypeDots(durNorType, dots)
+        #     case _:
+        #         # type ignore until https://github.com/python/mypy/issues/3004 resolved
+        #         self.durationNormal = durationNormal  # type: ignore
+
         # Type is 'start', 'stop', 'startStop', False or None: determines whether to start or stop
         # the bracket/group drawing
         # startStop is not used in musicxml, it will be encoded
@@ -1160,10 +1185,10 @@ class Tuplet(prebase.ProtoM21Object):
     # PRIVATE METHODS #
 
     def _reprInternal(self):
-        base = f'{self.numberNotesActual!r}/{self.numberNotesNormal!r}'
+        base_rep = f'{self.numberNotesActual!r}/{self.numberNotesNormal!r}'
         if self.durationNormal is not None:
-            base += f'/{self.durationNormal.type}'
-        return base
+            base_rep += f'/{self.durationNormal.type}'
+        return base_rep
 
     def _checkFrozen(self):
         if self.frozen is True:
@@ -3179,7 +3204,6 @@ class TupletFixer:
     reflect proper beaming, etc.  It does not alter the quarterLength
     of any notes.
     '''
-
     def __init__(self, streamIn=None):
         self.streamIn = streamIn
 
@@ -3359,51 +3383,48 @@ class TupletFixer:
 
         if currentTupletDuration == totalTupletDuration:
             return
-        else:
-            excessRatio = opFrac(currentTupletDuration / totalTupletDuration)
-            inverseExcessRatio = opFrac(1 / excessRatio)
-
-            if excessRatio == int(excessRatio):  # divide tuplets into smaller
-                largestTupletType = ordinalTypeFromNum[largestTupletTypeOrdinal]
-
-                for n in tupletGroup:
-                    normalDots = 0
-                    n.duration.tuplets[0].frozen = False  # bad
-                    if n.duration.tuplets[0].durationNormal is not None:
-                        normalDots = n.duration.tuplets[0].durationNormal.dots
-                    n.duration.tuplets[0].durationNormal = durationTupleFromTypeDots(
-                        largestTupletType, normalDots)
-                    actualDots = 0
-                    if n.duration.tuplets[0].durationActual is not None:
-                        actualDots = n.duration.tuplets[0].durationActual.dots
-                    n.duration.tuplets[0].durationActual = durationTupleFromTypeDots(
-                        largestTupletType, actualDots)
-                    n.duration.tuplets[0].frozen = True
-                    n.duration.informClient()
-
-            elif inverseExcessRatio == int(inverseExcessRatio):  # redefine tuplets by GCD
-                smallestTupletType = ordinalTypeFromNum[smallestTupletTypeOrdinal]
-                for n in tupletGroup:
-                    normalDots = 0
-                    n.duration.tuplets[0].frozen = False  # bad
-                    if n.duration.tuplets[0].durationNormal is not None:
-                        normalDots = n.duration.tuplets[0].durationNormal.dots
-                    # TODO: this should be frozen!
-                    durTuple = durationTupleFromTypeDots(smallestTupletType, normalDots)
-                    n.duration.tuplets[0].durationNormal = durTuple
-
-                    actualDots = 0
-                    if n.duration.tuplets[0].durationActual is not None:
-                        actualDots = n.duration.tuplets[0].durationActual.dots
-                    durTuple = durationTupleFromTypeDots(smallestTupletType,
-                                                         actualDots)
-                    n.duration.tuplets[0].durationActual = durTuple
-                    n.duration.tuplets[0].frozen = True
-                    n.duration.informClient()
 
-            else:
-                pass
-                # print('Crazy!', currentTupletDuration, totalTupletDuration, excess)
+        excessRatio = opFrac(currentTupletDuration / totalTupletDuration)
+        inverseExcessRatio = opFrac(1 / excessRatio)
+
+        if excessRatio == int(excessRatio):  # divide tuplets into smaller
+            largestTupletType = ordinalTypeFromNum[largestTupletTypeOrdinal]
+
+            for n in tupletGroup:
+                normalDots = 0
+                n.duration.tuplets[0].frozen = False  # bad
+                if n.duration.tuplets[0].durationNormal is not None:
+                    normalDots = n.duration.tuplets[0].durationNormal.dots
+                n.duration.tuplets[0].durationNormal = durationTupleFromTypeDots(
+                    largestTupletType, normalDots)
+                actualDots = 0
+                if n.duration.tuplets[0].durationActual is not None:
+                    actualDots = n.duration.tuplets[0].durationActual.dots
+                n.duration.tuplets[0].durationActual = durationTupleFromTypeDots(
+                    largestTupletType, actualDots)
+                n.duration.tuplets[0].frozen = True
+                n.duration.informClient()
+
+        elif inverseExcessRatio == int(inverseExcessRatio):  # redefine tuplets by GCD
+            smallestTupletType = ordinalTypeFromNum[smallestTupletTypeOrdinal]
+            for n in tupletGroup:
+                normalDots = 0
+                n.duration.tuplets[0].frozen = False  # bad
+                if n.duration.tuplets[0].durationNormal is not None:
+                    normalDots = n.duration.tuplets[0].durationNormal.dots
+                # TODO: this should be frozen!
+                durTuple = durationTupleFromTypeDots(smallestTupletType, normalDots)
+                n.duration.tuplets[0].durationNormal = durTuple
+
+                actualDots = 0
+                if n.duration.tuplets[0].durationActual is not None:
+                    actualDots = n.duration.tuplets[0].durationActual.dots
+                durTuple = durationTupleFromTypeDots(smallestTupletType,
+                                                     actualDots)
+                n.duration.tuplets[0].durationActual = durTuple
+                n.duration.tuplets[0].frozen = True
+                n.duration.informClient()
+        # else: pass
 
 # -------------------------------------------------------------------------------
 
@@ -3699,16 +3720,15 @@ class Test(unittest.TestCase):
         self.assertEqual(d.type, '16th')
         self.assertFalse(d.linked)  # note set
 
-
-#         d = duration.Duration()
-#         d.setTypeUnlinked('quarter')
-#         self.assertEqual(d.type, 'quarter')
-#         self.assertEqual(d.quarterLength, 0.0)  # note set
-#         self.assertFalse(d.linked)  # note set
-#
-#         d.setQuarterLengthUnlinked(20)
-#         self.assertEqual(d.quarterLength, 20.0)
-#         self.assertFalse(d.linked)  # note set
+        # d = duration.Duration()
+        # d.setTypeUnlinked('quarter')
+        # self.assertEqual(d.type, 'quarter')
+        # self.assertEqual(d.quarterLength, 0.0)  # note set
+        # self.assertFalse(d.linked)  # note set
+        #
+        # d.setQuarterLengthUnlinked(20)
+        # self.assertEqual(d.quarterLength, 20.0)
+        # self.assertFalse(d.linked)  # note set
 
 
     def x_testStrangeMeasure(self):
diff --git a/music21/interval.py b/music21/interval.py
index 7f2588ac7..6b2c47959 100644
--- a/music21/interval.py
+++ b/music21/interval.py
@@ -1676,7 +1676,7 @@ class DiatonicInterval(IntervalBase):
 
         if isinstance(generic, GenericInterval):
             self.generic = generic
-        elif common.isNum(generic) or isinstance(generic, str):
+        elif common.isInt(generic) or isinstance(generic, str):
             self.generic = GenericInterval(generic)
         else:  # pragma: no cover
             # too rare to cover.
diff --git a/music21/layout.py b/music21/layout.py
index 9cbde027b..2867fd93d 100644
--- a/music21/layout.py
+++ b/music21/layout.py
@@ -429,7 +429,7 @@ class StaffGroup(spanner.Spanner):
                  name: str | None = None,
                  barTogether: t.Literal[True, False, None, 'Mensurstrich'] = True,
                  abbreviation: str | None = None,
-                 symbol: t.Literal['bracket', 'line', 'grace', 'square'] = None,
+                 symbol: t.Literal['bracket', 'line', 'brace', 'square'] = None,
                  **keywords):
         super().__init__(*spannedElements, **keywords)
 
diff --git a/music21/mei/base.py b/music21/mei/base.py
index ee02a3139..0c8a45dfb 100644
--- a/music21/mei/base.py
+++ b/music21/mei/base.py
@@ -176,7 +176,6 @@ from __future__ import annotations
 
 from collections import defaultdict
 from copy import deepcopy
-from fractions import Fraction  # for typing
 import typing as t
 from uuid import uuid4
 from xml.etree.ElementTree import Element, ParseError, fromstring, ElementTree
@@ -201,6 +200,10 @@ from music21 import stream
 from music21 import spanner
 from music21 import tie
 
+
+if t.TYPE_CHECKING:
+    from fractions import Fraction
+
 environLocal = environment.Environment('mei.base')
 
 
diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py
index 4da45cf84..d4142e175 100755
--- a/music21/metadata/__init__.py
+++ b/music21/metadata/__init__.py
@@ -1625,11 +1625,8 @@ class Metadata(base.Music21Object):
 
         >>> md.bestTitle = 'Bonaparte'
         Traceback (most recent call last):
-        AttributeError: can't set...
+        AttributeError: can't set attribute 'bestTitle'
         '''
-        # When 3.10 is minimum version, change to:
-        # AttributeError: can't set attribute 'bestTitle'
-
         searchId = (
             'title',
             'popularTitle',
diff --git a/music21/midi/translate.py b/music21/midi/translate.py
index 3854fff20..6a9d2f08a 100644
--- a/music21/midi/translate.py
+++ b/music21/midi/translate.py
@@ -1261,9 +1261,10 @@ def getPacketFromMidiEvent(
     return post
 
 
+# noinspection PyTypeChecker
 def elementToMidiEventList(
     el: base.Music21Object
-) -> list[midi.MidiEvent] | None:
+) -> list[midi.MidiEvent | midi.MidiEvent] | None:
     '''
     Return a list of MidiEvents (or None) from a Music21Object,
     assuming that dynamics have already been applied, etc.
@@ -1278,42 +1279,40 @@ def elementToMidiEventList(
     [<music21.midi.MidiEvent NOTE_ON, track=None, channel=1, pitch=60, velocity=90>,
      <music21.midi.MidiEvent NOTE_OFF, track=None, channel=1, pitch=60, velocity=0>]
     '''
-    # TODO: this is the best use of the switch statement when minimum Python
-    #    version is 3.10
-    sub: list[midi.DeltaTime | midi.MidiEvent] | None
-    if isinstance(el, note.Rest):
-        return None
-    elif isinstance(el, note.Note):
-        # get a list of midi events
-        # using this property here is easier than using the above conversion
-        # methods, as we do not need to know what the object is
-        sub = noteToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, note.Unpitched):
-        sub = noteToMidiEvents(el, includeDeltaTime=False, channel=10)
-    elif isinstance(el, chord.Chord):
-        # TODO: skip Harmony unless showAsChord
-        sub = chordToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, percussion.PercussionChord):
-        sub = chordToMidiEvents(el, includeDeltaTime=False, channel=10)
-    elif isinstance(el, dynamics.Dynamic):
-        return None  # dynamics have already been applied to notes
-    elif isinstance(el, meter.TimeSignature):
-        # return a pair of events
-        sub = timeSignatureToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, key.KeySignature):
-        sub = keySignatureToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, tempo.MetronomeMark):
-        # any tempo indication will work
-        # note: tempo indications need to be in channel one for most playback
-        sub = tempoToMidiEvents(el, includeDeltaTime=False)
-    elif isinstance(el, instrument.Instrument):
-        # first instrument will have been gathered above with get start elements
-        sub = instrumentToMidiEvents(el, includeDeltaTime=False)
-    else:
-        # other objects may have already been added
-        return None
-
-    return sub
+    # remove Jetbrains "noinspection PyTypeChecker" when
+    # https://youtrack.jetbrains.com/issue/PY-48011/Pattern-Matching-Type-inference
+    # is fixed.  Until then, don't implement more Structural Pattern Matching.
+    match el:
+        case note.Rest():
+            return None
+        case note.Note():
+            # get a list of midi events
+            # using this property here is easier than using the above conversion
+            # methods, as we do not need to know what the object is
+            return noteToMidiEvents(el, includeDeltaTime=False)
+        case note.Unpitched():
+            return noteToMidiEvents(el, includeDeltaTime=False, channel=10)
+        case chord.Chord():
+            # TODO: skip Harmony unless showAsChord
+            return chordToMidiEvents(el, includeDeltaTime=False)
+        case percussion.PercussionChord():
+            return chordToMidiEvents(el, includeDeltaTime=False, channel=10)
+        case dynamics.Dynamic():
+            return None  # dynamics have already been applied to notes
+        case meter.TimeSignature():
+            return timeSignatureToMidiEvents(el, includeDeltaTime=False)
+        case key.KeySignature():
+            return keySignatureToMidiEvents(el, includeDeltaTime=False)
+        case tempo.MetronomeMark():
+            # any tempo indication will work
+            # note: tempo indications need to be in channel one for most playback
+            return tempoToMidiEvents(el, includeDeltaTime=False)
+        case instrument.Instrument():
+            # first instrument will have been gathered above with get start elements
+            return instrumentToMidiEvents(el, includeDeltaTime=False)
+        case _:
+            # other objects may have already been added
+            return None
 
 
 def streamToPackets(
@@ -3227,6 +3226,8 @@ class Test(unittest.TestCase):
                  instrument.Trumpet]
 
         sc = scale.MinorScale()
+        # something strange with PyCharm type checker here.  Thinks it is off one argument.
+        # noinspection PyTypeChecker
         pitches = sc.getPitches('c2', 'c5')
         random.shuffle(pitches)
 
@@ -3276,6 +3277,8 @@ class Test(unittest.TestCase):
         import random
 
         sc = scale.MajorScale()
+        # something strange with PyCharm type checker here.  Thinks it is off one argument.
+        # noinspection PyTypeChecker
         pitches = sc.getPitches('c2', 'c5')
         random.shuffle(pitches)
 
@@ -3328,6 +3331,8 @@ class Test(unittest.TestCase):
                  instrument.Piano]
 
         sc = scale.MajorScale()
+        # something strange with PyCharm type checker here.  Thinks it is off one argument.
+        # noinspection PyTypeChecker
         pitches = sc.getPitches('c2', 'c5')
         # random.shuffle(pitches)
 
@@ -4072,17 +4077,19 @@ class Test(unittest.TestCase):
     def testExportUnpitched(self):
         from music21 import midi as midiModule
 
-        m = stream.Measure(
-            [instrument.BassDrum(),
+        m = stream.Measure([
+            instrument.BassDrum(),
             note.Unpitched(),
-            percussion.PercussionChord([note.Unpitched(), note.Unpitched()])]
-        )
-        trks = streamHierarchyToMidiTracks(m)
-        bd_trk = trks[1]
+            percussion.PercussionChord([note.Unpitched(), note.Unpitched()]),
+        ])
+        tracks = streamHierarchyToMidiTracks(m)
+        bass_drum_track = tracks[1]
 
-        self.assertTrue({ev.channel for ev in bd_trk.events}, {10})
+        self.assertTrue({ev.channel for ev in bass_drum_track.events}, {10})
         note_ons = [
-            ev for ev in bd_trk.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
+            ev for ev in bass_drum_track.events
+            if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON
+        ]
         self.assertEqual(len(note_ons), 3)
         self.assertEqual({ev.pitch for ev in note_ons}, {35})
 
@@ -4090,24 +4097,24 @@ class Test(unittest.TestCase):
         m.pop(0)
         m.insert(0, instrument.Instrument())
 
-        trks = streamHierarchyToMidiTracks(m)
-        drum_trk = trks[1]
+        tracks = streamHierarchyToMidiTracks(m)
+        drum_track = tracks[1]
 
-        self.assertTrue({ev.channel for ev in drum_trk.events}, {10})
+        self.assertTrue({ev.channel for ev in drum_track.events}, {10})
         note_ons = [
-            ev for ev in drum_trk.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
+            ev for ev in drum_track.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
         self.assertEqual(len(note_ons), 3)
         self.assertEqual({ev.pitch for ev in note_ons}, {60})  # fallback
 
         # Change the stored instrument: affects that note only
         m.notes.first().storedInstrument = instrument.Agogo()
 
-        trks = streamHierarchyToMidiTracks(m)
-        mixed_trk = trks[1]
+        tracks = streamHierarchyToMidiTracks(m)
+        mixed_track = tracks[1]
 
-        self.assertTrue({ev.channel for ev in mixed_trk.events}, {10})
+        self.assertTrue({ev.channel for ev in mixed_track.events}, {10})
         note_ons = [
-            ev for ev in mixed_trk.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
+            ev for ev in mixed_track.events if ev.type is midiModule.ChannelVoiceMessages.NOTE_ON]
         self.assertEqual(len(note_ons), 3)
         self.assertEqual([ev.pitch for ev in note_ons], [67, 60, 60])
 
diff --git a/music21/musedata/translate.py b/music21/musedata/translate.py
index 33ecc70c5..fc2fec5b1 100644
--- a/music21/musedata/translate.py
+++ b/music21/musedata/translate.py
@@ -438,46 +438,45 @@ class Test(unittest.TestCase):
             str(post),
             '<music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/partial/right>>')
 
-
-#         s = corpus.parse('hwv56', '1-18')
-#         self.assertEqual(len(s.parts), 5)
-#         # the fourth part is vocal, and has no beams defined
-#         self.assertEqual(str(s.parts[3].getElementsByClass(
-#             'Measure')[3].notesAndRests[0].beams), '<music21.beam.Beams >')
-#         self.assertEqual(str(s.parts[3].getElementsByClass(
-#             'Measure')[3].notesAndRests[0].lyric), 'sud')
-#
-#         # the bottom part has 8ths beamed two to a bar
-#         self.assertEqual(str(s.parts[4].getElementsByClass(
-#             'Measure')[3].notesAndRests[0].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/start>>')
-#         self.assertEqual(str(s.parts[4].getElementsByClass(
-#             'Measure')[3].notesAndRests[1].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/continue>>')
-#         self.assertEqual(str(s.parts[4].getElementsByClass(
-#             'Measure')[3].notesAndRests[2].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/continue>>')
-#         self.assertEqual(str(s.parts[4].getElementsByClass(
-#             'Measure')[3].notesAndRests[3].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/stop>>')
-#
-#         # s.show()
-#         # test that stage1 files continue to have makeBeams called
-#         s = corpus.parse('bwv1080', '16')
-#         # measure two has 9/16 beamed in three beats of 16ths
-#         self.assertEqual(len(s.parts), 2)
-#
-#         # s.parts[0].getElementsByClass(stream.Measure)[1].show()
-#
-#         self.assertEqual(str(s.parts[0].getElementsByClass(
-#             'Measure')[1].notesAndRests[0].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>>')
-#         self.assertEqual(str(s.parts[0].getElementsByClass(
-#             'Measure')[1].notesAndRests[1].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/continue>>')
-#         self.assertEqual(str(s.parts[0].getElementsByClass(
-#             'Measure')[1].notesAndRests[2].beams),
-#             '<music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/stop>>')
+        # s = corpus.parse('hwv56', '1-18')
+        # self.assertEqual(len(s.parts), 5)
+        # # the fourth part is vocal, and has no beams defined
+        # self.assertEqual(str(s.parts[3].getElementsByClass(
+        #     'Measure')[3].notesAndRests[0].beams), '<music21.beam.Beams >')
+        # self.assertEqual(str(s.parts[3].getElementsByClass(
+        #     'Measure')[3].notesAndRests[0].lyric), 'sud')
+        #
+        # # the bottom part has 8ths beamed two to a bar
+        # self.assertEqual(str(s.parts[4].getElementsByClass(
+        #     'Measure')[3].notesAndRests[0].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/start>>')
+        # self.assertEqual(str(s.parts[4].getElementsByClass(
+        #     'Measure')[3].notesAndRests[1].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/continue>>')
+        # self.assertEqual(str(s.parts[4].getElementsByClass(
+        #     'Measure')[3].notesAndRests[2].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/continue>>')
+        # self.assertEqual(str(s.parts[4].getElementsByClass(
+        #     'Measure')[3].notesAndRests[3].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/stop>>')
+        #
+        # # s.show()
+        # # test that stage1 files continue to have makeBeams called
+        # s = corpus.parse('bwv1080', '16')
+        # # measure two has 9/16 beamed in three beats of 16ths
+        # self.assertEqual(len(s.parts), 2)
+        #
+        # # s.parts[0].getElementsByClass(stream.Measure)[1].show()
+        #
+        # self.assertEqual(str(s.parts[0].getElementsByClass(
+        #     'Measure')[1].notesAndRests[0].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/start>/<music21.beam.Beam 2/start>>')
+        # self.assertEqual(str(s.parts[0].getElementsByClass(
+        #     'Measure')[1].notesAndRests[1].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/continue>/<music21.beam.Beam 2/continue>>')
+        # self.assertEqual(str(s.parts[0].getElementsByClass(
+        #     'Measure')[1].notesAndRests[2].beams),
+        #     '<music21.beam.Beams <music21.beam.Beam 1/stop>/<music21.beam.Beam 2/stop>>')
 
 
     def testAccidentals(self):
@@ -526,87 +525,86 @@ class Test(unittest.TestCase):
         # s.show()
 
 
-#     def testMuseDataStage1A(self):
-#         from music21 import corpus
-#         s = corpus.parse('k168', 1)
-#
-#         self.assertEqual(len(s.parts), 4)
-#         self.assertEqual(str(s.parts[0][meter.TimeSignature].first()),
-#                '<music21.meter.TimeSignature 4/4>')
-#
-#         self.assertEqual(
-#            [n.offset for n in s.parts[0].getElementsByClass(stream.Measure)[0].notes],
-#            [0.0, 3.0, 3.5, 3.75])
-#
-#         self.assertEqual([n.nameWithOctave for
-#                n in s.parts[0].getElementsByClass(stream.Measure)[0].notes],
-#                    ['F5', 'F5', 'E5', 'D5'])
-#
-#         self.assertEqual(
-#             [n.offset for n in s.parts[1].getElementsByClass(stream.Measure)[0].notes],
-#             [1.0, 2.0, 3.0])
+    # def testMuseDataStage1A(self):
+    #     from music21 import corpus
+    #     s = corpus.parse('k168', 1)
+    #
+    #     self.assertEqual(len(s.parts), 4)
+    #     self.assertEqual(str(s.parts[0][meter.TimeSignature].first()),
+    #            '<music21.meter.TimeSignature 4/4>')
+    #
+    #     self.assertEqual(
+    #        [n.offset for n in s.parts[0].getElementsByClass(stream.Measure)[0].notes],
+    #        [0.0, 3.0, 3.5, 3.75])
+    #
+    #     self.assertEqual([n.nameWithOctave for
+    #            n in s.parts[0].getElementsByClass(stream.Measure)[0].notes],
+    #                ['F5', 'F5', 'E5', 'D5'])
+    #
+    #     self.assertEqual(
+    #         [n.offset for n in s.parts[1].getElementsByClass(stream.Measure)[0].notes],
+    #         [1.0, 2.0, 3.0])
 
-#     def testMuseDataStage1B(self):
-#         from music21 import corpus
-#         s = corpus.parse('k169', 3)
-#
-#         self.assertEqual(len(s.parts), 4)
-#         self.assertEqual(str(s.parts[0][meter.TimeSignature].first()),
-#             '<music21.meter.TimeSignature 3/4>')
-#
-#         self.assertEqual(
-#             [n.offset for n in s.parts[0].getElementsByClass(stream.Measure)[0].notes],
-#                [0.0, 2.0])
-#
-#         self.assertEqual([n.nameWithOctave for
-#                    n in s.parts[0].getElementsByClass(stream.Measure)[0].notes], ['A4', 'B4'])
-#
-#         self.assertEqual([n.offset
-#                           for n in s.parts[2].getElementsByClass(stream.Measure)[0].notes],
-#            [0.0, 1.0, 2.0])
-
-
-#     def testMuseDataImportTempoA(self):
-#         from music21 import corpus
-#         # a small file
-#         s = corpus.parse('movement2-09.md')
-#         self.assertEqual(len(s.parts), 5)
-#         # the tempo is found in the 4th part here
-#         self.assertEqual(str(
-#             s.parts[3][tempo.TempoIndication].first()),
-#             '<music21.tempo.MetronomeMark Largo e piano Quarter=46>')
-#         # s.show()
-#
-#         s = corpus.parse('movement2-07.md')
-#         self.assertEqual(str(
-#             s[tempo.TempoIndication].first()),
-#             '<music21.tempo.MetronomeMark Largo Quarter=46>')
-
-#     def testMuseDataImportDynamicsA(self):
-#         # note: this is importing a large work, but this seems to presently
-#         # be the only one with dynamics
-#
-#         # TODO: Turn back on when a smaller work is found...
-#         from music21 import corpus
-#         s = corpus.parse('symphony94', 3)
-#         sFlat = s.flatten()
-#         # s.show()
-#         self.assertEqual(len(sFlat.getElementsByClass(dynamics.Dynamic)), 79)
-#
-#
-#     def testMuseDataImportErrorA(self):
-#         from music21 import corpus
-#         # this files was crashing in the handling of an error in beam notation
-#         s = corpus.parse('haydn/opus55no1/movement2.md')
-#         self.assertEqual(len(s[note.Note]), 1735)
-#
-#         # s.show('t')
-#
-#     def testMuseDataImportErrorB(self):
-#         # this file has a malformed END repeated twice
-#         from music21 import corpus
-#         s = corpus.parse('haydn/opus71no1/movement1.zip')
-#         self.assertEqual(len(s[note.Note]), 2792)
+    # def testMuseDataStage1B(self):
+    #     from music21 import corpus
+    #     s = corpus.parse('k169', 3)
+    #
+    #     self.assertEqual(len(s.parts), 4)
+    #     self.assertEqual(str(s.parts[0][meter.TimeSignature].first()),
+    #         '<music21.meter.TimeSignature 3/4>')
+    #
+    #     self.assertEqual(
+    #         [n.offset for n in s.parts[0].getElementsByClass(stream.Measure)[0].notes],
+    #            [0.0, 2.0])
+    #
+    #     self.assertEqual([n.nameWithOctave for
+    #                n in s.parts[0].getElementsByClass(stream.Measure)[0].notes], ['A4', 'B4'])
+    #
+    #     self.assertEqual([n.offset
+    #                       for n in s.parts[2].getElementsByClass(stream.Measure)[0].notes],
+    #        [0.0, 1.0, 2.0])
+
+
+    # def testMuseDataImportTempoA(self):
+    #     from music21 import corpus
+    #     # a small file
+    #     s = corpus.parse('movement2-09.md')
+    #     self.assertEqual(len(s.parts), 5)
+    #     # the tempo is found in the 4th part here
+    #     self.assertEqual(str(
+    #         s.parts[3][tempo.TempoIndication].first()),
+    #         '<music21.tempo.MetronomeMark Largo e piano Quarter=46>')
+    #     # s.show()
+    #
+    #     s = corpus.parse('movement2-07.md')
+    #     self.assertEqual(str(
+    #         s[tempo.TempoIndication].first()),
+    #         '<music21.tempo.MetronomeMark Largo Quarter=46>')
+
+    # def testMuseDataImportDynamicsA(self):
+    #     # note: this is importing a large work, but this seems to presently
+    #     # be the only one with dynamics
+    #
+    #     # TODO: Turn back on when a smaller work is found...
+    #     from music21 import corpus
+    #     s = corpus.parse('symphony94', 3)
+    #     sFlat = s.flatten()
+    #     # s.show()
+    #     self.assertEqual(len(sFlat.getElementsByClass(dynamics.Dynamic)), 79)
+
+    # def testMuseDataImportErrorA(self):
+    #     from music21 import corpus
+    #     # this files was crashing in the handling of an error in beam notation
+    #     s = corpus.parse('haydn/opus55no1/movement2.md')
+    #     self.assertEqual(len(s[note.Note]), 1735)
+    #
+    #     # s.show('t')
+
+    # def testMuseDataImportErrorB(self):
+    #     # this file has a malformed END repeated twice
+    #     from music21 import corpus
+    #     s = corpus.parse('haydn/opus71no1/movement1.zip')
+    #     self.assertEqual(len(s[note.Note]), 2792)
 
 # ------------------------------------------------------------------------------
 # define presented order in documentation
diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py
index 08f873190..b8e2c554b 100644
--- a/music21/musicxml/m21ToXml.py
+++ b/music21/musicxml/m21ToXml.py
@@ -6431,7 +6431,7 @@ class MeasureExporter(XMLExporterBase):
 
             if endingType:
                 mxEnding = Element('ending')
-                numberList = self.rbSpanners[0].getNumberList()
+                numberList = self.rbSpanners[0].numberRange
                 numberStr = str(numberList[0])
                 # 0 is not a valid "ending-number"
                 if numberStr == '0':
diff --git a/music21/musicxml/partStaffExporter.py b/music21/musicxml/partStaffExporter.py
index 16fd0ad6c..25d03e932 100644
--- a/music21/musicxml/partStaffExporter.py
+++ b/music21/musicxml/partStaffExporter.py
@@ -25,10 +25,12 @@ from music21.common.types import M21ObjType
 from music21.key import KeySignature
 from music21.layout import StaffGroup
 from music21.meter import TimeSignature
-from music21 import stream  # for typing
+from music21 import stream
+
 from music21.musicxml import helpers
 from music21.musicxml.xmlObjects import MusicXMLExportException, MusicXMLWarning
 
+
 def addStaffTags(
     measure: Element,
     staffNumber: int,
diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py
index 4855fefe3..118755fe0 100644
--- a/music21/musicxml/xmlToM21.py
+++ b/music21/musicxml/xmlToM21.py
@@ -15,29 +15,24 @@ from __future__ import annotations
 import copy
 import fractions
 import io
-import re
-import warnings
-
 from math import isclose
+import re
 import typing as t
-
+import warnings
 import xml.etree.ElementTree as ET
 
-from music21 import common
-from music21 import exceptions21
-from music21.musicxml import xmlObjects
-from music21.musicxml.xmlObjects import MusicXMLImportException, MusicXMLWarning
-
 from music21 import articulations
 from music21 import bar
-from music21 import base  # for typing
 from music21 import beam
 from music21 import chord
 from music21 import clef
+from music21 import common
 from music21 import defaults
 from music21 import duration
 from music21 import dynamics
 from music21 import editorial
+from music21 import environment
+from music21 import exceptions21
 from music21 import expressions
 from music21 import harmony  # for chord symbols
 from music21 import instrument
@@ -45,9 +40,9 @@ from music21 import interval  # for transposing instruments
 from music21 import key
 from music21 import layout
 from music21 import metadata
+from music21 import meter
 from music21.midi.percussion import MIDIPercussionException, PercussionMapper
 from music21 import note
-from music21 import meter
 from music21 import percussion
 from music21 import pitch
 from music21 import repeat
@@ -59,12 +54,15 @@ from music21 import tempo
 from music21 import text  # for text boxes
 from music21 import tie
 
-from music21 import environment
+from music21.musicxml import xmlObjects
+from music21.musicxml.xmlObjects import MusicXMLImportException, MusicXMLWarning
 
-environLocal = environment.Environment('musicxml.xmlToM21')
+if t.TYPE_CHECKING:
+    from music21 import base
+    # what goes in a `.staffReference`
+    StaffReferenceType = dict[int, list[base.Music21Object]]
 
-# what goes in a `.staffReference`
-StaffReferenceType = dict[int, list[base.Music21Object]]
+environLocal = environment.Environment('musicxml.xmlToM21')
 
 # const
 NO_STAFF_ASSIGNED = 0
@@ -879,6 +877,8 @@ class MusicXMLImporter(XMLParserBase):
             self.xmlText = self.xmlText.decode('utf-8')
         sio = io.StringIO(self.xmlText)
         try:
+            # StringIO is a SupportsRead[str] type.
+            # noinspection PyTypeChecker
             etree = ET.parse(sio)
             self.xmlRoot = etree.getroot()
         except ET.ParseError:
@@ -4732,7 +4732,9 @@ class MeasureParser(XMLParserBase):
             # not complete
 
             # TODO: this should also filter by number (in theory.)
-            rbSpanners = self.spannerBundle.getByClass('RepeatBracket').getByCompleteStatus(False)
+            rbSpanners = self.spannerBundle.getByClass(
+                spanner.RepeatBracket
+            ).getByCompleteStatus(False)
             # if we have no complete bracket objects, must start a new one
             if not rbSpanners:
                 # create with this measure as the object
diff --git a/music21/note.py b/music21/note.py
index 4b7126c51..5d141674f 100644
--- a/music21/note.py
+++ b/music21/note.py
@@ -456,7 +456,7 @@ class Lyric(prebase.ProtoM21Object, style.StyleMixin):
 
     @number.setter
     def number(self, value: int) -> None:
-        if not common.isNum(value):
+        if not common.isInt(value):
             raise LyricException('Number best be number')
         self._number = value
 
diff --git a/music21/repeat.py b/music21/repeat.py
index 2cb53855f..072322288 100644
--- a/music21/repeat.py
+++ b/music21/repeat.py
@@ -402,7 +402,7 @@ repeatExpressionReference = [
 ]
 
 # ------------------------------
-def insertRepeatEnding(s, start, end, endingNumber=1, *, inPlace=False):
+def insertRepeatEnding(s, start, end, endingNumber: int = 1, *, inPlace=False):
     '''
     Designates a range of measures as being repeated endings (i.e. first and second endings)
     within a stream s, where s either contains measures,
@@ -738,7 +738,7 @@ class Expander:
 
         # see if there are any repeat brackets
         self._repeatBrackets = self._src.flatten().getElementsByClass(
-            'RepeatBracket'
+            spanner.RepeatBracket
         ).stream()
 
         self._srcMeasureCount = len(self._srcMeasureStream)
@@ -1114,13 +1114,13 @@ class Expander:
                 # environLocal.printDebug(['_groupRepeatBracketIndices', rb])
                 # match = False
                 if rb.isFirst(m):  # for this rb, is this the first measures
-                    if rb.getNumberList()[0] in foundRBNumbers:
+                    if rb.numberRange[0] in foundRBNumbers:
                         # we have a new group
                         groups.append(groupIndices)
                         foundRBNumbers = []
                         groupIndices = {'repeatBrackets': [], 'measureIndices': []}
                     # store rb numbers to monitor when we are in a new group
-                    foundRBNumbers += rb.getNumberList()  # concat list
+                    foundRBNumbers += rb.numberRange  # concat list
                     # groupIndices['measureIndices'].append(i)
                     # need to jump to the index of the last measure this
                     # rb contains; need to add indices for measures found within
@@ -1175,7 +1175,7 @@ class Expander:
                     # get number list will return inclusive values; i.e.,
                     # 1,3 will
                     # return 1, 2, 3
-                    target += rb.getNumberList()
+                    target += rb.numberRange
                 match = list(range(1, max(target) + 1))  # max of target + 1
                 if match != target:
                     environLocal.printDebug([
@@ -1678,7 +1678,7 @@ class Expander:
             # are at the last repeat under this bracket
             if data['validIndices'] is not None:
                 # repeat times is the number of elements in the list
-                repeatTimes = len(data['repeatBracket'].getNumberList())
+                repeatTimes = len(data['repeatBracket'].numberRange)
                 # just get the expanded section
                 # streamObj.show('t')
                 out = self.processInnermostRepeatBars(
diff --git a/music21/spanner.py b/music21/spanner.py
index f3727ca72..080edace5 100644
--- a/music21/spanner.py
+++ b/music21/spanner.py
@@ -20,7 +20,7 @@ can be found in modules such as :ref:`moduleDynamics` (for things such as cresce
 '''
 from __future__ import annotations
 
-from collections.abc import Sequence
+from collections.abc import Sequence, Iterable
 import copy
 import typing as t
 import unittest
@@ -1227,7 +1227,7 @@ class RepeatBracket(Spanner):
     >>> sp.number = 3
     >>> sp
     <music21.spanner.RepeatBracket 3 <music21.stream.Measure 0 offset=0.0>>
-    >>> sp.getNumberList()  # the list of repeat numbers
+    >>> sp.numberRange  # the list of repeat numbers
     [3]
     >>> sp.number
     '3'
@@ -1235,7 +1235,7 @@ class RepeatBracket(Spanner):
     Range of repeats as string:
 
     >>> sp.number = '1-3'
-    >>> sp.getNumberList()
+    >>> sp.numberRange
     [1, 2, 3]
     >>> sp.number
     '1-3'
@@ -1243,7 +1243,7 @@ class RepeatBracket(Spanner):
     Range of repeats as list:
 
     >>> sp.number = [2, 3]
-    >>> sp.getNumberList()
+    >>> sp.numberRange
     [2, 3]
     >>> sp.number
     '2, 3'
@@ -1251,7 +1251,7 @@ class RepeatBracket(Spanner):
     Comma separated numbers:
 
     >>> sp.number = '1, 2, 3'
-    >>> sp.getNumberList()
+    >>> sp.numberRange
     [1, 2, 3]
     >>> sp.number
     '1-3'
@@ -1259,7 +1259,7 @@ class RepeatBracket(Spanner):
     Disjunct numbers:
 
     >>> sp.number = '1, 2, 3, 7'
-    >>> sp.getNumberList()
+    >>> sp.numberRange
     [1, 2, 3, 7]
     >>> sp.number
     '1, 2, 3, 7'
@@ -1276,131 +1276,129 @@ class RepeatBracket(Spanner):
     >>> sp.number
     '1, 2, 3, 7'
     '''
+
+    _DOC_ATTR = {
+        'numberRange': '''
+            Get a contiguous list of repeat numbers that are applicable for this instance.
+
+            Will always have at least one element, but [0] means undefined
+
+            >>> rb = spanner.RepeatBracket()
+            >>> rb.numberRange
+            [0]
+
+            >>> rb.number = '1,2'
+            >>> rb.numberRange
+            [1, 2]
+        ''',
+        'overrideDisplay': '''
+            Override the string representation of this bracket, or use
+            None to not override.
+        ''',
+    }
+
     def __init__(self,
                  *spannedElements,
-                 number: int | None = None,
+                 number: int | str | Iterable[int] = 0,
                  overrideDisplay: str | None = None,
                  **keywords):
         super().__init__(*spannedElements, **keywords)
-
-        self._number: int | None = None
         # store a range, inclusive of the single number assignment
-        self._numberRange: list[int] = []
-        # are there exactly two numbers that should be written as  3, 4 not 3-4.
-        self._numberSpanIsAdjacent: bool = False
-        # can we write as '3, 4' or '5-10' and not as '1, 5, 6, 11'
-        self._numberSpanIsContiguous: bool = True
+        self.numberRange: list[int] = []
         self.overrideDisplay = overrideDisplay
+        self.number = number
 
-        if number is not None:
-            self.number = number
+    @property
+    def _numberSpanIsAdjacent(self) -> bool:
+        '''
+        are there exactly two numbers that should be written as 3, 4 not 3-4.
+        '''
+        return len(self.numberRange) == 2 and self.numberRange[0] == self.numberRange[1] - 1
+
+    @property
+    def _numberSpanIsContiguous(self) -> bool:
+        '''
+        can we write as '3, 4' or '5-10' and not as '1, 5, 6, 11'
+        '''
+        return common.contiguousList(self.numberRange)
 
     # property to enforce numerical numbers
-    def _getNumber(self):
+    def _getNumber(self) -> str:
         '''
         This must return a string, as we may have single numbers or lists.
-        For a raw numerical list, use getNumberList() below.
+        For a raw numerical list, look at `.numberRange`.
         '''
-        if not self._numberRange:
-            return ''
-        elif len(self._numberRange) == 1:
-            return str(self._number)
+        if len(self.numberRange) == 1:
+            if self.numberRange[0] == 0:
+                return ''
+            return str(self.numberRange[0])
         else:
             if not self._numberSpanIsContiguous:
-                return ', '.join([str(x) for x in self._numberRange])
+                return ', '.join([str(x) for x in self.numberRange])
             elif self._numberSpanIsAdjacent:
-                return f'{self._numberRange[0]}, {self._numberRange[-1]}'
+                return f'{self.numberRange[0]}, {self.numberRange[-1]}'
             else:  # range of values
-                return f'{self._numberRange[0]}-{self._numberRange[-1]}'
+                return f'{self.numberRange[0]}-{self.numberRange[-1]}'
 
-    def _setNumber(self, value):
+    def _setNumber(self, value: int | str | Iterable[int]):
         '''
         Set the bracket number. There may be range of values provided
         '''
-        if value in ('', None):
-            # assume this is 1
-            self._numberRange = [1]
-            self._number = 1
-        elif common.isIterable(value):
-            self._numberRange = []  # clear
+        if value == '':
+            # undefined.
+            self.numberRange = [0]
+        elif common.holdsType(value, int):
+            self.numberRange = []  # clear
             for x in value:
-                if common.isNum(x):
-                    self._numberRange.append(x)
-                else:
-                    raise SpannerException(
-                        f'number for RepeatBracket must be a number, not {value!r}')
-            self._number = min(self._numberRange)
-            self._numberSpanIsContiguous = common.contiguousList(self._numberRange)
-            if (len(self._numberRange) == 2) and (self._numberRange[0] == self._numberRange[1] - 1):
-                self._numberSpanIsAdjacent = True
-            else:
-                self._numberSpanIsAdjacent = False
-
+                self.numberRange.append(x)
         elif isinstance(value, str):
             # assume defined a range with a dash; assumed inclusive
             if '-' in value:
                 start, end = value.split('-')
-                self._numberRange = list(range(int(start), int(end) + 1))
-                self._numberSpanIsAdjacent = False
-                self._numberSpanIsContiguous = True
+                self.numberRange = list(range(int(start), int(end) + 1))
 
             elif ',' in value:
-                self._numberRange = []  # clear
-                for x in value.split(','):
-                    x = int(x.strip())
-                    self._numberRange.append(x)
-                self._number = min(self._numberRange)
-
-                # returns bool
-                self._numberSpanIsContiguous = common.contiguousList(self._numberRange)
-                if ((len(self._numberRange) == 2)
-                        and (self._numberRange[0] == self._numberRange[1] - 1)):
-                    self._numberSpanIsAdjacent = True
-                else:
-                    self._numberSpanIsAdjacent = False
-
+                self.numberRange = []  # clear
+                for one_letter_value in value.split(','):
+                    one_number = int(one_letter_value.strip())
+                    self.numberRange.append(one_number)
             elif value.isdigit():
-                self._numberRange.append(int(value))
+                self.numberRange = [int(value)]
             else:
                 raise SpannerException(f'number for RepeatBracket must be a number, not {value!r}')
-            self._number = min(self._numberRange)
-        elif common.isNum(value):
-            self._numberRange = []  # clear
-            self._number = value
-            if value not in self._numberRange:
-                self._numberRange.append(value)
+        elif common.isInt(value):
+            self.numberRange = []  # clear
+            if value not in self.numberRange:
+                self.numberRange.append(value)
         else:
             raise SpannerException(f'number for RepeatBracket must be a number, not {value!r}')
 
     number = property(_getNumber, _setNumber, doc='''
+            Get or set the number -- returning a string always.
+
+            >>> rb = spanner.RepeatBracket()
+            >>> rb.number
+            ''
+            >>> rb.number = '5-7'
+            >>> rb.number
+            '5-7'
+            >>> rb.numberRange
+            [5, 6, 7]
+            >>> rb.number = 1
         ''')
 
-    def getNumberList(self):
-        '''Get a contiguous list of repeat numbers that are applicable for this instance.
-
-        Will always have at least one element, but [0] means undefined
-
-        >>> rb = spanner.RepeatBracket()
-        >>> rb.getNumberList()
-        [0]
-
-        >>> rb.number = '1,2'
-        >>> rb.getNumberList()
-        [1, 2]
+    @common.deprecated('v9', 'v10', 'Look at .numberRange instead')
+    def getNumberList(self):  # pragma: no cover
         '''
-        nr = self._numberRange
-        if not nr:
-            return [0]
-        else:
-            return nr
+        Deprecated -- just look at .numberRange
+        '''
+        return self.numberRange
 
     def _reprInternal(self):
         if self.overrideDisplay is not None:
             msg = self.overrideDisplay + ' '
-        elif self.number is not None:
-            msg = str(self.number) + ' '
         else:
-            msg = ''
+            msg = self.number + ' '
         return msg + super()._reprInternal()
 
 
@@ -1684,8 +1682,8 @@ class Line(Spanner):
         super().__init__(*spannedElements, **keywords)
 
         DEFAULT_TICK = 'down'
-        self._endTick = DEFAULT_TICK  # can ne up/down/arrow/both/None
-        self._startTick = DEFAULT_TICK  # can ne up/down/arrow/both/None
+        self._endTick = DEFAULT_TICK  # can be up/down/arrow/both/None
+        self._startTick = DEFAULT_TICK  # can be up/down/arrow/both/None
 
         self._endHeight = None  # for up/down, specified in tenths
         self._startHeight = None  # for up/down, specified in tenths
diff --git a/music21/stream/base.py b/music21/stream/base.py
index 5ed2fb103..5ce4b2dc6 100644
--- a/music21/stream/base.py
+++ b/music21/stream/base.py
@@ -55,7 +55,6 @@ from music21 import key
 from music21 import metadata
 from music21 import meter
 from music21 import note
-from music21 import pitch  # for typing
 from music21 import tie
 from music21 import repeat
 from music21 import sites
@@ -71,6 +70,7 @@ from music21.stream.enums import GivenElementsBehavior, RecursionType
 
 
 if TYPE_CHECKING:
+    from music21 import pitch
     from music21 import spanner
 
 
@@ -4823,10 +4823,10 @@ class Stream(core.StreamCore, t.Generic[M21ObjType]):
 
         # Replace old measures in spanners with new measures
         # Example: out is a Part, out.spannerBundle has RepeatBrackets spanning measures
-        # TODO: when dropping support for Py3.9 (min=3.10) add strict=True
         for oldM, newM in zip(
             self.getElementsByClass(Measure),
-            out.getElementsByClass(Measure)
+            out.getElementsByClass(Measure),
+            strict=True
         ):
             out.spannerBundle.replaceSpannedElement(oldM, newM)
 
diff --git a/music21/stream/iterator.py b/music21/stream/iterator.py
index 631c8f904..b2f09032b 100644
--- a/music21/stream/iterator.py
+++ b/music21/stream/iterator.py
@@ -32,7 +32,7 @@ from music21.exceptions21 import StreamException
 from music21 import note
 from music21.stream import filters
 from music21 import prebase
-from music21 import base   # just for typing.
+from music21 import base   # just for typing. (but in a bound, so keep here)
 
 from music21.sites import SitesException
 
diff --git a/music21/tinyNotation.py b/music21/tinyNotation.py
index e66d9bd29..3c9f27be2 100644
--- a/music21/tinyNotation.py
+++ b/music21/tinyNotation.py
@@ -235,7 +235,6 @@ import re
 import typing  # not importing as t, because of extensive preexisting use of `t` as token
 import unittest
 
-from music21.base import Music21Object  # for typing only.
 from music21 import note
 from music21 import duration
 from music21 import environment
@@ -246,6 +245,11 @@ from music21 import expressions
 from music21 import meter
 from music21 import pitch
 
+
+if typing.TYPE_CHECKING:
+    from music21.base import Music21Object
+
+
 environLocal = environment.Environment('tinyNotation')
 
 
@@ -269,7 +273,7 @@ class State:
     2
     '''
     # expires after N tokens or never.
-    autoExpires: typing.Union[typing.Literal[False], int] = False
+    autoExpires: typing.Literal[False] | int = False
 
     def __init__(self, parent=None, stateInfo=None):
         self.affectedTokens = []
@@ -283,7 +287,7 @@ class State:
         '''
         pass
 
-    def end(self) -> typing.Optional[Music21Object]:
+    def end(self) -> Music21Object | None:
         '''
         called just after removing state
         '''
@@ -759,12 +763,7 @@ class NoteToken(NoteOrRestToken):
         return t
 
 
-def _getDefaultTokenMap() -> typing.List[
-        typing.Tuple[
-            str,
-            typing.Type[Token]
-        ]
-]:
+def _getDefaultTokenMap() -> list[tuple[str, type[Token]]]:
     '''
     Returns the default tokenMap for TinyNotation.
 
@@ -1124,8 +1123,8 @@ class Converter:
         self.stream = stream.Part()
         self.stateDict = copy.copy(_stateDictDefault)
         self.stringRep = stringRep
-        self.activeStates: typing.List[State] = []
-        self.preTokens: typing.List[str] = []
+        self.activeStates: list[State] = []
+        self.preTokens: list[str] = []
 
         self.generalBracketStateRe = re.compile(r'(\w+){')
         self.tieStateRe = re.compile(r'~')
@@ -1141,7 +1140,7 @@ class Converter:
         self.makeNotation = makeNotation
         self.raiseExceptions = raiseExceptions
         # will be filled by self.setupRegularExpressions()
-        self._tokenMapRe: typing.List[typing.Tuple[typing.Pattern, typing.Type]] = []
+        self._tokenMapRe: list[tuple[typing.Pattern, type]] = []
 
     def load(self, stringRep: str) -> None:
         '''
@@ -1363,7 +1362,7 @@ class Converter:
 
         return t
 
-    def parseEndStates(self, t: str) -> typing.Tuple[str, int]:
+    def parseEndStates(self, t: str) -> tuple[str, int]:
         # noinspection GrazieInspection
         '''
         Trims the endState token (`}`) from the t string


Test patch:
diff --git a/music21/braille/test.py b/music21/braille/test.py
index fdc7a95f7..3cf6dc173 100644
--- a/music21/braille/test.py
+++ b/music21/braille/test.py
@@ -2763,8 +2763,8 @@ Barline final ⠣⠅
         ml[1].append(spanner.Slur(ml[1].notes[0], ml[1].notes[1]))
         ml[3].insert(0.0, expressions.TextExpression('rit.'))
         self.s = bm
-#         self.b = '''
-#         '''
+        # self.b = '''
+        # '''
 
     def test_example13_14(self):
         bm = converter.parse("tinynotation: 3/4 e'4 e' f'# g'2. f'#8 d' a d' e' c'# d'2.").flatten()
@@ -2895,8 +2895,8 @@ Barline final ⠣⠅
         ml[0].insert(0.0, expressions.TextExpression('very sweetly'))
         ml[-1].rightBarline = None
         self.s = bm
-#         self.b = '''
-#         '''
+        # self.b = '''
+        # '''
 
     def test_example13_26(self):
         bm = converter.parse('''
@@ -3287,8 +3287,8 @@ Barline final ⠣⠅
         bm.makeNotation(inPlace=True, cautionaryNotImmediateRepeat=False)
         bm.getElementsByClass(stream.Measure)[-1].rightBarline = None
         self.s = bm
-#         self.b = '''
-#         '''
+        # self.b = '''
+        # '''
 
     def test_example15_11(self):
         bm = converter.parse('''
@@ -3370,8 +3370,8 @@ Barline final ⠣⠅
         bm.makeNotation(inPlace=True, cautionaryNotImmediateRepeat=False)
         bm.getElementsByClass(stream.Measure)[-1].rightBarline = None
         self.s = bm
-#         self.b = '''
-#         '''
+        # self.b = '''
+        # '''
 
     def test_example16_15(self):
         bm = converter.parse('''
diff --git a/music21/corpus/testCorpus.py b/music21/corpus/testCorpus.py
index 0c1844084..1f41bd2d7 100644
--- a/music21/corpus/testCorpus.py
+++ b/music21/corpus/testCorpus.py
@@ -62,13 +62,13 @@ class Test(unittest.TestCase):
         filePathCollection = corpus.getComposer('josquin', ['abc'])
         self.assertGreaterEqual(len(filePathCollection), 8)
 
-#     def testHandelImport(self):
-#         # can get a single file just by file name
-#         unused_fp = corpus.getWork('hwv56/movement1-01')#
-#         fpCollection = corpus.getComposer('handel')
-#         self.assertGreaterEqual(len(fpCollection), 1)
-#         fpCollection = corpus.getComposer('handel', ['md'])
-#         self.assertGreaterEqual(len(fpCollection), 1)
+    # def testHandelImport(self):
+    #     # can get a single file just by file name
+    #     unused_fp = corpus.getWork('hwv56/movement1-01')#
+    #     fpCollection = corpus.getComposer('handel')
+    #     self.assertGreaterEqual(len(fpCollection), 1)
+    #     fpCollection = corpus.getComposer('handel', ['md'])
+    #     self.assertGreaterEqual(len(fpCollection), 1)
 
     def testSearch01(self):
         searchResults = corpus.search('china', field='locale')
@@ -150,81 +150,81 @@ class Test(unittest.TestCase):
         searchResults = corpus.search(ks, field='keySignature')
         self.assertEqual(len(searchResults) >= 32, True, len(searchResults))
 
-#     def testSearch12(self):
-#         # searching virtual entries
-#         searchResults = corpus.search('coltrane', field='composer')
-#         self.assertGreater(len(searchResults), 0)
-#         # returns items in pairs: url and work number
-#         self.assertEqual(searchResults[0].sourcePath,
-#             'http://impromastering.com/uploads/transcription_file/' +
-#             'file/196/Giant_Steps__John_Coltrane_C.xml')
-
-#     def testGetWorkList(self):
-#         self.assertGreaterEqual(len(corpus.corpora.CoreCorpus().getPaths('.md')), 38)
-#         workList = corpus.corpora.CoreCorpus().getWorkList('bach/artOfFugue_bwv1080', 1, '.zip')
-#         self.assertEqual(len(workList), 1)
-#         self.assertEqual(len(
-#                corpus.corpora.CoreCorpus().getWorkList('handel/hwv56', (1, 1), '.md')), 1)
-#         self.assertEqual(len(
-#                corpus.corpora.CoreCorpus().getWorkList('handel/hwv56', '1-01', '.md')), 1)
-#         self.assertEqual(len(
-#                corpus.corpora.CoreCorpus().getWorkList('bach/artOfFugue_bwv1080')), 21)
-#         self.assertEqual(len(
-#                corpus.corpora.CoreCorpus().getWorkList('bach/artOfFugue_bwv1080', 1)), 1)
-#
-#         # there are two versions of this file
-#         self.assertEqual(len(corpus.getWorkList('beethoven/opus18no1', 1)), 2)
-#
-#         # if specify movement
-#         for bwv in [
-#             'bwv846', 'bwv847', 'bwv848', 'bwv849', 'bwv850', 'bwv851',
-#             'bwv852', 'bwv853', 'bwv854', 'bwv855', 'bwv856', 'bwv857',
-#             'bwv858', 'bwv859', 'bwv860', 'bwv861', 'bwv862', 'bwv863',
-#             'bwv864', 'bwv865', 'bwv866', 'bwv867', 'bwv868', 'bwv869',
-#             'bwv870', 'bwv871', 'bwv872', 'bwv873', 'bwv874', 'bwv875',
-#             'bwv876', 'bwv877', 'bwv878', 'bwv879', 'bwv880', 'bwv881',
-#             'bwv882', 'bwv883', 'bwv884', 'bwv885', 'bwv886', 'bwv887',
-#             'bwv888', 'bwv889', 'bwv890', 'bwv891', 'bwv892', 'bwv893',
-#             ]:
-#             # print(bwv)
-#             self.assertEqual(len(corpus.corpora.CoreCorpus().getWorkList(bwv)), 2)
-#             self.assertEqual(len(corpus.corpora.CoreCorpus().getWorkList(bwv, 1)), 1)
-#             self.assertEqual(len(corpus.corpora.CoreCorpus().getWorkList(bwv, 2)), 1)
-
-#     def testWTCImport01(self):
-#         score = corpus.parse('bach/bwv846', 1)
-#         self.assertEqual(
-#             score.metadata.title,
-#             'WTC I: Prelude and Fugue in C major',
-#             )
-#         self.assertEqual(score.metadata.movementNumber, '1')
-
-#     def testWTCImport02(self):
-#         score = corpus.parse('bach/bwv846', 2)
-#         self.assertEqual(score.metadata.movementName, 'Fugue  I. ')
-#         self.assertEqual(score.metadata.movementNumber, '2')
-
-#     def testWTCImport03(self):
-#         score = corpus.parse('bach/bwv862', 1)
-#         self.assertEqual(
-#             score.metadata.title,
-#             'WTC I: Prelude and Fugue in A flat major',
-#             )
-
-#     def testWTCImport04(self):
-#         score = corpus.parse('bach/bwv888', 1)
-#         self.assertEqual(
-#             score.metadata.title,
-#             'WTC II: Prelude and Fugue in A major',
-#             )
-#         # s.show()
-
-#     def testWorkReferences(self):
-#         s = corpus.getWorkReferences()
-#
-#         # presently 19 top level lists
-#         self.assertGreaterEqual(len(s), 19)
-#         self.assertEqual(len(s[0].keys()), 4)
+    # def testSearch12(self):
+    #     # searching virtual entries
+    #     searchResults = corpus.search('coltrane', field='composer')
+    #     self.assertGreater(len(searchResults), 0)
+    #     # returns items in pairs: url and work number
+    #     self.assertEqual(searchResults[0].sourcePath,
+    #         'http://impromastering.com/uploads/transcription_file/' +
+    #         'file/196/Giant_Steps__John_Coltrane_C.xml')
+
+    # def testGetWorkList(self):
+    #     self.assertGreaterEqual(len(corpus.corpora.CoreCorpus().getPaths('.md')), 38)
+    #     workList = corpus.corpora.CoreCorpus().getWorkList('bach/artOfFugue_bwv1080', 1, '.zip')
+    #     self.assertEqual(len(workList), 1)
+    #     self.assertEqual(len(
+    #            corpus.corpora.CoreCorpus().getWorkList('handel/hwv56', (1, 1), '.md')), 1)
+    #     self.assertEqual(len(
+    #            corpus.corpora.CoreCorpus().getWorkList('handel/hwv56', '1-01', '.md')), 1)
+    #     self.assertEqual(len(
+    #            corpus.corpora.CoreCorpus().getWorkList('bach/artOfFugue_bwv1080')), 21)
+    #     self.assertEqual(len(
+    #            corpus.corpora.CoreCorpus().getWorkList('bach/artOfFugue_bwv1080', 1)), 1)
+    #
+    #     # there are two versions of this file
+    #     self.assertEqual(len(corpus.getWorkList('beethoven/opus18no1', 1)), 2)
+    #
+    #     # if specify movement
+    #     for bwv in [
+    #         'bwv846', 'bwv847', 'bwv848', 'bwv849', 'bwv850', 'bwv851',
+    #         'bwv852', 'bwv853', 'bwv854', 'bwv855', 'bwv856', 'bwv857',
+    #         'bwv858', 'bwv859', 'bwv860', 'bwv861', 'bwv862', 'bwv863',
+    #         'bwv864', 'bwv865', 'bwv866', 'bwv867', 'bwv868', 'bwv869',
+    #         'bwv870', 'bwv871', 'bwv872', 'bwv873', 'bwv874', 'bwv875',
+    #         'bwv876', 'bwv877', 'bwv878', 'bwv879', 'bwv880', 'bwv881',
+    #         'bwv882', 'bwv883', 'bwv884', 'bwv885', 'bwv886', 'bwv887',
+    #         'bwv888', 'bwv889', 'bwv890', 'bwv891', 'bwv892', 'bwv893',
+    #         ]:
+    #         # print(bwv)
+    #         self.assertEqual(len(corpus.corpora.CoreCorpus().getWorkList(bwv)), 2)
+    #         self.assertEqual(len(corpus.corpora.CoreCorpus().getWorkList(bwv, 1)), 1)
+    #         self.assertEqual(len(corpus.corpora.CoreCorpus().getWorkList(bwv, 2)), 1)
+
+    # def testWTCImport01(self):
+    #     score = corpus.parse('bach/bwv846', 1)
+    #     self.assertEqual(
+    #         score.metadata.title,
+    #         'WTC I: Prelude and Fugue in C major',
+    #         )
+    #     self.assertEqual(score.metadata.movementNumber, '1')
+
+    # def testWTCImport02(self):
+    #     score = corpus.parse('bach/bwv846', 2)
+    #     self.assertEqual(score.metadata.movementName, 'Fugue  I. ')
+    #     self.assertEqual(score.metadata.movementNumber, '2')
+
+    # def testWTCImport03(self):
+    #     score = corpus.parse('bach/bwv862', 1)
+    #     self.assertEqual(
+    #         score.metadata.title,
+    #         'WTC I: Prelude and Fugue in A flat major',
+    #         )
+
+    # def testWTCImport04(self):
+    #     score = corpus.parse('bach/bwv888', 1)
+    #     self.assertEqual(
+    #         score.metadata.title,
+    #         'WTC II: Prelude and Fugue in A major',
+    #         )
+    #     # s.show()
+
+    # def testWorkReferences(self):
+    #     s = corpus.getWorkReferences()
+    #
+    #     # presently 19 top level lists
+    #     self.assertGreaterEqual(len(s), 19)
+    #     self.assertEqual(len(s[0].keys()), 4)
 
 
 if __name__ == '__main__':
diff --git a/music21/musicxml/test_xmlToM21.py b/music21/musicxml/test_xmlToM21.py
index c763b5b09..2933ed939 100644
--- a/music21/musicxml/test_xmlToM21.py
+++ b/music21/musicxml/test_xmlToM21.py
@@ -1256,13 +1256,13 @@ class Test(unittest.TestCase):
         # Measure 3, right barline: <ending number="3" type="stop"/>
         score = converter.parse(testPrimitive.multiDigitEnding)
         repeatBrackets = score.recurse().getElementsByClass(spanner.RepeatBracket)
-        self.assertListEqual(repeatBrackets[0].getNumberList(), [1, 2])
-        self.assertListEqual(repeatBrackets[1].getNumberList(), [3])
+        self.assertListEqual(repeatBrackets[0].numberRange, [1, 2])
+        self.assertListEqual(repeatBrackets[1].numberRange, [3])
 
         nonconformingInput = testPrimitive.multiDigitEnding.replace('1,2', 'ad lib.')
         score2 = converter.parse(nonconformingInput)
         repeatBracket = score2.recurse().getElementsByClass(spanner.RepeatBracket).first()
-        self.assertListEqual(repeatBracket.getNumberList(), [1])
+        self.assertListEqual(repeatBracket.numberRange, [1])
 
     def testChordAlteration(self):
         from music21 import musicxml
diff --git a/music21/stream/tests.py b/music21/stream/tests.py
index 4b675efb0..4703a66c2 100644
--- a/music21/stream/tests.py
+++ b/music21/stream/tests.py
@@ -1933,19 +1933,17 @@ class Test(unittest.TestCase):
         sInnerFlat = sInner.flatten()
         sInnerFlat.id = 'sInnerFlat'
 
-#         # but it has sOuter has a context
-#         self.assertIn(sOuter, sInnerFlat.sites)
-#         # environLocal.printDebug(['sites.get() of sInnerFlat', sInnerFlat.sites.get()])
-#         # environLocal.printDebug(['sites.siteDict of sInnerFlat', sInnerFlat.sites.siteDict])
-#
-#
-#         self.assertIn(sOuter, sInnerFlat.sites)
-#
-#         # this returns the proper dictionary entry
-#         # environLocal.printDebug(
-#         #    ['sInnerFlat.sites.siteDict[id(sInner)', sInnerFlat.sites.siteDict[id(sOuter)]])
-#         # we can extract out the same reference
-#         unused_sOuterOut = sInnerFlat.sites.getById(id(sOuter))
+        # # but it has sOuter has a context
+        # self.assertIn(sOuter, sInnerFlat.sites)
+        # # environLocal.printDebug(['sites.get() of sInnerFlat', sInnerFlat.sites.get()])
+        # # environLocal.printDebug(['sites.siteDict of sInnerFlat', sInnerFlat.sites.siteDict])
+        # self.assertIn(sOuter, sInnerFlat.sites)
+
+        # # this returns the proper dictionary entry
+        # # environLocal.printDebug(
+        # #    ['sInnerFlat.sites.siteDict[id(sInner)', sInnerFlat.sites.siteDict[id(sOuter)]])
+        # # we can extract out the same reference
+        # unused_sOuterOut = sInnerFlat.sites.getById(id(sOuter))
 
         # this works
         post = sInnerFlat.getContextByClass(clef.Clef)
@@ -3359,13 +3357,13 @@ class Test(unittest.TestCase):
         self.assertEqual(m.barDurationProportion(), Fraction(1, 3), 4)
         self.assertEqual(m.barDuration.quarterLength, 3, 4)
 
-# temporarily commented out
-#         m.shiftElementsAsAnacrusis()
-#         self.assertTrue(m in m.notesAndRests[0].sites)
-#         self.assertEqual(m.notesAndRests[0].offset, 2.0)
-#         # now the duration is full
-#         self.assertAlmostEqual(m.barDurationProportion(), 1.0, 4)
-#         self.assertAlmostEqual(m.highestOffset, 2.0, 4)
+        # temporarily commented out
+        # m.shiftElementsAsAnacrusis()
+        # self.assertTrue(m in m.notesAndRests[0].sites)
+        # self.assertEqual(m.notesAndRests[0].offset, 2.0)
+        # # now the duration is full
+        # self.assertAlmostEqual(m.barDurationProportion(), 1.0, 4)
+        # self.assertAlmostEqual(m.highestOffset, 2.0, 4)
 
         m = Measure()
         m.timeSignature = meter.TimeSignature('5/4')
@@ -3379,11 +3377,11 @@ class Test(unittest.TestCase):
         self.assertEqual(m.barDurationProportion(), Fraction(2, 5), 4)
         self.assertEqual(m.barDuration.quarterLength, 5.0)
 
-#         m.shiftElementsAsAnacrusis()
-#         self.assertEqual(m.notesAndRests[0].offset, 3.0)
-#         self.assertEqual(n1.offset, 3.0)
-#         self.assertEqual(n2.offset, 3.5)
-#         self.assertAlmostEqual(m.barDurationProportion(), 1.0, 4)
+        # m.shiftElementsAsAnacrusis()
+        # self.assertEqual(m.notesAndRests[0].offset, 3.0)
+        # self.assertEqual(n1.offset, 3.0)
+        # self.assertEqual(n2.offset, 3.5)
+        # self.assertAlmostEqual(m.barDurationProportion(), 1.0, 4)
 
     def testInsertAndShiftBasic(self):
         offsets = [0, 2, 4, 6, 8, 10, 12]
@@ -5907,52 +5905,49 @@ class Test(unittest.TestCase):
 
         # s1.show()
 
-#         s0 = corpus.parse('hwv56', '1-05')
-#         # can use index values
-#         s2 = s0.partsToVoices(([0, 1], [2, 4], 3), permitOneVoicePerPart=True)
-#         self.assertEqual(len(s2.parts), 3)
-#         self.assertEqual(len(s2.parts[0].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(len(s2.parts[1].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(len(s2.parts[2].getElementsByClass(
-#             'Measure')[0].voices), 1)
-#
-#         s2 = s0.partsToVoices((['Violino I', 'Violino II'], ['Viola', 'Bassi'], ['Basso']),
-#                permitOneVoicePerPart=True)
-#         self.assertEqual(len(s2.parts), 3)
-#         self.assertEqual(len(s2.parts[0].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(len(s2.parts[1].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(len(s2.parts[2].getElementsByClass(
-#             'Measure')[0].voices), 1)
-#
-#
-#         # this will keep the voice part unaltered
-#         s2 = s0.partsToVoices((['Violino I', 'Violino II'], ['Viola', 'Bassi'], 'Basso'),
-#                permitOneVoicePerPart=False)
-#         self.assertEqual(len(s2.parts), 3)
-#         self.assertEqual(len(s2.parts[0].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(len(s2.parts[1].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(s2.parts[2].getElementsByClass(
-#             'Measure')[0].hasVoices(), False)
-#
-#
-#         # mm 16-19 are a good examples
-#         s1 = corpus.parse('hwv56', '1-05').measures(16, 19)
-#         s2 = s1.partsToVoices((['Violino I', 'Violino II'], ['Viola', 'Bassi'], 'Basso'))
-#         # s.show()
-#
-#         self.assertEqual(len(s2.parts), 3)
-#         self.assertEqual(len(s2.parts[0].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(len(s2.parts[1].getElementsByClass(
-#             'Measure')[0].voices), 2)
-#         self.assertEqual(s2.parts[2].getElementsByClass(
-#             'Measure')[0].hasVoices(), False)
+        # s0 = corpus.parse('hwv56', '1-05')
+        # # can use index values
+        # s2 = s0.partsToVoices(([0, 1], [2, 4], 3), permitOneVoicePerPart=True)
+        # self.assertEqual(len(s2.parts), 3)
+        # self.assertEqual(len(s2.parts[0].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(len(s2.parts[1].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(len(s2.parts[2].getElementsByClass(
+        #     'Measure')[0].voices), 1)
+
+        # s2 = s0.partsToVoices((['Violino I', 'Violino II'], ['Viola', 'Bassi'], ['Basso']),
+        #        permitOneVoicePerPart=True)
+        # self.assertEqual(len(s2.parts), 3)
+        # self.assertEqual(len(s2.parts[0].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(len(s2.parts[1].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(len(s2.parts[2].getElementsByClass(
+        #     'Measure')[0].voices), 1)
+        # # this will keep the voice part unaltered
+        # s2 = s0.partsToVoices((['Violino I', 'Violino II'], ['Viola', 'Bassi'], 'Basso'),
+        #        permitOneVoicePerPart=False)
+        # self.assertEqual(len(s2.parts), 3)
+        # self.assertEqual(len(s2.parts[0].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(len(s2.parts[1].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(s2.parts[2].getElementsByClass(
+        #     'Measure')[0].hasVoices(), False)
+
+        # # mm 16-19 are a good examples
+        # s1 = corpus.parse('hwv56', '1-05').measures(16, 19)
+        # s2 = s1.partsToVoices((['Violino I', 'Violino II'], ['Viola', 'Bassi'], 'Basso'))
+        # # s.show()
+
+        # self.assertEqual(len(s2.parts), 3)
+        # self.assertEqual(len(s2.parts[0].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(len(s2.parts[1].getElementsByClass(
+        #     'Measure')[0].voices), 2)
+        # self.assertEqual(s2.parts[2].getElementsByClass(
+        #     'Measure')[0].hasVoices(), False)
 
     def testVoicesToPartsA(self):
 
@@ -7013,11 +7008,11 @@ class Test(unittest.TestCase):
         self.assertEqual([m.seconds for m in s.getElementsByClass(Measure)], [6.0, 10.0, 4.0])
 
     # TODO: New piece with Metronome Mark Boundaries
-#     def testMetronomeMarkBoundaries(self):
-# #         s = corpus.parse('hwv56/movement2-09.md')
-#         mmBoundaries = s.metronomeMarkBoundaries()
-#         self.assertEqual(str(mmBoundaries),
-#                '[(0.0, 20.0, <music21.tempo.MetronomeMark Largo e piano Quarter=46>)]')
+    # def testMetronomeMarkBoundaries(self):
+    #     s = corpus.parse('hwv56/movement2-09.md')
+    #     mmBoundaries = s.metronomeMarkBoundaries()
+    #     self.assertEqual(str(mmBoundaries),
+    #            '[(0.0, 20.0, <music21.tempo.MetronomeMark Largo e piano Quarter=46>)]')
 
     def testAccumulatedTimeA(self):
         s = Stream()
diff --git a/music21/test/commonTest.py b/music21/test/commonTest.py
index 944467114..e8f2fb663 100644
--- a/music21/test/commonTest.py
+++ b/music21/test/commonTest.py
@@ -76,12 +76,12 @@ def load_source(name: str, path: str) -> types.ModuleType:
         raise FileNotFoundError(f'No such file or directory: {path!r}')
     if name in sys.modules:
         module = sys.modules[name]
-        spec.loader.exec_module(module)
     else:
         module = importlib.util.module_from_spec(spec)
         if module is None:
             raise FileNotFoundError(f'No such file or directory: {path!r}')
-        spec.loader.exec_module(module)
+        sys.modules[name] = module
+    spec.loader.exec_module(module)
 
     return module
 
@@ -424,7 +424,7 @@ class ModuleGather:
             with warnings.catch_warnings():
                 mod = load_source(name, fp)
         except Exception as excp:  # pylint: disable=broad-except
-            environLocal.warn(['failed import:', fp, '\n',
+            environLocal.warn(['failed import:', name, '\t', fp, '\n',
                                '\tEXCEPTION:', str(excp).strip()])
             return None
 
diff --git a/music21/test/test_repeat.py b/music21/test/test_repeat.py
index 2bb657e12..65b3e7c5d 100644
--- a/music21/test/test_repeat.py
+++ b/music21/test/test_repeat.py
@@ -1117,7 +1117,7 @@ class Test(unittest.TestCase):
         bars = s2.parts[0].flatten().getElementsByClass(bar.Repeat)
         self.assertEqual(len(bars), 0)
 
-#         self.assertEqual(len(s2.parts[0].flatten().notes), 111)
+        # self.assertEqual(len(s2.parts[0].flatten().notes), 111)
 
     def testExpandRepeatsImportedC(self):
         s = converter.parse(testPrimitive.repeatExpressionsA)
@@ -1600,10 +1600,10 @@ class Test(unittest.TestCase):
 
         # p.show()
 
-        #         ex = repeat.Expander(p)
-        #         self.assertTrue(ex.isExpandable())
-        #         post = ex.process()
-        #         post.show()
+        # ex = repeat.Expander(p)
+        # self.assertTrue(ex.isExpandable())
+        # post = ex.process()
+        # post.show()
 
         post = p.expandRepeats()
         self.assertEqual(len(post), 37)