Skip to content

Commit

Permalink
Merge pull request #1277 from cuthbertLab/v8-rem3-7
Browse files Browse the repository at this point in the history
Remove support for Py3.7; add some 3.8+ only feat.
  • Loading branch information
mscuthbert authored Apr 19, 2022
2 parents cc34154 + cee811e commit 9169917
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 80 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/maincheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, "3.10"]
python-version: [3.8, 3.9, "3.10"]
steps:
- uses: actions/setup-python@v2
with:
Expand All @@ -27,7 +27,7 @@ jobs:
- name: Run Main Test script
run: python -c 'from music21.test.testSingleCoreAll import ciMain as ci; ci()'
- name: Coveralls
if: ${{ matrix.python-version == '3.7' }}
if: ${{ matrix.python-version == '3.8' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_SERVICE_NAME: github
Expand Down
9 changes: 5 additions & 4 deletions music21/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@
'''
import sys

minPythonVersion = (3, 7)
minPythonVersion = (3, 8)
minPythonVersionStr = '.'.join([str(x) for x in minPythonVersion])
if sys.version_info < minPythonVersion:
# DO NOT CHANGE THIS TO AN f-String -- it needs to run on old python.
raise ImportError('''
Music21 v.7.0+ is a Python {}+ only library.
Music21 v.8.0+ is a Python {}+ only library.
Use music21 v1 to run on Python 2.1-2.6.
Use music21 v4 to run on Python 2.7.
Use music21 v5.1 to run on Python 3.4.
Use music21 v5.7 to run on Python 3.5.
Use music21 v6.7 to run on Python 3.6.
Use music21 v7.3 to run on Python 3.7
If you have the wrong version there are several options for getting
the right one.
Expand Down Expand Up @@ -173,7 +174,7 @@
from music21 import prebase # noqa: E402
from music21 import sites # noqa: E402

# should this simply be from music21.base import * since __all__ is well defined?
# should this simply be from music21.base import * since __all__ is well-defined?
from music21.base import Music21Exception # noqa: E402
from music21.base import SitesException # noqa: E402
from music21.base import Music21ObjectException # noqa: E402
Expand All @@ -192,7 +193,7 @@
from music21.test.testRunner import mainTest # noqa: E402

# -----------------------------------------------------------------------------
# now import all modules so they are accessible from "import music21"
# now import all modules to make them accessible from "import music21"
from music21 import abcFormat # noqa: E402
from music21 import alpha # noqa: E402
from music21 import analysis # noqa: E402
Expand Down
10 changes: 5 additions & 5 deletions music21/audioSearch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def histogram(data, bins):
# noinspection PyShadowingNames
'''
Partition the list in `data` into a number of bins defined by `bins`
and return the number of elements in each bins and a set of `bins` + 1
and return the number of elements in each bin and a set of `bins` + 1
elements where the first element (0) is the start of the first bin,
the last element (-1) is the end of the last bin, and every remaining element (i)
is the dividing point between one bin and another.
Expand Down Expand Up @@ -552,7 +552,7 @@ def smoothFrequencies(
220
Different levels of smoothing have different effects. At smoothLevel=2,
the isolated 220hz sample is pulling down the samples around it:
the isolated 220hz sample is pulling down the surrounding samples:
>>> audioSearch.smoothFrequencies(inputPitches, smoothLevels=2)[:5]
[330, 275, 358, 399, 420]
Expand Down Expand Up @@ -813,7 +813,7 @@ def notesAndDurationsToStream(
'''
take a list of :class:`~music21.note.Note` objects or rests
and an equally long list of how long
each ones lasts in terms of samples and returns a
each one lasts in terms of samples and returns a
Stream using the information from quarterLengthEstimation
and quantizeDurations.
Expand Down Expand Up @@ -969,11 +969,11 @@ def decisionProcess(
countdown = countdown + 1
elif dist > 20 and countdown == 0:
countdown += 1
environLocal.printDebug(f'Excessive distance....? dist={dist}') # 3.8 replace {dist=}
environLocal.printDebug(f'Excessive distance....? {dist=}')

elif dist > 30 and countdown == 1:
countdown += 1
environLocal.printDebug(f'Excessive distance....? dist={dist}') # 3.8 replace {dist=}
environLocal.printDebug(f'Excessive distance....? {dist=}')

elif ((firstNotePage is not None and lastNotePage is not None)
and ((positionBeginningData < firstNotePage
Expand Down
43 changes: 20 additions & 23 deletions music21/braille/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
import copy
import enum
import unittest

from typing import Optional, Union
from typing import Optional, Union, TypedDict

from music21 import bar
from music21 import chord
Expand Down Expand Up @@ -110,15 +109,12 @@ class Affinity(enum.IntEnum):
layout.PageLayout,
layout.StaffLayout]

# Uncomment when Python 3.8 is the minimum version
# from typing import TypedDict, Optional
# class GroupingGlobals(TypedDict):
# keySignature: Optional[key.KeySignature]
# timeSignature: Optional[meter.TimeSignature]
# GROUPING_GLOBALS: GroupingGlobals = {...}
class GroupingGlobals(TypedDict):
keySignature: Optional[key.KeySignature]
timeSignature: Optional[meter.TimeSignature]


GROUPING_GLOBALS = {
GROUPING_GLOBALS: GroupingGlobals = {
'keySignature': None, # will be key.KeySignature(0) on first call
'timeSignature': None, # will be meter.TimeSignature('4/4') on first call
}
Expand All @@ -130,12 +126,8 @@ def setGroupingGlobals():
in Braille is run, but saves creating two expensive objects if never run
'''
if GROUPING_GLOBALS['keySignature'] is None:
# remove noinspection when Python 3.8 is the minimum
# noinspection PyTypeChecker
GROUPING_GLOBALS['keySignature'] = key.KeySignature(0)
if GROUPING_GLOBALS['timeSignature'] is None:
# remove noinspection when Python 3.8 is the minimum
# noinspection PyTypeChecker
GROUPING_GLOBALS['timeSignature'] = meter.TimeSignature('4/4')


Expand Down Expand Up @@ -186,7 +178,7 @@ def __init__(self, *args):
<music21.note.Rest quarter>
<music21.note.Note F>
These are the defaults and they are shared across all objects...
These are the defaults, and they are shared across all objects...
>>> bg.keySignature
<music21.key.KeySignature of no sharps or flats>
Expand Down Expand Up @@ -236,7 +228,7 @@ def __getattr__(self, attr):

def __str__(self):
'''
Return an unicode braille representation
Return a unicode braille representation
of each object in the BrailleElementGrouping.
'''
allObjects = []
Expand Down Expand Up @@ -300,7 +292,7 @@ class BrailleSegment(text.BrailleText):
def __init__(self, lineLength: int = 40):
'''
A segment is "a group of measures occupying more than one braille line."
Music is divided into segments so as to "present the music to the reader
Music is divided into segments in order to "present the music to the reader
in a meaningful manner and to give him convenient reference points to
use in memorization" (BMTM, 71).
Expand Down Expand Up @@ -441,14 +433,14 @@ def __str__(self):

def transcribe(self):
'''
transcribes all of the noteGroupings in this dict by:
Transcribes all noteGroupings in this dict by:
first transcribing the Heading (if applicable)
then the Measure Number
then adds appropriate numbers of dummyRests
then adds the Rest of the Note Groupings
* First transcribes the Heading (if applicable)
* then the Measure Number
* then adds appropriate numbers of dummyRests
* then adds the Rest of the Note Groupings
returns brailleText
Returns brailleText
'''
# noinspection PyAttributeOutsideInit
self.groupingKeysToProcess = list(sorted(self.keys()))
Expand Down Expand Up @@ -922,6 +914,7 @@ def extractTempoTextGrouping(self):
self.extractMeasureNumber()

def consolidate(self):
# noinspection PyShadowingNames
'''
Puts together certain types of elements according to the last digit of their key
(if it is the same as Affinity.NOTEGROUP or not.
Expand Down Expand Up @@ -1126,6 +1119,7 @@ def __str__(self):
return out

def yieldCombinedGroupingKeys(self):
# noinspection PyShadowingNames
'''
yields all the keys in order as a tuple of (rightKey, leftKey) where
two keys are grouped if they have the same segmentKey except for the hand.
Expand Down Expand Up @@ -1174,7 +1168,8 @@ def matchOther(thisKey_inner, otherKey):
yield(thisKey, storedLeft)
elif (thisKey.affinity == Affinity.NOTEGROUP
and matchOther(thisKey._replace(affinity=Affinity.INACCORD), storedLeft)):
# r.h. notegroup goes before an lh inaccord, despite this being out of order
# r.h. notegroup goes before an l.h. inaccord,
# despite this being out of order
yield(thisKey, storedLeft)
else:
yield(None, storedLeft)
Expand Down Expand Up @@ -1400,6 +1395,7 @@ def findSegments(music21Part,
suppressOctaveMarks=False,
upperFirstInNoteFingering=True,
):
# noinspection PyShadowingNames
'''
Takes in a :class:`~music21.stream.Part`.
Expand Down Expand Up @@ -1745,6 +1741,7 @@ def getRawSegments(music21Part,
setHand=None,
maxLineLength: int = 40,
):
# noinspection PyShadowingNames
'''
Takes in a :class:`~music21.stream.Part`, divides it up into segments (i.e. instances of
:class:`~music21.braille.segment.BrailleSegment`). This function assumes
Expand Down
27 changes: 14 additions & 13 deletions music21/duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
import io
import unittest
from math import inf, isnan
from typing import Union, Tuple, Dict, List, Optional, Iterable
from typing import Union, Tuple, Dict, List, Optional, Iterable, Literal

from collections import namedtuple

Expand Down Expand Up @@ -268,7 +268,7 @@ def quarterLengthToClosestType(qLen: OffsetQLIn):
>>> duration.quarterLengthToClosestType(1.8)
('quarter', False)
Some very very close types will return True for exact conversion...
Some extremely close types will return True for exact conversion...
>>> duration.quarterLengthToClosestType(2.0000000000000001)
('half', True)
Expand Down Expand Up @@ -335,8 +335,9 @@ def convertQuarterLengthToType(qLen: OffsetQLIn) -> str:
f'cannot convert quarterLength {qLen} exactly to type')
return durationType

# TODO: in Py3.8+ Union[Tuple[int, str], Tuple[Literal[False], Literal[False]]]
def dottedMatch(qLen: OffsetQLIn, maxDots=4) -> Union[Tuple[int, str], Tuple[bool, bool]]:
def dottedMatch(qLen: OffsetQLIn,
maxDots=4
) -> Union[Tuple[int, str], Tuple[Literal[False], Literal[False]]]:
'''
Given a quarterLength, determine if there is a dotted
(or non-dotted) type that exactly matches. Returns a pair of
Expand Down Expand Up @@ -385,7 +386,7 @@ def quarterLengthToNonPowerOf2Tuplet(
power of 2 denominator (such as 7:6) that represents the quarterLength and the
DurationTuple that should be used to express the note.
This could be a double dotted note, but also a tuplet...
This could be a double-dotted note, but also a tuplet...
>>> duration.quarterLengthToNonPowerOf2Tuplet(7)
(<music21.duration.Tuplet 8/7/quarter>, DurationTuple(type='breve', dots=0, quarterLength=8.0))
Expand All @@ -410,7 +411,7 @@ def quarterLengthToNonPowerOf2Tuplet(
elif qFrac.numerator > qFrac.denominator * 2:
while qFrac.numerator > qFrac.denominator * 2:
qFrac = qFrac / 2
# qFrac will always be in lowest terms
# qFrac will always be expressed in lowest terms

closestSmallerType, unused_match = quarterLengthToClosestType(qLen / qFrac.denominator)

Expand Down Expand Up @@ -452,7 +453,7 @@ def quarterLengthToTuplet(
<music21.duration.Tuplet 5/1/quarter>]
By specifying only 1 `maxToReturn`, the a single-length list containing the
By specifying only 1 `maxToReturn`, a single-length list containing the
Tuplet with the smallest type will be returned.
>>> duration.quarterLengthToTuplet(0.3333333, 1)
Expand Down Expand Up @@ -504,7 +505,7 @@ def quarterConversion(qLen: OffsetQLIn) -> QuarterLengthConversion:
Components is a tuple of DurationTuples (normally one) that
add up to the qLen when multiplied by...
Tuplet is a single :class:`~music21.duration.Tuplet` that adjusts all of the components.
Tuplet is a single :class:`~music21.duration.Tuplet` that adjusts all components.
(All quarterLengths can, technically, be notated as a single unit
given a complex enough tuplet, as a last resort will look up to 199 as a tuplet type).
Expand Down Expand Up @@ -587,7 +588,7 @@ def quarterConversion(qLen: OffsetQLIn) -> QuarterLengthConversion:
tuplet=<music21.duration.Tuplet 7/4/16th>)
A 4/7ths of a whole note, or
A quarter that is 4/7th of of a quarter
A quarter that is 4/7th of a quarter
>>> duration.quarterConversion(4/7)
QuarterLengthConversion(components=(DurationTuple(type='quarter', dots=0, quarterLength=1.0),),
Expand Down Expand Up @@ -1408,7 +1409,7 @@ def durationActual(self, dA: Union[DurationTuple, 'Duration']):
@property
def durationNormal(self) -> DurationTuple:
'''
durationNormal is a DurationTuple that represents the notes that are
durationNormal is a DurationTuple that represents the notes that
would be present in the space normally (if there were no tuplets). For instance, in a 7
dotted-eighth in the place of 2 double-dotted quarter notes tuplet,
the durationNormal would be...
Expand Down Expand Up @@ -2435,7 +2436,7 @@ def _updateQuarterLength(self):
return

if self._dotGroups == (0,) and not self.tuplets and len(self.components) == 1:
# do common tasks fast:
# make sure to do common tasks fast:
self._qtrLength = self.components[0].quarterLength
else:
self._qtrLength = opFrac(self.quarterLengthNoTuplets * self.aggregateTupletMultiplier())
Expand Down Expand Up @@ -2943,7 +2944,7 @@ def aggregateTupletMultiplier(self) -> OffsetQL:
Fraction(8, 15)
'''
if not self.tuplets:
# do common cases fast.
# common cases should be fast.
return 1.0

currentMultiplier = 1.0
Expand Down Expand Up @@ -3072,7 +3073,7 @@ def __init__(self, *arguments, **keywords):
self.components = newComponents # set new components

# make time is encoded in musicxml as divisions; here it can
# by a duration; but should it be the duration suggested by the grace?
# be encoded as a duration; but should it be the duration suggested by the grace?
self._makeTime = False
self._slash = None
self.slash = True # can be True, False, or None; make None go to True?
Expand Down
11 changes: 3 additions & 8 deletions music21/roman.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@
import unittest
import copy
import re
from typing import Dict, Union, Optional, List, Tuple

# when python 3.7 is removed from support:
# from typing import Literal
from typing import Dict, Union, Optional, List, Tuple, Literal

from collections import namedtuple

Expand Down Expand Up @@ -534,7 +531,7 @@ def figureTupleSolo(
def identifyAsTonicOrDominant(
inChord: Union[list, tuple, chord.Chord],
inKey: key.Key
) -> Union[str, bool]:
) -> Union[str, Literal[False]]:
'''
Returns the roman numeral string expression (either tonic or dominant) that
best matches the inChord. Useful when you know inChord is either tonic or
Expand Down Expand Up @@ -2693,9 +2690,7 @@ def _parseRNAloneAmidstAug6(self, workingFigure, useScale):
secondary_tonic = self.secondaryRomanNumeralKey.tonic
self.secondaryRomanNumeralKey = key.Key(secondary_tonic, 'minor')

# when Python 3.7 support is removed
# aug6type: Literal['It', 'Ger', 'Fr', 'Sw'] = aug6Match.group(1)
aug6type = aug6Match.group(1)
aug6type: Literal['It', 'Ger', 'Fr', 'Sw'] = aug6Match.group(1)

if aug6type in ('It', 'Ger'):
self.scaleDegree = 4
Expand Down
Loading

0 comments on commit 9169917

Please sign in to comment.