Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

use more v3.10 features. TypeGuards #1447

Merged
merged 6 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions music21/abcFormat/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,11 +799,10 @@ def testLyrics(self):
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')

Expand Down
34 changes: 17 additions & 17 deletions music21/articulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,23 +668,23 @@ def testBasic(self):
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])))


# ------------------------------------------------------------------------------
Expand Down
8 changes: 2 additions & 6 deletions music21/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
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
Expand All @@ -57,7 +56,7 @@
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
Expand All @@ -68,6 +67,7 @@
from music21 import tie

if TYPE_CHECKING:
import fractions
from music21 import meter
from music21 import stream
from music21 import spanner
Expand Down Expand Up @@ -685,8 +685,6 @@ def editorial(self) -> Editorial:

@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
Expand Down Expand Up @@ -743,8 +741,6 @@ def style(self) -> Style:

@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.
Expand Down
10 changes: 6 additions & 4 deletions music21/braille/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,12 +22,14 @@
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
Expand Down Expand Up @@ -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:
Expand Down
16 changes: 8 additions & 8 deletions music21/braille/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2763,8 +2763,8 @@ def xtest_example13_11(self):
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()
Expand Down Expand Up @@ -2895,8 +2895,8 @@ def xtest_example13_19(self):
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('''
Expand Down Expand Up @@ -3287,8 +3287,8 @@ def xtest_example15_10(self):
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('''
Expand Down Expand Up @@ -3370,8 +3370,8 @@ def xtest_example16_6(self):
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('''
Expand Down
2 changes: 1 addition & 1 deletion music21/clef.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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


Expand Down
97 changes: 88 additions & 9 deletions music21/common/classTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion music21/common/numberTools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading