diff --git a/.pylintrc b/.pylintrc index b901d91df..fb797479d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -290,7 +290,7 @@ init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). -dummy-variables-rgx=_$|dummy|unused|i$|j$|junk|counter +dummy-variables-rgx=_|dummy|unused|i$|j$|junk|counter # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. diff --git a/documentation/docbuild/documenters.py b/documentation/docbuild/documenters.py index 4baf0400b..efd856e06 100644 --- a/documentation/docbuild/documenters.py +++ b/documentation/docbuild/documenters.py @@ -538,8 +538,11 @@ def _formatInheritedMembersMapping(self, mapping, banner): for classDocumenter in self.baseClassDocumenters: if classDocumenter not in mapping: continue - result.append(banner.format( - classDocumenter.rstCrossReferenceString)) + result.append( + banner.format( + classDocumenter.rstCrossReferenceString + ) + ) result.append('') memberDocumenters = mapping[classDocumenter] result.append('.. hlist::') @@ -757,10 +760,9 @@ def inheritedReadwritePropertiesMapping(self): >>> mapping = documenter.inheritedReadwritePropertiesMapping >>> sortBy = lambda x: x.referentPackageSystemPath >>> for classDocumenter in sorted(mapping, key=sortBy): - ... print('{0}:'.format(classDocumenter.referentPackageSystemPath)) + ... print(f'{classDocumenter.referentPackageSystemPath}:') ... for attributeDocumenter in mapping[classDocumenter][:10]: - ... print('- {0}'.format(attributeDocumenter.referentPackageSystemPath)) - ... + ... print(f'- {attributeDocumenter.referentPackageSystemPath}') music21.base.Music21Object: - music21.base.Music21Object.activeSite - music21.base.Music21Object.derivation @@ -940,13 +942,10 @@ def rstInheritedDocAttrFormat(self): result.append('.. hlist::') result.append(' :columns: 3') result.append('') - formatString = ' - :attr:`~{0}.{1}`' + basePath = baseDocumenter.referentPackageSystemPath for attrName in attrNames: result.append( - formatString.format( - baseDocumenter.referentPackageSystemPath, - attrName, - ) + f' - :attr:`~{basePath}.{attrName}`' ) result.append('') return result @@ -1594,14 +1593,15 @@ def getRstComposerWorksFormat(self, corpusWork): # def getRstVirtualWorkFileDictFormat(self, corpusFile): # result = [] - # result.append('- {0} *({1})*: `{2}`'.format( - # str(corpusFile.title), - # str(corpusFile.format), - # str(corpusFile.path), - # )) + # result.append( + # f'- {corpusFile.title} ' + # f'*({corpusFile.format})*: ' + # f'`{corpusFile.path}`' + # ) # result.append('') - # result.append(' Source: {0}'.format( - # str(corpusFile.url))) + # result.append( + # f' Source: {corpusFile.url}' + # ) # result.append('') # return result diff --git a/documentation/source/developerReference/developerGuidelines.ipynb b/documentation/source/developerReference/developerGuidelines.ipynb index 07fb94f9e..333245507 100644 --- a/documentation/source/developerReference/developerGuidelines.ipynb +++ b/documentation/source/developerReference/developerGuidelines.ipynb @@ -145,14 +145,17 @@ "Conventions:\n", "\n", " - **Strings MUST be 'single-quoted', but \"double quotes\" are allowed internally**\n", - " - This rule applies to triple quotes and doc strings also, contrary to PEP 257.\n", - " - Docstrings must begin and end on their own lines. No one-line doc-strings or text\n", + " - This rule applies to triple quotes and docstrings also, contrary to PEP 257.\n", + " - Docstrings must begin and end on their own lines. No one-line docstrings or text\n", " immediately following the triple quotes.\n", " - When there is a hyphen or single quote in the string, double quotes should be used,\n", " not escaping/backslashing.\n", " - For long streams of TinyNotation or Lilypond code, which both use single quotes to indicate octave,\n", " triple single quotes around the string are better than double quotes. Internal whitespace\n", " rarely matters in those formats.\n", + " - Before v10 concatenating strings without a plus sign was discouraged. I've changed my mind,\n", + " and am \"letting Python be Python\" now especially when three or more lines are involved.\n", + " However, use a + sign if concatenation is mixed with different comma-separated arguments.\n", " - Documentation should follow quoting in American English grammar when not\n", " discussing code. So for instance, a quotation in documentation is in double quotes.\n", " - Variable names:\n", @@ -163,7 +166,8 @@ " - Line lengths are capped at 100, but if approaching this limit, look for ways to avoid one-lining.\n", " - if it's easy to split your line into two which are both under 80 characters, do so.\n", " - Line continuation characters (`\\`) are not allowed; use open parentheses.\n", - " - Prefer f-strings to `.format()`. The `%` interpolation is no longer allowed.\n", + " - Greatly prefer f-strings to `.format()`. The `%` interpolation is no longer allowed.\n", + " - `.format()` is only to be used when a repeated format string is involved.\n", " - Annotating types is **required** in new code, and encouraged to be added to older code.\n", " - e.g. `self.counter: int = 0` or `def makeNoises() -> list['noise.Noise']:`\n", " - The typing library should always be imported as `t`.\n", @@ -193,7 +197,8 @@ " manipulation of the original object. When `inPlace` is True, nothing should be returned\n", " (not true for `music21j` since passing through objects is so expected in JavaScript thanks\n", " to JQuery and other libraries). Use the `@overload` decorator to show how this parameter\n", - " affects the return value -- Python makes this a bit hard, but see for instance, :meth:`~music21.stream.base.Stream.getElementsByClass` for an example of how to use this.\n", + " affects the return value -- Python makes this a bit hard, but see for\n", + " instance, :meth:`~music21.stream.base.Stream.getElementsByClass` for an example of how to use this.\n", " - Use descriptive pull request titles (rather than GitHub's default \"Update pitch.py\")\n", " - Do not have a PR title so long that it cannot be seen in one line. Simplify and\n", " rewrite and go into more detail in the description. I depend on skimming PR titles\n", diff --git a/music21/__init__.py b/music21/__init__.py index 351446b07..7cbd1faa5 100644 --- a/music21/__init__.py +++ b/music21/__init__.py @@ -167,6 +167,16 @@ 'voiceLeading', 'volpiano', 'volume', + + 'Music21Exception', + 'SitesException', + 'Music21ObjectException', + 'ElementException', + + 'Groups', + 'Music21Object', + 'ElementWrapper', + 'VERSION', ] # ------------------------------------------------------------------------------ diff --git a/music21/abcFormat/__init__.py b/music21/abcFormat/__init__.py index 8dd819b91..5772d98d2 100644 --- a/music21/abcFormat/__init__.py +++ b/music21/abcFormat/__init__.py @@ -2621,7 +2621,7 @@ def tokenProcess(self) -> None: else: environLocal.printDebug( ['broken rhythm marker ' - + f'({token.src}) not positioned between two notes or chords']) + f'({token.src}) not positioned between two notes or chords']) # need to update tuplets with currently active meter if isinstance(token, ABCTuplet): @@ -2698,7 +2698,7 @@ def tokenProcess(self) -> None: if lastDefaultQL is None: raise ABCHandlerException( 'no active default note length provided for note processing. ' - + f'tPrev: {tPrev}, token: {token}, tNext: {tNext}' + f'tPrev: {tPrev}, token: {token}, tNext: {tNext}' ) token.activeDefaultQuarterLength = lastDefaultQL token.activeKeySignature = lastKeySignature @@ -3671,21 +3671,22 @@ def testSplitByMeasure(self): ah.process(testFiles.hectorTheHero) ahm = ah.splitByMeasure() - for i, l, r in [(0, None, None), # metadata - (2, '|:', '|'), - (3, '|', '|'), - (-2, '[1', ':|'), - (-1, '[2', '|'), - ]: - if l is None: + for i, left, right in [ + (0, None, None), # metadata + (2, '|:', '|'), + (3, '|', '|'), + (-2, '[1', ':|'), + (-1, '[2', '|'), + ]: + if left is None: self.assertEqual(ahm[i].leftBarToken, None) else: - self.assertEqual(ahm[i].leftBarToken.src, l) + self.assertEqual(ahm[i].leftBarToken.src, left) - if r is None: + if right is None: self.assertEqual(ahm[i].rightBarToken, None) else: - self.assertEqual(ahm[i].rightBarToken.src, r) + self.assertEqual(ahm[i].rightBarToken.src, right) # for ahSub in ah.splitByMeasure(): # environLocal.printDebug(['split by measure:', ahSub.tokens]) @@ -3696,37 +3697,39 @@ def testSplitByMeasure(self): ah.process(testFiles.theBeggerBoy) ahm = ah.splitByMeasure() - for i, l, r in [(0, None, None), # metadata - (1, None, '|'), - (-1, '||', None), # trailing lyric metadata - ]: - if l is None: + for i, left, right in [ + (0, None, None), # metadata + (1, None, '|'), + (-1, '||', None), # trailing lyric metadata + ]: + if left is None: self.assertEqual(ahm[i].leftBarToken, None) else: - self.assertEqual(ahm[i].leftBarToken.src, l) + self.assertEqual(ahm[i].leftBarToken.src, left) - if r is None: + if right is None: self.assertEqual(ahm[i].rightBarToken, None) else: - self.assertEqual(ahm[i].rightBarToken.src, r) + self.assertEqual(ahm[i].rightBarToken.src, right) # test a simple string with no bars ah = ABCHandler() ah.process('M:6/8\nL:1/8\nK:G\nc1D2') ahm = ah.splitByMeasure() - for i, l, r in [(0, None, None), # metadata - (-1, None, None), # note data, but no bars - ]: - if l is None: + for i, left, right in [ + (0, None, None), # metadata + (-1, None, None), # note data, but no bars + ]: + if left is None: self.assertEqual(ahm[i].leftBarToken, None) else: - self.assertEqual(ahm[i].leftBarToken.src, l) + self.assertEqual(ahm[i].leftBarToken.src, left) - if r is None: + if right is None: self.assertEqual(ahm[i].rightBarToken, None) else: - self.assertEqual(ahm[i].rightBarToken.src, r) + self.assertEqual(ahm[i].rightBarToken.src, right) def testMergeLeadingMetaData(self): from music21.abcFormat import testFiles diff --git a/music21/analysis/correlate.py b/music21/analysis/correlate.py index f43702b2f..563d94c39 100644 --- a/music21/analysis/correlate.py +++ b/music21/analysis/correlate.py @@ -131,7 +131,7 @@ def pitchToDynamic(self, dataPoints=True): dstCheck = self.streamObj.recurse().getElementsByClass(objName) if not dstCheck: raise CorrelateException('cannot create correlation: an object ' - + f'that is not found in the Stream: {objName}') + f'that is not found in the Stream: {objName}') self._findActive(objNameSrc, objNameDst) diff --git a/music21/analysis/metrical.py b/music21/analysis/metrical.py index e51023c68..10aa5222b 100644 --- a/music21/analysis/metrical.py +++ b/music21/analysis/metrical.py @@ -43,7 +43,7 @@ def labelBeatDepth(streamIn): >>> sOut = [] >>> for n in s.flatten().notes: ... stars = "".join([l.text for l in n.lyrics]) - ... sOut.append("{0:8s} {1}".format(n.beatStr, stars)) + ... sOut.append(f'{n.beatStr:8s} {stars}') >>> print("\n".join(sOut)) 1 **** 1 1/2 * diff --git a/music21/analysis/pitchAnalysis.py b/music21/analysis/pitchAnalysis.py index 4adc9d451..c1352b6b6 100644 --- a/music21/analysis/pitchAnalysis.py +++ b/music21/analysis/pitchAnalysis.py @@ -21,7 +21,7 @@ def pitchAttributeCount(s, pitchAttr='name'): >>> bach = corpus.parse('bach/bwv324.xml') >>> pcCount = analysis.pitchAnalysis.pitchAttributeCount(bach, 'pitchClass') >>> for n in sorted(pcCount): - ... print("%2d: %2d" % (n, pcCount[n])) + ... print(f'{n:2d}: {pcCount[n]:2d}') 0: 3 2: 26 3: 3 @@ -36,7 +36,7 @@ def pitchAttributeCount(s, pitchAttr='name'): >>> nameCount = analysis.pitchAnalysis.pitchAttributeCount(bach, 'name') >>> for n, count in nameCount.most_common(3): - ... print("%2s: %2d" % (n, nameCount[n])) + ... print(f'{n:>2s}: {nameCount[n]:2d}') D: 26 A: 17 F#: 15 @@ -44,7 +44,7 @@ def pitchAttributeCount(s, pitchAttr='name'): >>> nameOctaveCount = analysis.pitchAnalysis.pitchAttributeCount(bach, 'nameWithOctave') >>> for n in sorted(nameOctaveCount): - ... print("%3s: %2d" % (n, nameOctaveCount[n])) + ... print(f'{n:>3s}: {nameOctaveCount[n]:2d}') A2: 2 A3: 5 A4: 10 diff --git a/music21/analysis/reduceChords.py b/music21/analysis/reduceChords.py index ec0d47794..6913bfc8f 100644 --- a/music21/analysis/reduceChords.py +++ b/music21/analysis/reduceChords.py @@ -320,7 +320,7 @@ def computeMeasureChordWeights( >>> cr = analysis.reduceChords.ChordReducer() >>> cws = cr.computeMeasureChordWeights(s) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 3.0 (0, 11, 4, 5) 1.0 @@ -329,7 +329,7 @@ def computeMeasureChordWeights( >>> cws = cr.computeMeasureChordWeights(s, ... weightAlgorithm=cr.quarterLengthBeatStrength) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 2.2 (0, 11, 4, 5) 0.5 @@ -338,7 +338,7 @@ def computeMeasureChordWeights( >>> cws = cr.computeMeasureChordWeights(s, ... weightAlgorithm=cr.quarterLengthBeatStrengthMeasurePosition) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 3.0 (0, 11, 4, 5) 0.5 @@ -347,7 +347,7 @@ def computeMeasureChordWeights( >>> cws = cr.computeMeasureChordWeights(s, ... weightAlgorithm=cr.qlbsmpConsonance) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 3.0 (0, 11, 4, 5) 0.1 ''' diff --git a/music21/analysis/reduceChordsOld.py b/music21/analysis/reduceChordsOld.py index 16e1d7e9e..2281f8c32 100644 --- a/music21/analysis/reduceChordsOld.py +++ b/music21/analysis/reduceChordsOld.py @@ -159,7 +159,7 @@ def computeMeasureChordWeights(self, measureObj, weightAlgorithm=None): >>> cr = analysis.reduceChordsOld.ChordReducer() >>> cws = cr.computeMeasureChordWeights(s) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 3.0 (0, 11, 4, 5) 1.0 @@ -167,7 +167,7 @@ def computeMeasureChordWeights(self, measureObj, weightAlgorithm=None): >>> cws = cr.computeMeasureChordWeights(s, weightAlgorithm=cr.quarterLengthBeatStrength) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 2.2 (0, 11, 4, 5) 0.5 @@ -176,7 +176,7 @@ def computeMeasureChordWeights(self, measureObj, weightAlgorithm=None): >>> cws = cr.computeMeasureChordWeights(s, ... weightAlgorithm=cr.quarterLengthBeatStrengthMeasurePosition) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 3.0 (0, 11, 4, 5) 0.5 @@ -184,7 +184,7 @@ def computeMeasureChordWeights(self, measureObj, weightAlgorithm=None): >>> cws = cr.computeMeasureChordWeights(s, weightAlgorithm=cr.qlbsmpConsonance) >>> for pcs in sorted(cws): - ... print("%18r %2.1f" % (pcs, cws[pcs])) + ... print(f'{pcs!r:18} {cws[pcs]:2.1f}') (0, 4, 7) 3.0 (0, 11, 4, 5) 0.5 ''' diff --git a/music21/analysis/reduction.py b/music21/analysis/reduction.py index 7698032ea..e850be322 100644 --- a/music21/analysis/reduction.py +++ b/music21/analysis/reduction.py @@ -295,13 +295,13 @@ def _extractNoteReductiveEvent(self, n, infoDict=None, removeAfterParsing=True): # a list of Lyric objects - for k, l in enumerate(n.lyrics): + for k, lyr in enumerate(n.lyrics): # store measure index - rn = ReductiveNote(l.text, n, infoDict['measureIndex'], offset) + rn = ReductiveNote(lyr.text, n, infoDict['measureIndex'], offset) if rn.isParsed(): # environLocal.printDebug(['parsing reductive note', rn]) # use id, lyric text as hash - key = str(id(n)) + l.text + key = str(id(n)) + lyr.text self._reductiveNotes[key] = rn removalIndices.append(k) if removeAfterParsing: @@ -1095,7 +1095,7 @@ def _matchWeightedData(self, match, target): dataMatch[2], dataTarget[2], msg=(f'for partId {partId}, entry {i}: ' - + f'should be {dataMatch[2]} <-> was {dataTarget[2]}') + f'should be {dataMatch[2]} <-> was {dataTarget[2]}') ) def testPartReductionB(self, show=False): diff --git a/music21/analysis/windowed.py b/music21/analysis/windowed.py index a0c200ddb..370912c94 100644 --- a/music21/analysis/windowed.py +++ b/music21/analysis/windowed.py @@ -164,7 +164,8 @@ def analyze(self, windowSize, windowType='overlap'): windowCount = int(windowCountFloat) if windowCountFloat != windowCount: warnings.warn( - 'maxWindowCount is not divisible by windowSize, possibly undefined behavior' + 'maxWindowCount is not divisible by windowSize, possibly undefined behavior', + stacklevel=2 ) elif windowType == 'adjacentAverage': windowCount = maxWindowCount diff --git a/music21/audioSearch/__init__.py b/music21/audioSearch/__init__.py index f056fd391..92a99681c 100644 --- a/music21/audioSearch/__init__.py +++ b/music21/audioSearch/__init__.py @@ -140,7 +140,7 @@ def autocorrelationFunction(recordedSignal, recordSampleRateIn): # len(_missingImport) > 0: raise AudioSearchException( 'Cannot run autocorrelationFunction without ' - + f'numpy installed (scipy recommended). Missing {bmi}') + f'numpy installed (scipy recommended). Missing {bmi}') import numpy convolve = None try: @@ -151,7 +151,7 @@ def autocorrelationFunction(recordedSignal, recordSampleRateIn): # noinspection PyPackageRequirements from scipy.signal import fftconvolve as convolve # type: ignore except ImportError: # pragma: no cover - warnings.warn('Running convolve without scipy -- will be slower') + warnings.warn('Running convolve without scipy -- will be slower', stacklevel=2) convolve = numpy.convolve recordedSignal = numpy.array(recordedSignal) @@ -276,7 +276,7 @@ def normalizeInputFrequency(inputPitchFrequency, thresholds=None, pitches=None): ): raise AudioSearchException( 'Cannot normalize input frequency if thresholds are given and ' - + 'pitches are not, or vice-versa') + + 'pitches are not, or vice versa') if thresholds is None: (thresholds, pitches) = prepareThresholds() diff --git a/music21/audioSearch/recording.py b/music21/audioSearch/recording.py index 64802018b..0c18b4740 100644 --- a/music21/audioSearch/recording.py +++ b/music21/audioSearch/recording.py @@ -74,7 +74,6 @@ def samplesFromRecording(seconds=10.0, storeFile=True, for i in range(recordingLength): data = st.read(recordChunkLength) storedWaveSampleList.append(data) - # print('Time elapsed: %.3f s\n' % (time.time() - time_start)) st.close() p_audio.terminate() diff --git a/music21/audioSearch/scoreFollower.py b/music21/audioSearch/scoreFollower.py index e705c35f9..cad65c792 100644 --- a/music21/audioSearch/scoreFollower.py +++ b/music21/audioSearch/scoreFollower.py @@ -126,9 +126,9 @@ def repeatTranscription(self): # print('WE STAY AT:',) # print(self.lastNotePosition, len(self.scoreNotesOnly),) - # print('en percent %s %%' % (self.lastNotePosition * 100 / len(self.scoreNotesOnly)),) + # print(f'en percent {self.lastNotePosition * 100 / len(self.scoreNotesOnly)}%') # print(' this search begins at: ', self.startSearchAtSlot,) - # print('countdown %s' % self.countdown) + # print(f'countdown {self.countdown}') # print('Measure last note', self.scoreStream[self.lastNotePosition].measureNumber) environLocal.printDebug('repeat transcription starting') diff --git a/music21/base.py b/music21/base.py index 5a1acb801..775baaf72 100644 --- a/music21/base.py +++ b/music21/base.py @@ -245,7 +245,7 @@ class Groups(list): # no need to inherit from slotted object def _validName(self, value: str) -> None: if not isinstance(value, str): raise exceptions21.GroupException('Only strings can be used as group names, ' - + f'not {value!r}') + f'not {value!r}') # if ' ' in value: # raise exceptions21.GroupException('Spaces are not allowed as group names') @@ -588,7 +588,7 @@ def id(self, new_id: int|str): if isinstance(new_id, int) and new_id > defaults.minIdNumberToConsiderMemoryLocation: msg = 'Setting an ID that could be mistaken for a memory location ' msg += f'is discouraged: got {new_id}' - warnings.warn(msg) + warnings.warn(msg, stacklevel=2) self._id = new_id def mergeAttributes(self, other: Music21Object) -> None: @@ -638,8 +638,8 @@ def _deepcopySubclassable(self, ignoreAttributes = ignoreAttributes | defaultIgnoreSet new = common.defaultDeepcopy(self, memo, ignoreAttributes=ignoreAttributes) - setattr(new, '_cache', {}) - setattr(new, '_sites', Sites()) + new._cache = {} + new._sites = Sites() if 'groups' in defaultIgnoreSet: new.groups = Groups() @@ -648,7 +648,7 @@ def _deepcopySubclassable(self, newDerivation = Derivation(client=new) newDerivation.origin = self newDerivation.method = '__deepcopy__' - setattr(new, '_derivation', newDerivation) + new._derivation = newDerivation # None activeSite is correct for new value # must do this after copying @@ -749,8 +749,8 @@ def hasEditorialInformation(self) -> bool: >>> mObj.hasEditorialInformation True ''' - # anytime something is changed here, change in style.StyleMixin and vice-versa - return not (self._editorial is None) + # any time something is changed here, change in style.StyleMixin and vice versa + return self._editorial is not None @property def editorial(self) -> Editorial: @@ -772,7 +772,7 @@ def editorial(self) -> Editorial: # Dev note: because the property "editorial" shadows module editorial, # typing has to be in quotes. - # anytime something is changed here, change in style.StyleMixin and vice-versa + # anytime something is changed here, change in style.StyleMixin and vice versa if self._editorial is None: self._editorial = Editorial() return self._editorial @@ -800,8 +800,8 @@ def hasStyleInformation(self) -> bool: >>> mObj.hasStyleInformation True ''' - # anytime something is changed here, change in style.StyleMixin and vice-versa - return not (self._style is None) + # anytime something is changed here, change in style.StyleMixin and vice versa + return self._style is not None @property def style(self) -> Style: @@ -826,7 +826,7 @@ def style(self) -> Style: >>> n.style.absoluteX is None True ''' - # anytime something is changed here, change in style.StyleMixin and vice-versa + # anytime something is changed here, change in style.StyleMixin and vice versa if not self.hasStyleInformation: StyleClass = self._styleClass self._style = StyleClass() @@ -2110,7 +2110,7 @@ def contextSites( memo.add(siteObj) environLocal.printDebug( f'looking in contextSites for {siteObj}' - + f' with position {positionInStream.shortRepr()}') + f' with position {positionInStream.shortRepr()}') for topLevel, inStreamPos, recurType in siteObj.contextSites( callerFirst=callerFirst, memo=memo, @@ -2127,10 +2127,11 @@ def contextSites( hypotheticalPosition = positionInStream.modify(offset=inStreamOffset) if topLevel not in memo: - # environLocal.printDebug('Yielding {}, {}, {} from contextSites'.format( - # topLevel, - # inStreamPos.shortRepr(), - # recurType)) + # environLocal.printDebug( + # f'Yielding {topLevel}, ' + # f'{inStreamPos.shortRepr()}, ' + # f'{recurType} from contextSites' + # ) if returnSortTuples: yield ContextSortTuple(topLevel, hypotheticalPosition, recurType) else: @@ -2143,7 +2144,7 @@ def contextSites( for derivedObject in topLevel.derivation.chain(): environLocal.printDebug( 'looking now in derivedObject, ' - + f'{derivedObject} with offsetAppend {offsetAppend}') + f'{derivedObject} with offsetAppend {offsetAppend}') for derivedCsTuple in derivedObject.contextSites( callerFirst=None, memo=memo, @@ -2440,7 +2441,7 @@ def _setActiveSite(self, site: stream.Stream|None): except SitesException as se: raise SitesException( 'activeSite cannot be set for ' - + f'object {self} not in the Stream {site}' + f'object {self} not in the Stream {site}' ) from se self._activeSiteStoredOffset = storedOffset @@ -3218,7 +3219,7 @@ def splitAtQuarterLength( if quarterLength > self.duration.quarterLength: raise DurationException( f'cannot split a duration ({self.duration.quarterLength}) ' - + f'at this quarterLength ({quarterLength})' + f'at this quarterLength ({quarterLength})' ) if retainOrigin is True: @@ -3378,8 +3379,8 @@ def splitByQuarterLengths( if opFrac(sum(quarterLengthList)) != self.duration.quarterLength: raise Music21ObjectException( 'cannot split by quarter length list whose sum is not ' - + 'equal to the quarterLength duration of the source: ' - + f'{quarterLengthList}, {self.duration.quarterLength}' + 'equal to the quarterLength duration of the source: ' + f'{quarterLengthList}, {self.duration.quarterLength}' ) # if nothing to do diff --git a/music21/beam.py b/music21/beam.py index 21b659c3e..b2971aa53 100644 --- a/music21/beam.py +++ b/music21/beam.py @@ -367,8 +367,9 @@ def mergeConnectingPartialBeams(beamsList): continue if nextBeam.type in ('continue', 'stop'): environLocal.warn( - 'Found a messed up beam pair {}, {}, at index {} of \n{}'.format( - bThis, bNext, i, beamsList)) + f'Found a messed up beam pair {bThis}, {bNext}, ' + f'at index {i} of \n{beamsList}' + ) continue thisBeam.type = 'start' diff --git a/music21/braille/basic.py b/music21/braille/basic.py index aae0121d7..63ef59245 100644 --- a/music21/braille/basic.py +++ b/music21/braille/basic.py @@ -204,7 +204,7 @@ def chordToBraille(music21Chord, descending=True, showOctave=True): except KeyError: environRules.warn( f'Accidental {currentPitch.accidental} of ' - + f'chord {music21Chord} cannot be transcribed to braille.' + f'chord {music21Chord} cannot be transcribed to braille.' ) intervalDistance = interval.notesToGeneric(basePitch, currentPitch).undirected if intervalDistance > 8: @@ -1115,7 +1115,7 @@ def timeSigToBraille(m21TimeSignature): brailleSig = ''.join(timeSigTrans) m21TimeSignature.editorial.brailleEnglish.append( f'Time Signature {m21TimeSignature.numerator}/{m21TimeSignature.denominator} ' - + f'{brailleSig}' + f'{brailleSig}' ) return brailleSig except (BrailleBasicException, KeyError) as unused_error: # pragma: no cover diff --git a/music21/braille/segment.py b/music21/braille/segment.py index f934f3d7c..951e5af47 100644 --- a/music21/braille/segment.py +++ b/music21/braille/segment.py @@ -429,18 +429,22 @@ def __str__(self): continue except TypeError: pass - allKeys.append('Measure {0}, {1} {2}:\n'.format(itemKey.measure, - affinityNames[itemKey.affinity], - itemKey.ordinal + 1)) + allKeys.append( + f'Measure {itemKey.measure}, ' + f'{affinityNames[itemKey.affinity]} ' + f'{itemKey.ordinal + 1}:\n' + ) gStr = str(grouping) allGroupings.append(gStr) prevKey = itemKey - allElementGroupings = '\n'.join([''.join([k, g, '\n===']) - for (k, g) in list(zip(allKeys, allGroupings))]) + allElementGroupings = '\n'.join( + f'{k}{g}\n===' + for k, g in zip(allKeys, allGroupings) + ) out = '\n'.join(['---begin segment---', - name, - allElementGroupings, - '---end segment---']) + name, + allElementGroupings, + '---end segment---']) return out def transcribe(self): @@ -1114,22 +1118,30 @@ def __str__(self): allPairs = [] for (rightKey, leftKey) in self.yieldCombinedGroupingKeys(): if rightKey is not None: - rightHeading = 'Measure {0} Right, {1} {2}:\n'.format( - rightKey.measure, affinityNames[rightKey.affinity], rightKey.ordinal + 1) + rightHeading = ( + f'Measure {rightKey.measure} Right, ' + f'{affinityNames[rightKey.affinity]} ' + f'{rightKey.ordinal + 1}:\n' + ) rightContents = str(self._groupingDict.get(rightKey)) rightFull = ''.join([rightHeading, rightContents]) else: rightFull = '' if leftKey is not None: - leftHeading = '\nMeasure {0} Left, {1} {2}:\n'.format( - leftKey.measure, affinityNames[leftKey.affinity], leftKey.ordinal + 1) + leftHeading = ( + f'\nMeasure {leftKey.measure} Left, ' + f'{affinityNames[leftKey.affinity]} ' + f'{leftKey.ordinal + 1}:\n' + ) leftContents = str(self._groupingDict.get(leftKey)) leftFull = ''.join([leftHeading, leftContents]) else: leftFull = '' allPairs.append('\n'.join([rightFull, leftFull, '====\n'])) - out = '\n'.join(['---begin grand segment---', name, ''.join(allPairs), - '---end grand segment---']) + out = '\n'.join(['---begin grand segment---', + name, + ''.join(allPairs), + '---end grand segment---']) return out def yieldCombinedGroupingKeys(self): @@ -1240,13 +1252,14 @@ def matchOther(thisKey_inner, otherKey): # gkLeft = gkRight._replace(affinity=gkRight.affinity - 1) # try: # groupingKeysLeft.remove(gkLeft) - # except ValueError: + # except ValueError as ve: # raise BrailleSegmentException( - # 'Misaligned braille groupings: ' + - # 'groupingKeyLeft was %s' % gkLeft + - # 'groupingKeyRight was %s' % gkRight + - # 'rightSegment was %s, leftSegment was %s' % - # (rightSegment, leftSegment)) + # 'Misaligned braille groupings: ' + # f'groupingKeyLeft was {gkLeft} ' + # f'groupingKeyRight was {gkRight} ' + # f'rightSegment was {rightSegment}, ' + # f'leftSegment was {leftSegment}' + # ) from ve # # try: # combinedGroupingTuple = (gkRight, gkLeft) @@ -2358,7 +2371,7 @@ def splitMeasure(music21Measure, beatDivisionOffset=0, useTimeSignature=None): if abs(beatDivisionOffset) > len(ts.beatDivisionDurations): raise BrailleSegmentException( f'beatDivisionOffset {beatDivisionOffset} is outside ' - + f'of ts.beatDivisionDurations {ts.beatDivisionDurations}' + f'of ts.beatDivisionDurations {ts.beatDivisionDurations}' ) duration_index = len(ts.beatDivisionDurations) - abs(beatDivisionOffset) try: @@ -2366,7 +2379,7 @@ def splitMeasure(music21Measure, beatDivisionOffset=0, useTimeSignature=None): offset = opFrac(offset) except IndexError: environRules.warn('Problem in converting a time signature in measure ' - + f'{music21Measure.number}, offset may be wrong') + f'{music21Measure.number}, offset may be wrong') bs = copy.deepcopy(ts.beatSequence) numberOfPartitions = 2 diff --git a/music21/chord/__init__.py b/music21/chord/__init__.py index 376bc9225..5d6da1858 100644 --- a/music21/chord/__init__.py +++ b/music21/chord/__init__.py @@ -849,8 +849,8 @@ def __getitem__(self, key: int|str|note.Note|pitch.Pitch): if isinstance(key, int): try: foundNote = self._notes[key] # must be a number - except (KeyError, IndexError): - raise KeyError(keyErrorStr) + except (KeyError, IndexError) as exc: + raise KeyError(keyErrorStr) from exc elif isinstance(key, str): key = key.upper() diff --git a/music21/chord/tables.py b/music21/chord/tables.py index 0dfcee549..1508fc47b 100644 --- a/music21/chord/tables.py +++ b/music21/chord/tables.py @@ -1561,8 +1561,8 @@ def _validateAddress(address): # ultimately this can be removed # try: # nfSet = cardinalityToChordMembers[card][(index, inversion)][0] - # except KeyError: - # raise ChordTablesException('cannot validate address: %s' % address) + # except KeyError as ke: + # raise ChordTablesException(f'cannot validate address: {address}') from ke return (card, index, inversion) @@ -1945,7 +1945,7 @@ def testForteNumberWithInversionToTnIndex(self): else: partition[key[0]].append(value) # append unique ids - for key, value in partition.items(): + for value in partition.values(): # the length of the list should be the max value stored self.assertEqual(max(value), len(value)) diff --git a/music21/clef.py b/music21/clef.py index 01c442c3c..b81f6ec81 100644 --- a/music21/clef.py +++ b/music21/clef.py @@ -297,7 +297,7 @@ def octaveChange(self) -> int: >>> clef.Treble8vbClef().octaveChange -1 - Changing octaveChange changes lowestLine (but not vice-versa) + Changing octaveChange changes lowestLine (but not vice versa) >>> tc.lowestLine 31 @@ -858,7 +858,7 @@ def clefFromString(clefString, octaveShift=0) -> Clef: if lineNum < 1 or lineNum > 5: raise ClefException('line number (second character) must be 1-5; do not use this ' - + f'function for clefs on special staves such as {xnStr!r}') + f'function for clefs on special staves such as {xnStr!r}') clefObj: Clef if thisType in CLASS_FROM_TYPE: diff --git a/music21/common/__init__.py b/music21/common/__init__.py index bea3d4a4c..f3a6b6144 100644 --- a/music21/common/__init__.py +++ b/music21/common/__init__.py @@ -38,18 +38,31 @@ 'weakrefTools', ] +from music21.common import classTools from music21.common.classTools import * # including isNum, isListLike +from music21.common import decorators from music21.common.decorators import * # gives the deprecated decorator +from music21.common import enums from music21.common.enums import * +from music21.common import fileTools from music21.common.fileTools import * # file tools. +from music21.common import formats from music21.common.formats import * # most are deprecated! +from music21.common import misc from music21.common.misc import * # most are deprecated! +from music21.common import numberTools from music21.common.numberTools import * # including opFrac +from music21.common import objects from music21.common.objects import * +from music21.common import pathTools from music21.common.pathTools import * +from music21.common import parallel from music21.common.parallel import * +from music21.common import stringTools from music21.common.stringTools import * +from music21.common import types from music21.common.types import * +from music21.common import weakrefTools from music21.common.weakrefTools import * # including wrapWeakref DEBUG_OFF = 0 diff --git a/music21/common/objects.py b/music21/common/objects.py index bd169fa69..573511763 100644 --- a/music21/common/objects.py +++ b/music21/common/objects.py @@ -193,7 +193,7 @@ class SlottedObjectMixin: def __getstate__(self): if getattr(self, '__dict__', None) is not None: - state = getattr(self, '__dict__').copy() + state = self.__dict__.copy() else: state = {} slots = self._getSlotsRecursive() diff --git a/music21/common/parallel.py b/music21/common/parallel.py index f6942a247..6703db6a1 100644 --- a/music21/common/parallel.py +++ b/music21/common/parallel.py @@ -70,7 +70,7 @@ def runParallel(iterable, parallelFunction, *, With a custom updateFunction that gets each output: >>> def yak(position, length, output): - ... print("%s:%s %s is a lot of notes!" % (position, length, output)) + ... print(f'{position}:{length} {output} is a lot of notes!') >>> #_DOCS_SHOW outputs = common.runParallel(files, countNotes, updateFunction=yak) >>> outputs = common.runNonParallel(files, countNotes, updateFunction=yak) #_DOCS_HIDE 0:3 165 is a lot of notes! @@ -80,7 +80,7 @@ def runParallel(iterable, parallelFunction, *, Or with updateSendsIterable, we can get the original files data as well: >>> def yik(position, length, output, fn): - ... print("%s:%s (%s) %s is a lot of notes!" % (position, length, fn, output)) + ... print(f'{position}:{length} ({fn}) {output} is a lot of notes!') >>> #_DOCS_SHOW outputs = common.runParallel(files, countNotes, updateFunction=yik, >>> outputs = common.runNonParallel(files, countNotes, updateFunction=yik, #_DOCS_HIDE ... updateSendsIterable=True) diff --git a/music21/configure.py b/music21/configure.py index 8045d8150..3759fe93c 100644 --- a/music21/configure.py +++ b/music21/configure.py @@ -1419,7 +1419,7 @@ def run(self, forceList=None): # self.timeLeft -= value # # def printPrompt(self): -# sys.stdout.write('%s: ' % self.prompt) +# sys.stdout.write(f'{self.prompt}: ') # # def run(self): # self.printPrompt() # print on first call @@ -1445,7 +1445,7 @@ def run(self, forceList=None): # current.removeTime(updateInterval) # # if intervalCount % reportInterval == reportInterval - 1: -# sys.stdout.write('\n time out in %s seconds\n' % current.timeLeft) +# sys.stdout.write(f'\n time out in {current.timeLeft} seconds\n') # current.printPrompt() # # intervalCount += 1 @@ -1459,7 +1459,7 @@ def run(self, forceList=None): # if post == None: # print('got no value') # else: -# print('got: %s' % post) +# print(f'got: {post}') # ------------------------------------------------------------------------------ # define presented order in documentation _DOC_ORDER: list[type] = [] diff --git a/music21/converter/__init__.py b/music21/converter/__init__.py index 2ba1837c2..71e4b9108 100644 --- a/music21/converter/__init__.py +++ b/music21/converter/__init__.py @@ -706,8 +706,9 @@ def parseData( elif 'Time Signature:' in dataStrMakeStr and 'm1' in dataStrMakeStr: useFormat = 'romanText' else: - raise ConverterException('File not found or no such format found for: %s' % - dataStrMakeStr) + raise ConverterException( + f'File not found or no such format found for: {dataStrMakeStr}' + ) self.setSubConverterFromFormat(useFormat) if t.TYPE_CHECKING: diff --git a/music21/corpus/__init__.py b/music21/corpus/__init__.py index 500a62ab8..38cacecc0 100644 --- a/music21/corpus/__init__.py +++ b/music21/corpus/__init__.py @@ -67,7 +67,7 @@ 'manager', 'noCorpus', 'parse', - # virtual + 'virtual', 'search', 'work', ] diff --git a/music21/corpus/chorales.py b/music21/corpus/chorales.py index 09b636e61..9987ddf1a 100644 --- a/music21/corpus/chorales.py +++ b/music21/corpus/chorales.py @@ -1168,7 +1168,7 @@ def __next__(self): _currentIndex becomes higher than the _highestIndex, the iteration stops. ''' if self._currentIndex > self._highestIndex: - raise StopIteration + raise StopIteration() nextChorale = self._returnChorale() self._currentIndex += 1 return nextChorale @@ -1273,7 +1273,7 @@ def _returnChorale(self, choraleIndex=None): try: nextIndex = self._currentIndex + 1 riemenschneiderName = ('bach/choraleAnalyses/' - + f'riemenschneider{nextIndex:03d}.rntxt') + f'riemenschneider{nextIndex:03d}.rntxt') analysis = corpus.parse(riemenschneiderName) if analysis is not None: chorale.insert(0, analysis.parts[0]) @@ -1582,7 +1582,7 @@ def currentNumber(self, value): else: raise BachException( f'{value} does not correspond to a chorale in the ' - + f'{self.numberingSystem} numbering system' + f'{self.numberingSystem} numbering system' ) elif self._iterationType == 'index': @@ -1699,7 +1699,7 @@ def highestNumber(self, value): else: raise BachException( f'{value} does not correspond to a chorale in the ' - + f'{self.numberingSystem} numbering system' + f'{self.numberingSystem} numbering system' ) # - Return Type diff --git a/music21/corpus/manager.py b/music21/corpus/manager.py index 1114b9cf9..5f6c5ccb2 100644 --- a/music21/corpus/manager.py +++ b/music21/corpus/manager.py @@ -355,9 +355,10 @@ def getMetadataBundleByCorpus(corpusObject: corpora.Corpus) -> bundles.MetadataB assert mdb is not None # cacheMetadataBundleFromDisk makes None impossible return mdb else: # pragma: no cover - raise CorpusException('No metadata bundle found for corpus {0} with name {1}'.format( - corpusObject, corpusName)) - + raise CorpusException( + f'No metadata bundle found for corpus {corpusObject} ' + f'with name {corpusName}' + ) def cacheMetadataBundleFromDisk(corpusObject: corpora.Corpus) -> None: r''' diff --git a/music21/duration.py b/music21/duration.py index 0b5dcc6f0..2a9e739fb 100644 --- a/music21/duration.py +++ b/music21/duration.py @@ -250,7 +250,7 @@ def quarterLengthToClosestType(qLen: OffsetQLIn) -> tuple[str, bool]: ''' def raise_it(qLen_inner): return DurationException('Cannot return types smaller than 2048th; ' - + f'qLen was: {qLen}') + f'qLen was: {qLen}') if not qLen: raise raise_it(qLen) @@ -2122,7 +2122,8 @@ def componentStartTime(self, componentIndex: int) -> float: if not (0 <= componentIndex < len(self.components)): raise IndexError( f'invalid component index value {componentIndex} ' - + f'submitted; value must be an integer between 0 and {len(self.components) - 1}') + f'submitted; value must be an integer between 0 and {len(self.components) - 1}' + ) components = self.components[:componentIndex] return float(sum([c.quarterLength for c in components])) diff --git a/music21/dynamics.py b/music21/dynamics.py index 764667f40..112154f67 100644 --- a/music21/dynamics.py +++ b/music21/dynamics.py @@ -127,7 +127,7 @@ class Dynamic(base.Music21Object): >>> ppp = dynamics.Dynamic(0.15) # on 0 to 1 scale >>> ppp.value 'ppp' - >>> print('%.2f' % ppp.volumeScalar) + >>> print(f'{ppp.volumeScalar:.2f}') 0.15 Note that we got lucky last time because the dynamic 0.15 exactly corresponds @@ -138,7 +138,7 @@ class Dynamic(base.Music21Object): >>> loud = dynamics.Dynamic(0.98) # on 0 to 1 scale >>> loud.value 'fff' - >>> print('%.2f' % loud.volumeScalar) + >>> print(f'{loud.volumeScalar:.2f}') 0.98 Transferring the .value ('fff') to a new Dynamic object will set the volumeScalar @@ -147,7 +147,7 @@ class Dynamic(base.Music21Object): >>> loud2 = dynamics.Dynamic(loud.value) >>> loud2.value 'fff' - >>> print('%.2f' % loud2.volumeScalar) + >>> print(f'{loud2.volumeScalar:.2f}') 0.90 Custom dynamics are possible: diff --git a/music21/environment.py b/music21/environment.py index e2ce9d0bb..2a1ff1107 100644 --- a/music21/environment.py +++ b/music21/environment.py @@ -613,9 +613,9 @@ def getRootTempDir(self): if not refDir.exists(): raise EnvironmentException( - 'user-specified scratch directory ({!s}) does not exist; ' - 'remove preference file or reset Environment'.format( - refDir)) + f'user-specified scratch directory ({refDir!s}) does not exist; ' + f'remove preference file or reset Environment' + ) return refDir def getSettingsPath(self): diff --git a/music21/expressions.py b/music21/expressions.py index 08da2e730..8596c3f6a 100644 --- a/music21/expressions.py +++ b/music21/expressions.py @@ -2835,7 +2835,7 @@ def __init__(self, arpeggioType: str|None = None, **keywords): if arpeggioType not in ('normal', 'up', 'down', 'non-arpeggio'): raise ValueError( 'Arpeggio type must be "normal", "up", "down", or "non-arpeggio", ' - + f'not {arpeggioType!r}.' + f'not {arpeggioType!r}.' ) self.type = arpeggioType @@ -2874,7 +2874,7 @@ def __init__(self, if arpeggioType not in ('normal', 'up', 'down', 'non-arpeggio'): raise ValueError( 'Arpeggio type must be "normal", "up", "down", or "non-arpeggio", ' - + f'not {arpeggioType!r}.' + f'not {arpeggioType!r}.' ) self.type = arpeggioType diff --git a/music21/features/base.py b/music21/features/base.py index 61183da4d..41a606540 100644 --- a/music21/features/base.py +++ b/music21/features/base.py @@ -1083,7 +1083,7 @@ def write(self, fp=None, format=None, includeClassLabel=True): outputFormat = self._getOutputFormat(format) if outputFormat is None: raise DataSetException('no output format could be defined from file path ' - + f'{fp} or format {format}') + f'{fp} or format {format}') return outputFormat.write(fp=fp, includeClassLabel=includeClassLabel) @@ -1692,8 +1692,11 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # if c != matchData[i].getclass(): # mismatch += 1 # - # print('%s %s: misclassified %s/%s of %s' % ( - # classifierStr, classifierType, mismatch, len(matchData), matchStr)) + # print( + # f'{classifierStr} {classifierType}: misclassified ' + # f'{mismatch}/{len(matchData)} of ' + # f'{matchStr}' + # ) # # # if classifierType == orngTree.TreeLearner: # # orngTree.printTxt(classifier) @@ -1741,9 +1744,11 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # if c != matchData[i].getclass(): # mismatch += 1 # - # print('%s %s: misclassified %s/%s of %s' % ( - # classifierStr, classifierType, mismatch, len(matchData), matchStr)) - + # print( + # f'{classifierStr} {classifierType}: misclassified ' + # f'{mismatch}/{len(matchData)} of ' + # f'{matchStr}' + # ) # def xtestOrangeClassifiers(self): # pragma: no cover # ''' @@ -1770,17 +1775,16 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # print('Possible classes:', data.domain.classVar.values) # print('Original Class', end=' ') # for l in classifiers: - # print('%-13s' % (l.name), end=' ') + # print(f'{l.name:<13}', end=' ') # print() # # for example in data: - # print('(%-10s) ' % (example.getclass()), end=' ') + # print(f'({example.getclass():<10}) ', end=' ') # for c in classifiers: # p = c([example, orange.GetProbabilities]) - # print('%5.3f ' % (p[0]), end=' ') + # print(f'{p[0]:5.3f} ', end=' ') # print('') - # def xtestOrangeClassifierTreeLearner(self): # pragma: no cover # import orange, orngTree # pylint: disable=import-error # data = orange.ExampleTable( @@ -1790,7 +1794,9 @@ def x_testRegionClassificationJSymbolicB(self): # pragma: no cover # # tree = orngTree.TreeLearner(data) # for i in range(len(data)): # p = tree(data[i], orange.GetProbabilities) - # print('%s: %5.3f (originally %s)' % (i + 1, p[1], data[i].getclass())) + # print( + # f'{i + 1}: {p[1]:5.3f} (originally {data[i].getclass()})' + # ) # # orngTree.printTxt(tree) diff --git a/music21/figuredBass/checker.py b/music21/figuredBass/checker.py index 76d8159d5..e39f20388 100644 --- a/music21/figuredBass/checker.py +++ b/music21/figuredBass/checker.py @@ -85,7 +85,7 @@ def extractHarmonies(music21Stream): >>> from music21.figuredBass import checker >>> allHarmonies = checker.extractHarmonies(score) >>> for (offsets, notes) in sorted(allHarmonies.items()): - ... print('{0!s:15}[{1!s:23}{2!s:23}{3!s:22}]'.format(offsets, notes[0], notes[1], notes[2])) + ... print(f'{offsets!s:15}[{notes[0]!s:23}{notes[1]!s:23}{notes[2]!s:22}]') (0.0, 1.5) [ ] (1.5, 2.0) [ ] (2.0, 3.0) [ ] @@ -123,7 +123,7 @@ def createOffsetMapping(music21Part): >>> v0 = score[0] >>> offsetMapping = checker.createOffsetMapping(v0) >>> for (offsets, notes) in sorted(offsetMapping.items()): - ... print('{0!s:15}[{1!s:22}]'.format(offsets, notes[0])) + ... print(f'{offsets!s:15}[{notes[0]!s:22}]') (0.0, 1.5) [ ] (1.5, 2.0) [ ] (2.0, 3.0) [] @@ -157,7 +157,7 @@ def correlateHarmonies(currentMapping, music21Part): >>> v1 = score[1] >>> newMapping = checker.correlateHarmonies(offsetMapping, v1) >>> for (offsets, notes) in sorted(newMapping.items()): - ... print('{0!s:15}[{1!s:23}{2!s:21}]'.format(offsets, notes[0], notes[1])) + ... print(f'{offsets!s:15}[{notes[0]!s:23}{notes[1]!s:21}]') (0.0, 1.5) [ ] (1.5, 2.0) [ ] (2.0, 3.0) [ ] @@ -369,18 +369,12 @@ def voiceCrossing(possibA): ''' partViolations = [] for part1Index in range(len(possibA)): - try: # noqa - higherPitch = possibA[part1Index] - # noinspection PyStatementEffect - higherPitch.ps # pylint: disable=pointless-statement - except AttributeError: + higherPitch = possibA[part1Index] + if not hasattr(higherPitch, 'ps'): continue for part2Index in range(part1Index + 1, len(possibA)): - try: # noqa - lowerPitch = possibA[part2Index] - # noinspection PyStatementEffect - lowerPitch.ps # pylint: disable=pointless-statement - except AttributeError: + lowerPitch = possibA[part2Index] + if not hasattr(lowerPitch, 'ps'): continue if higherPitch < lowerPitch: partViolations.append((part1Index + 1, part2Index + 1)) diff --git a/music21/figuredBass/notation.py b/music21/figuredBass/notation.py index fe8099ec3..0ff137f8a 100644 --- a/music21/figuredBass/notation.py +++ b/music21/figuredBass/notation.py @@ -771,8 +771,10 @@ def convertToPitch(pitchString): if isinstance(pitchString, str): try: return pitch.Pitch(pitchString) - except: - raise ValueError('Cannot convert string ' + pitchString + ' to a music21 Pitch.') + except (ValueError, pitch.PitchException) as pe: + raise ValueError( + 'Cannot convert string ' + pitchString + ' to a music21 Pitch.' + ) from pe raise TypeError('Cannot convert ' + pitchString + ' to a music21 Pitch.') diff --git a/music21/figuredBass/possibility.py b/music21/figuredBass/possibility.py index e116bab33..f3944f5a9 100644 --- a/music21/figuredBass/possibility.py +++ b/music21/figuredBass/possibility.py @@ -199,7 +199,10 @@ def upperPartsWithinLimit(possibA, maxSemitoneSeparation=12): return areUpperPartsWithinLimit -def pitchesWithinLimit(possibA, maxPitch=pitch.Pitch('B5')): +DEFAULT_MAX_PITCH = pitch.Pitch('B5') + + +def pitchesWithinLimit(possibA, maxPitch=DEFAULT_MAX_PITCH): ''' Returns True if all pitches in possibA are less than or equal to the maxPitch provided. Comparisons between pitches are done using pitch diff --git a/music21/figuredBass/realizer.py b/music21/figuredBass/realizer.py index 853767ca1..2ad803df3 100644 --- a/music21/figuredBass/realizer.py +++ b/music21/figuredBass/realizer.py @@ -61,9 +61,6 @@ from music21.figuredBass import rules from music21.figuredBass import segment -if t.TYPE_CHECKING: - from music21.stream.iterator import StreamIterator - def figuredBassFromStream(streamPart: stream.Stream) -> FiguredBassLine: # noinspection PyShadowingNames @@ -276,7 +273,8 @@ def addElement(self, bassObject: note.Note, notationString=None): else: raise FiguredBassLineException( 'Not a valid bassObject (only note.Note, ' - + f'harmony.ChordSymbol, and roman.RomanNumeral supported) was {bassObject!r}') + f'harmony.ChordSymbol, and roman.RomanNumeral supported) was {bassObject!r}' + ) def generateBassLine(self): ''' @@ -720,7 +718,7 @@ def generateRealizationFromPossibilityProgression(self, possibilityProgression): else: # Chorale-style output upperParts = [] - for partNumber in range(len(possibilityProgression[0]) - 1): + for _partNumber in range(len(possibilityProgression[0]) - 1): fbPart = stream.Part() sol.insert(0.0, fbPart) fbPart.append([copy.deepcopy(self._keySig), copy.deepcopy(self._inTime)]) diff --git a/music21/freezeThaw.py b/music21/freezeThaw.py index 2726884b5..6f5217e2d 100644 --- a/music21/freezeThaw.py +++ b/music21/freezeThaw.py @@ -1083,9 +1083,11 @@ def x_testSimplePickle(self): # for el in s._elements: # idEl = el.id # if idEl not in storedIds: - # print('Could not find ID %s for element %r at offset %f' % - # (idEl, el, el.offset)) + # print( + # f'Could not find ID {idEl} for element {el!r} at offset {el.offset:f}' + # ) # print(storedIds) + # s.show('t') def x_testFreezeThawPickle(self): diff --git a/music21/graph/axis.py b/music21/graph/axis.py index 329c16b26..969b28c91 100644 --- a/music21/graph/axis.py +++ b/music21/graph/axis.py @@ -1334,7 +1334,7 @@ def ticks(self): self.setBoundariesFromData() for i in range(self.minValue, self.maxValue + 1): # place string in tex format for italic display - ticks.append((i, r'$%s$' % dynamics.shortNames[i])) + ticks.append((i, f'${dynamics.shortNames[i]}$')) return ticks # ----------------------------------------------------------------------------- diff --git a/music21/graph/findPlot.py b/music21/graph/findPlot.py index 63ec04d37..b57895251 100644 --- a/music21/graph/findPlot.py +++ b/music21/graph/findPlot.py @@ -367,7 +367,7 @@ def _bestPlotType(graphClassesToChooseFrom): # no matches for values. Try agnostic about X and Y graphRemove = [] - for axisLetter, axisValue in (('x', xValue), ('y', yValue), ('z', zValue)): + for _axisLetter, axisValue in (('x', xValue), ('y', yValue), ('z', zValue)): for gc in graphClasses: if axisValue is None: continue diff --git a/music21/graph/primitives.py b/music21/graph/primitives.py index 8fec85c3f..815bd4546 100644 --- a/music21/graph/primitives.py +++ b/music21/graph/primitives.py @@ -1104,7 +1104,7 @@ def renderSubplot(self, subplot): # for x in range(int(math.floor(xMin)), # round(math.ceil(xMax)), # rangeStep): - # xTicks.append([x, '%s' % x]) + # xTicks.append([x, f'{x}']) # self.setTicks('x', xTicks) # environLocal.printDebug(['xTicks', xTicks]) @@ -1430,7 +1430,7 @@ def renderSubplot(self, subplot): rects = [] for i in range(barsPerGroup): yVals = [] - for j, x in enumerate(xVals): + for j, _x in enumerate(xVals): # get position, then get bar group yVals.append(yBundles[j][i]) xValsShifted = [] diff --git a/music21/harmony.py b/music21/harmony.py index 840c6293e..ac6cafdbb 100644 --- a/music21/harmony.py +++ b/music21/harmony.py @@ -1429,7 +1429,7 @@ def removeChordSymbols(chordType): class ChordSymbol(Harmony): # noinspection SpellCheckingInspection - ''' + r''' Class representing the Chord Symbols commonly found on lead sheets. Chord Symbol objects can be instantiated one of two main ways: @@ -1485,7 +1485,8 @@ class ChordSymbol(Harmony): ... chordSymbolName = 'C' + s ... h = harmony.ChordSymbol(chordSymbolName) ... pitchNames = [str(p) for p in h.pitches] - ... print('%-10s%s' % (chordSymbolName, '[' + (', '.join(pitchNames)) + ']')) + ... print(f'{chordSymbolName:<10}', end='[') + ... print(*pitchNames, sep=', ', end=']\n') C [C3, E3, G3] Cm [C3, E-3, G3] C+ [C3, E3, G#3] diff --git a/music21/humdrum/__init__.py b/music21/humdrum/__init__.py index 9bd6a2dc3..28bdfe67e 100644 --- a/music21/humdrum/__init__.py +++ b/music21/humdrum/__init__.py @@ -14,10 +14,8 @@ people who have previously used humdrum or need to import humdrum data (in spines, etc.) into music21. - Humdrum programs and their closest music21 equivalents: - ============ ================================================= ========================================================================================================================================================================= Humdrum music21 notes ============ ================================================= ========================================================================================================================================================================= diff --git a/music21/humdrum/instruments.py b/music21/humdrum/instruments.py index 6136f4485..4f0e89389 100644 --- a/music21/humdrum/instruments.py +++ b/music21/humdrum/instruments.py @@ -241,9 +241,10 @@ def fromHumdrumClass(hdClass): i = humdrumInstrumentClassToInstrument[hdClass] iObj = getattr(instrument, i)() return iObj - except: + except (IndexError, KeyError, AttributeError) as exc: raise HumdrumInstrumentException( - f'Cannot get an instrument from this humdrum class *IC{hdClass}') + f'Cannot get an instrument from this humdrum class *IC{hdClass}' + ) from exc def fromHumdrumInstrument(hdInst): @@ -259,9 +260,10 @@ def fromHumdrumInstrument(hdInst): i = humdrumInstruments[hdInst] iObj = getattr(instrument, i)() return iObj - except: + except (IndexError, KeyError, AttributeError) as exc: raise HumdrumInstrumentException( - f'Cannot get an instrument from this humdrum class: *I{hdInst}') + f'Cannot get an instrument from this humdrum class: *I{hdInst}' + ) from exc class Test(unittest.TestCase): diff --git a/music21/humdrum/spineParser.py b/music21/humdrum/spineParser.py index 3aeef6790..782105ee3 100644 --- a/music21/humdrum/spineParser.py +++ b/music21/humdrum/spineParser.py @@ -677,7 +677,7 @@ def createHumdrumSpines(self, protoSpines=None, eventCollections=None): if exchangeActive is not False: raise HumdrumException('ProtoSpine found with unpaired exchange instruction ' - + f'at line {i} [{thisEventCollection.events}]') + f'at line {i} [{thisEventCollection.events}]') currentSpineList = newSpineList return spineCollection @@ -920,10 +920,10 @@ def __init__(self, position: int = 0, contents: str = '!!! NUL: None'): value = value.strip() if code is None: raise HumdrumException('GlobalReferenceLine (!!!) found without a code ' - + f'listed; this is probably a problem! {contents} ') + f'listed; this is probably a problem! {contents} ') except IndexError: # pragma: no cover raise HumdrumException('GlobalReferenceLine (!!!) found without a code listed; ' - + f'this is probably a problem! {contents} ') + f'this is probably a problem! {contents} ') self.contents = contents self.code = code @@ -1096,7 +1096,7 @@ def __next__(self): Returns the current event and increments the iteration index. ''' if self.iterIndex == len(self.eventList): - raise StopIteration + raise StopIteration() thisEvent = self.eventList[self.iterIndex] self.iterIndex += 1 return thisEvent @@ -1627,7 +1627,7 @@ def __next__(self): Returns the current spine and decrements the iteration index. ''' if self.iterIndex < 0: - raise StopIteration + raise StopIteration() thisSpine = self.spines[self.iterIndex] self.iterIndex -= 1 return thisSpine @@ -2937,7 +2937,7 @@ def testSpineMazurka(self): hf1.parse() masterStream = hf1.stream # for spineX in hf1.spineCollection: - # spineX.stream.id = 'spine %s' % str(spineX.id) + # spineX.stream.id = f'spine {spineX.id}' # masterStream.append(spineX.stream) # self.assertTrue(common.whitespaceEqual # (common.stripAddresses(expectedOutput), diff --git a/music21/interval.py b/music21/interval.py index 5864145bb..c3b7798fa 100644 --- a/music21/interval.py +++ b/music21/interval.py @@ -1281,7 +1281,7 @@ def mod7inversion(self) -> int: ''' Return the inversion of this interval within an octave. For instance, seconds become sevenths, octaves become unisons, - and vice-versa. + and vice versa. All are undirected intervals. @@ -2083,7 +2083,7 @@ def reverse(self) -> DiatonicInterval: 'A-5' (Ascending) Augmented Unisons reverse to (Descending) - Diminished Unisons and vice-versa + Diminished Unisons and vice versa >>> aug1 = interval.DiatonicInterval('augmented', 1) >>> aug1.direction @@ -3500,7 +3500,7 @@ def _diatonicTransposePitch(self, # pitchAlt.ps = pitch2.ps + halfStepsToFix # environLocal.printDebug( # 'coercing pitch due to a transposition that requires an extreme ' + - # 'accidental: %s -> %s' % (pitch2, pitchAlt) ) + # f'accidental: {pitch2} -> {pitchAlt}') # pitch2 = pitchAlt pitch2.ps = pitch2.ps + halfStepsToFix else: @@ -3962,8 +3962,9 @@ def transposePitch( else: if not hasattr(interval1, 'transposePitch'): raise IntervalException( - 'interval must be a music21.interval.Interval object not {}'.format( - interval1.__class__.__name__)) + 'interval must be a music21.interval.Interval object not ' + + interval1.__class__.__name__ + ) return interval1.transposePitch(pitch1, inPlace=inPlace) diff --git a/music21/key.py b/music21/key.py index 3fdbf229e..895eb8b89 100644 --- a/music21/key.py +++ b/music21/key.py @@ -431,7 +431,7 @@ def asKey(self, mode: str|None = None, tonic: str|None = None): ''' our_sharps = self.sharps or 0 # || 0 in case of None -- non-standard key-signature if mode is not None and tonic is not None: - warnings.warn(f'ignoring provided tonic: {tonic}', KeyWarning) + warnings.warn(f'ignoring provided tonic: {tonic}', KeyWarning, stacklevel=2) if mode is None and tonic is None: mode = 'major' if mode is None and tonic is not None: diff --git a/music21/layout.py b/music21/layout.py index 3f83def75..85ad5dfac 100644 --- a/music21/layout.py +++ b/music21/layout.py @@ -381,7 +381,7 @@ def __init__(self, def _reprInternal(self): return (f'distance {self.distance!r}, staffNumber {self.staffNumber!r}, ' - + f'staffSize {self.staffSize!r}, staffLines {self.staffLines!r}') + f'staffSize {self.staffSize!r}, staffLines {self.staffLines!r}') # ------------------------------------------------------------------------------ @@ -1593,10 +1593,12 @@ def __init__(self, givenElements=None, **keywords): self.staffLayout = None def _reprInternal(self): - return '{0}: p.{1}, sys.{2}, st.{3}'.format(self.scoreStaffNumber, - self.pageNumber, - self.pageSystemNumber, - self.staffNumber) + return ( + f'{self.scoreStaffNumber}: ' + f'p.{self.pageNumber}, ' + f'sys.{self.pageSystemNumber}, ' + f'st.{self.staffNumber}' + ) _DOC_ORDER = [ScoreLayout, PageLayout, SystemLayout, StaffLayout, LayoutBase, diff --git a/music21/lily/lilyObjects.py b/music21/lily/lilyObjects.py index e9281d923..0be88e3e7 100644 --- a/music21/lily/lilyObjects.py +++ b/music21/lily/lilyObjects.py @@ -143,10 +143,10 @@ def setAttributes(self, m21Object): foundClass = True break - if foundClass is False: # pragma: no cover + if not foundClass: # pragma: no cover raise LilyObjectsException( 'Could not support setting attributes from ' - + f'{m21Object}: supported classes: {self.supportedClasses}') + f'{m21Object}: supported classes: {self.supportedClasses}') return attrs def setAttributesFromClassObject(self, classLookup, m21Object): @@ -188,8 +188,9 @@ def setAttributesFromClassObject(self, classLookup, m21Object): if classLookup not in self.m21toLy: # pragma: no cover raise LilyObjectsException( 'Could not support setting attributes from ' - + f'{m21Object} error in self.m21toLy,' - + ' missing class definitions and no "*"') + f'{m21Object} error in self.m21toLy,' + ' missing class definitions and no "*"' + ) classDict = self.m21toLy[classLookup] for m21Attribute in classDict: try: diff --git a/music21/lily/translate.py b/music21/lily/translate.py index 4e8c23745..ec06edd07 100644 --- a/music21/lily/translate.py +++ b/music21/lily/translate.py @@ -313,7 +313,7 @@ def loadFromMusic21Object(self, m21ObjectIn): scoreObj = stream.Score() scoreObj.insert(0, m21ObjectIn) self.loadObjectFromScore(scoreObj, makeNotation=False) - # raise LilyTranslateException('Unknown stream type %s.' % (m21ObjectIn.__class__)) + # raise LilyTranslateException(f'Unknown stream type {m21ObjectIn.__class__}') def loadObjectFromOpus(self, opusIn=None, makeNotation=True): r''' @@ -597,7 +597,7 @@ def getLySpacersFromStream(self, streamIn, measuresOnly=True): dur = str(self.lyMultipliedDurationFromDuration(el.duration)) returnString = returnString + 's' + dur # general exception is the only way to catch str exceptions - except: # pylint: disable=bare-except + except Exception: # pylint: disable=broad-exception-caught for c in el.duration.components: dur = str(self.lyMultipliedDurationFromDuration(c)) returnString = returnString + 's' + dur @@ -1088,8 +1088,6 @@ def appendM21ObjectToContext(self, thisObject): lyObject = self.lyPrefixCompositeMusicFromStream(thisObject) currentMusicList.append(lyObject) lyObject.setParent(contextObject) - # except AttributeError as ae: - # raise ValueError('Cannot parse %s: %s' % (thisObject, str(ae))) elif 'Note' in c or 'Rest' in c: self.appendContextFromNoteOrRest(thisObject) elif 'Chord' in c: @@ -2197,18 +2195,19 @@ def lyPrefixCompositeMusicFromVariant(self, simpleString='Staff', music=lpOssiaMusicVariantWithSpacer) - # optionalContextMod = r''' + # optionalContextMod = rf''' # \with { # \remove "Time_signature_engraver" - # alignAboveContext = #"%s" + # alignAboveContext = #"{containerId}" # fontSize = #-3 # \override StaffSymbol.staff-space = #(magstep -3) # \override StaffSymbol.thickness = #(magstep -3) # \override TupletBracket.bracket-visibility = ##f # \override TupletNumber.stencil = ##f # \override Clef.transparent = ##t + # \override BarLine.transparent = ##t # } - # ''' % containerId #\override BarLine.transparent = ##t + # ''' # # is the best way of fixing the #barlines that I have come up with. # lpPrefixCompositeMusicVariant.optionalContextMod = optionalContextMod diff --git a/music21/metadata/__init__.py b/music21/metadata/__init__.py index 46deee800..c325113ee 100755 --- a/music21/metadata/__init__.py +++ b/music21/metadata/__init__.py @@ -134,6 +134,21 @@ 'Metadata', 'RichMetadata', + 'Contributor', + 'Copyright', + 'Creator', + 'Date', + 'DateBetween', + 'DatePrimitive', + 'DateRelative', + 'DateSelection', + 'DateSingle', + 'Imprint', + 'Text', + 'ValueType', + + 'PropertyDescription', + 'bundles', 'caching', 'primitives', @@ -656,7 +671,7 @@ def copyright(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'copyright', value) + self.copyright = value # SPECIAL METHODS # def all( @@ -1287,7 +1302,7 @@ def alternativeTitle(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'alternativeTitle', value) + self.alternativeTitle = value @property def composer(self): @@ -1321,7 +1336,7 @@ def composer(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'composer', value) + self.composer = value @property def composers(self): @@ -1357,7 +1372,7 @@ def composers(self, value: Iterable[str]) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'composers', value) + self.composers = value @property def dateCreated(self): @@ -1390,7 +1405,7 @@ def dateCreated(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'dateCreated', value) + self.dateCreated = value @property def fileFormat(self) -> str|None: @@ -1404,7 +1419,7 @@ def fileFormat(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'fileFormat', value) + self.fileFormat = value @property def filePath(self) -> str|None: @@ -1419,7 +1434,7 @@ def filePath(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'filePath', value) + self.filePath = value @property def corpusFilePath(self) -> str|None: @@ -1433,7 +1448,7 @@ def corpusFilePath(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'corpusFilePath', value) + self.corpusFilePath = value @property def fileNumber(self) -> str|None: @@ -1447,7 +1462,7 @@ def fileNumber(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'fileNumber', value) + self.fileNumber = value @property def localeOfComposition(self): @@ -1466,7 +1481,7 @@ def localeOfComposition(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'localeOfComposition', value) + self.localeOfComposition = value @property def librettist(self): @@ -1489,7 +1504,7 @@ def librettist(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'librettist', value) + self.librettist = value @property def librettists(self): @@ -1510,7 +1525,7 @@ def librettists(self, value: Iterable[str]) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'librettists', value) + self.librettists = value @property def lyricist(self): @@ -1536,7 +1551,7 @@ def lyricist(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'lyricist', value) + self.lyricist = value @property def lyricists(self): @@ -1557,7 +1572,7 @@ def lyricists(self, value: Iterable[str]) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'lyricists', value) + self.lyricists = value @property def movementName(self): @@ -1580,7 +1595,7 @@ def movementName(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'movementName', value) + self.movementName = value @property def movementNumber(self) -> str|None: @@ -1603,7 +1618,7 @@ def movementNumber(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'movementNumber', value) + self.movementNumber = value @property def number(self) -> str|None: @@ -1633,7 +1648,7 @@ def number(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'number', value) + self.number = value @property def opusNumber(self) -> str|None: @@ -1663,7 +1678,7 @@ def opusNumber(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'opusNumber', value) + self.opusNumber = value @property def title(self): @@ -1685,7 +1700,7 @@ def title(self, value: str) -> None: ''' For type checking only. Does not run. ''' - setattr(self, 'title', value) + self.title = value @property def bestTitle(self) -> str|None: diff --git a/music21/metadata/bundles.py b/music21/metadata/bundles.py index d04fabbd7..dd37a5083 100644 --- a/music21/metadata/bundles.py +++ b/music21/metadata/bundles.py @@ -1116,9 +1116,10 @@ def read(self, filePath=None): filePath = pathlib.Path(filePath) if not filePath.exists(): - environLocal.printDebug('no metadata found for: {0!r}; ' - 'try building cache with corpus.cacheMetadata({1!r})'.format( - self.name, self.name)) + environLocal.printDebug( + f'no metadata found for: {self.name!r}; ' + f'try building cache with corpus.cacheMetadata({self.name!r})' + ) return self newMdb = readPickleGzip(filePath) diff --git a/music21/metadata/caching.py b/music21/metadata/caching.py index 33b3f4bd8..01b4efe1b 100644 --- a/music21/metadata/caching.py +++ b/music21/metadata/caching.py @@ -157,8 +157,8 @@ def parseNonOpus(self, parsedObject): else: environLocal.printDebug( 'addFromPaths: got stream without metadata, ' - 'creating stub: {0}'.format( - common.relativepath(str(self.cleanFilePath)))) + f'creating stub: {common.relativepath(str(self.cleanFilePath))}' + ) metadataEntry = metadata.bundles.MetadataEntry( sourcePath=self.cleanFilePath, metadataPayload=None, @@ -166,8 +166,10 @@ def parseNonOpus(self, parsedObject): ) self.results.append(metadataEntry) except Exception: # wide catch is fine. pylint: disable=broad-exception-caught - environLocal.warn('Had a problem with extracting metadata ' - 'for {0}, piece ignored'.format(self.filePath)) + environLocal.warn( + 'Had a problem with extracting metadata ' + f'for {self.filePath}, piece ignored' + ) environLocal.warn(traceback.format_exc()) def parseOpus(self, parsedObject): @@ -182,9 +184,10 @@ def parseOpus(self, parsedObject): del score # for memory conservation except Exception as exception: # wide catch is fine. pylint: disable=broad-exception-caught environLocal.warn( - 'Had a problem with extracting metadata for score {0} ' - 'in {1}, whole opus ignored: {2}'.format( - scoreNumber, self.filePath, str(exception))) + 'Had a problem with extracting metadata for score ' + f'{scoreNumber} in {self.filePath}, whole opus ignored: ' + f'{exception}' + ) environLocal.printDebug(traceback.format_exc()) # Create a dummy metadata entry, representing the entire opus. # This lets the metadata bundle know it has already processed this @@ -208,8 +211,9 @@ def parseScoreInsideOpus(self, score, scoreNumber): if score.metadata is None or score.metadata.number is None: environLocal.printDebug( 'addFromPaths: got Opus that contains ' - 'Streams that do not have work numbers: ' - '{0}'.format(self.filePath)) + + 'Streams that do not have work numbers: ' + + str(self.filePath) + ) else: # update path to include work number corpusPath = metadata.bundles.MetadataBundle.corpusPathToKey( @@ -226,10 +230,9 @@ def parseScoreInsideOpus(self, score, scoreNumber): self.results.append(metadataEntry) except Exception as exception: # pylint: disable=broad-exception-caught environLocal.warn( - 'Had a problem with extracting metadata ' - 'for score {0} in {1}, whole opus ignored: ' - '{2}'.format( - scoreNumber, self.filePath, str(exception))) + f'Had a problem with extracting metadata for score {scoreNumber} ' + f'in {self.filePath}, whole opus ignored: {exception}' + ) environLocal.printDebug(traceback.format_exc()) # PUBLIC METHODS # @@ -294,11 +297,11 @@ def _report(totalJobs, remainingJobs, filePath, filePathErrorCount): ''' Report on the current job status. ''' - message = 'updated {0} of {1} files; total errors: {2} ... last file: {3}'.format( - totalJobs - remainingJobs, - totalJobs, - filePathErrorCount, - filePath, + completedJobs = totalJobs - remainingJobs + message = ( + f'updated {completedJobs} of {totalJobs} files; ' + f'total errors: {filePathErrorCount} ... ' + f'last file: {filePath}' ) return message @@ -344,7 +347,7 @@ def process_parallel(jobs, processCount=None): 'filePath': job.filePath, 'remainingJobs': remainingJobs, } - for worker in workers: + for _worker in workers: job_queue.put(None) job_queue.join() result_queue.close() diff --git a/music21/meter/base.py b/music21/meter/base.py index 848a9f944..b08158e00 100644 --- a/music21/meter/base.py +++ b/music21/meter/base.py @@ -1242,7 +1242,7 @@ def _setDefaultBeamPartitions(self) -> None: self.beamSequence.partition([3] * int(self.numerator / 3)) else: pass # doing nothing will beam all together - # environLocal.printDebug('default beam partitions set to: %s' % self.beamSequence) + # environLocal.printDebug(f'default beam partitions set to: {self.beamSequence}') def _setDefaultAccentWeights(self, depth: int = 3) -> None: ''' @@ -1999,8 +1999,9 @@ def getOffsetFromBeat(self, beat): if beatInt - 1 > len(self.beatSequence) - 1: raise TimeSignatureException( - 'requested beat value (%s) not found in beat partitions (%s) of ts %s' % ( - beatInt, self.beatSequence, self)) + f'requested beat value ({beatInt}) not found in beat partitions ' + f'({self.beatSequence}) of ts {self}' + ) # get a duration object for the beat; will translate into quarterLength # beat int counts from 1; subtract 1 to get index beatDur = self.beatSequence[beatInt - 1].duration diff --git a/music21/meter/core.py b/music21/meter/core.py index 49e8522a3..6fb86efb1 100644 --- a/music21/meter/core.py +++ b/music21/meter/core.py @@ -524,7 +524,7 @@ def __setitem__(self, key: int, value: MeterTerminal): # comparison of numerator and denominator if not isinstance(value, MeterTerminal): raise MeterException('values in MeterSequences must be MeterTerminals or ' - + f'MeterSequences, not {value}') + f'MeterSequences, not {value}') if value.ratioEqual(self[key]): self._partition[key] = value else: @@ -588,7 +588,7 @@ def _addTerminal(self, value: MeterTerminal|str) -> None: # elif isinstance(value, MeterTerminal): # may be a MeterSequence # mt = value # else: - # raise MeterException('cannot add %s to this sequence' % value) + # raise MeterException(f'cannot add {value} to this sequence') self._partition.append(mt) # clear cache self._levelListCache = {} @@ -1271,7 +1271,7 @@ def load(self, elif common.isIterable(value): # a list of Terminals or Sequence es for obj in value: - # environLocal.printDebug('creating MeterSequence with %s' % obj) + # environLocal.printDebug(f'creating MeterSequence with {obj}') self._addTerminal(obj) self._updateRatio() if targetWeight is not None: @@ -1371,12 +1371,12 @@ def weight(self, value: int | float) -> None: try: totalRatio = self._numerator / self._denominator - except TypeError: + except TypeError as te: raise MeterException( 'Something wrong with the type of ' - + 'this numerator %s %s or this denominator %s %s' % - (self._numerator, type(self._numerator), - self._denominator, type(self._denominator))) + f'this numerator {self._numerator} {type(self._numerator)} ' + f'or this denominator {self._denominator} {type(self._denominator)}' + ) from te for mt in self._partition: # for mt in self: @@ -1683,8 +1683,9 @@ def getLevelList(self, levelCount: int, flat: bool = True) -> list[MeterTerminal levelCount - 1, flat) else: # level count is at zero if flat: # make sequence into a terminal - mt = MeterTerminal('%s/%s' % ( - partition_i.numerator, partition_i.denominator)) + mt = MeterTerminal( + f'{partition_i.numerator}/{partition_i.denominator}' + ) # set weight to that of the sequence mt.weight = partition_i.weight mtList.append(mt) @@ -1855,7 +1856,7 @@ def offsetToIndex(self, qLenPos, includeCoincidentBoundaries=False) -> int: if qLenPos >= self.duration.quarterLength or qLenPos < 0: raise MeterException( f'cannot access from qLenPos {qLenPos} ' - + f'where total duration is {self.duration.quarterLength}' + f'where total duration is {self.duration.quarterLength}' ) qPos = 0 @@ -1969,8 +1970,9 @@ def offsetToSpan(self, qLenPos, permitMeterModulus=False): # environLocal.printDebug(['exceeding range:', self, # 'self.duration', self.duration]) raise MeterException( - 'cannot access qLenPos %s when total duration is %s and ts is %s' % ( - qLenPos, self.duration.quarterLength, self)) + f'cannot access qLenPos {qLenPos} when total duration is ' + f'{self.duration.quarterLength} and ts is {self}' + ) # environLocal.printDebug(['offsetToSpan', 'got qLenPos old', qLenPos]) qLenPos = qLenPos % self.duration.quarterLength @@ -2006,8 +2008,9 @@ def offsetToWeight(self, qLenPos): qLenPos = opFrac(qLenPos) if qLenPos >= self.duration.quarterLength or qLenPos < 0: raise MeterException( - 'cannot access qLenPos %s when total duration is %s and ts is %s' % ( - qLenPos, self.duration.quarterLength, self)) + f'cannot access qLenPos {qLenPos} when total duration is ' + f'{self.duration.quarterLength} and ts is {self}' + ) iMatch = self.offsetToIndex(qLenPos) return opFrac(self[iMatch].weight) diff --git a/music21/meter/tests.py b/music21/meter/tests.py index a3c0b8597..7256b6b7f 100644 --- a/music21/meter/tests.py +++ b/music21/meter/tests.py @@ -51,8 +51,9 @@ def testCompound(self): for i in range(8): msg = [] for j in range(1, random.choice([2, 4])): - msg.append('%s/%s' % (random.choice(meterStrNumerator), - random.choice(meterStrDenominator))) + numerator = random.choice(meterStrNumerator) + denominator = random.choice(meterStrDenominator) + msg.append(f'{numerator}/{denominator}') ts = TimeSignature('+'.join(msg)) m = stream.Measure() m.timeSignature = ts diff --git a/music21/meter/tools.py b/music21/meter/tools.py index 014abbb65..e7bc7126b 100644 --- a/music21/meter/tools.py +++ b/music21/meter/tools.py @@ -156,10 +156,10 @@ def slashMixedToFraction(valueSrc: str) -> tuple[NumDenomTuple, bool]: except ValueError: raise Music21Exception( 'Cannot parse this file -- this error often comes ' - + 'up if the musicxml pickled file is out of date after a change ' - + 'in musicxml/__init__.py . ' - + 'Clear your temp directory of .p and .p.gz files and try again. ' - + f'Time Signature: {valueSrc} ') + 'up if the musicxml pickled file is out of date after a change ' + 'in musicxml/__init__.py . ' + 'Clear your temp directory of .p and .p.gz files and try again. ' + f'Time Signature: {valueSrc} ') post: list[NumDenom] = [] # when encountering a missing denominator, find the first defined diff --git a/music21/midi/base.py b/music21/midi/base.py index 3b14b3f5b..7721b09b8 100644 --- a/music21/midi/base.py +++ b/music21/midi/base.py @@ -13,7 +13,7 @@ ''' Objects and tools for processing MIDI data. Converts from MIDI files to :class:`~music21.midi.MidiEvent`, :class:`~music21.midi.MidiTrack`, and -:class:`~music21.midi.MidiFile` objects, and vice-versa. +:class:`~music21.midi.MidiFile` objects, and vice versa. This module originally used routines from Will Ware's public domain midi.py library from 2001 which was once posted at diff --git a/music21/midi/tests.py b/music21/midi/tests.py index 554f315d9..8ddbdd38a 100644 --- a/music21/midi/tests.py +++ b/music21/midi/tests.py @@ -1273,10 +1273,13 @@ def testImportChordVoiceA(self): # looking at cases where notes appear to be chord but # are better seen as voices # specialized problem of not importing last notes + + # voice 1 -- m1 (half E G) m2 (F# B) m3 -- Chord C4C5 (zero duration?) + # voice 2 -- m1 whole C m2 whole D -- m3 (not existent) dirLib = common.getSourceFilePath() / 'midi' / 'testPrimitive' fp = dirLib / 'test13.mid' s = converter.parse(fp) - # s.show('t') + # s.show('text', addEndTimes=True) self.assertEqual(len(s.flatten().notes), 7) # s.show('midi') @@ -1292,7 +1295,7 @@ def testImportChordsA(self): # a simple file created in athenacl s = converter.parse(fp) - # s.show('t') + # s.show('text') self.assertEqual(len(s[chord.Chord]), 5) def testMidiEventsImported(self): @@ -1564,8 +1567,8 @@ def testMidiImportLyrics(self): for filename, encoding, lyricFact in testCases: fp = common.getSourceFilePath() / 'midi' / 'testPrimitive' / filename s = converter.parse(fp, encoding=encoding) - for (n, l) in zip(s.flatten().notes, lyricFact): - self.assertEqual(n.lyric, l) + for (n, lyricStr) in zip(s.flatten().notes, lyricFact): + self.assertEqual(n.lyric, lyricStr) def testMidiExportLyrics(self): lyricEn = 'cat' # ascii characters should be supported by every encoding diff --git a/music21/midi/translate.py b/music21/midi/translate.py index 585a11723..6e6a13965 100644 --- a/music21/midi/translate.py +++ b/music21/midi/translate.py @@ -318,7 +318,6 @@ def music21ObjectToMidiFile( # Notes def _constructOrUpdateNotRestSubclass( - eOn: MidiEvent, tOn: int, tOff: int, ticksPerQuarter: int, @@ -329,11 +328,9 @@ def _constructOrUpdateNotRestSubclass( Construct (or edit the duration of) a NotRest subclass, usually a note.Note (or a chord.Chord if provided to `returnClass`). - If the MidiEvent is on channel 10, then an Unpitched or PercussionChord - is constructed instead. Raises TypeError if an incompatible class is provided - for returnClass. - * Changed in v8: no inputM21 + * Changed in v10: Remove part about creating an Unpitched object -- that has been wrong + for some time (probably since PercussionChords were introduced) ''' if not issubclass(returnClass, note.NotRest): raise TypeError(f'Expected subclass of note.NotRest; got {returnClass}') @@ -421,16 +418,15 @@ def midiEventsToNote( The only supported usage now is two tuples. * Changed in v9.7: Expects a single TimedNoteEvent ''' - tOn, tOff, eOn = timedNoteEvent + tOn, tOff, midiOnEvent = timedNoteEvent returnClass: type[note.Unpitched]|type[note.Note] - if eOn.channel == 10: + if midiOnEvent.channel == 10: returnClass = note.Unpitched else: returnClass = note.Note nr = _constructOrUpdateNotRestSubclass( - eOn, tOn, tOff, ticksPerQuarter, @@ -438,12 +434,11 @@ def midiEventsToNote( ) if isinstance(nr, note.Note): - nr.pitch.midi = eOn.pitch + nr.pitch.midi = midiOnEvent.pitch elif isinstance(nr, note.Unpitched): try: - i = PERCUSSION_MAPPER.midiPitchToInstrument(eOn.pitch) + i = PERCUSSION_MAPPER.midiPitchToInstrument(midiOnEvent.pitch) except MIDIPercussionException: - # warnings.warn(str(mpe), TranslateWarning) i = instrument.UnpitchedPercussion() nr.storedInstrument = i # TODO: set reasonable displayPitch? @@ -452,9 +447,8 @@ def midiEventsToNote( f'Got something other than a Note or Unpitched from conversion: {nr}' ) - nr.volume.velocity = eOn.velocity + nr.volume.velocity = midiOnEvent.velocity nr.volume.velocityIsRelative = False # not relative coming from MIDI - # n._midiVelocity = eOn.velocity return t.cast(note.Note|note.Unpitched, nr) @@ -637,24 +631,27 @@ def midiEventsToChord( * Changed in v8: inputM21 is no longer supported. Flat list format is removed. * Changed in v9.7: expects a list of TimedNoteEvents ''' - tOn: int = 0 # ticks - tOff: int = 0 # ticks + # * Changed in v10: Fix long-standing bug where only the first on-time was being used + # not yet -- must fix quantization tests to do so... + # THIS IS A BUG -- should be timedNoteList[0] + # fixing it breaks stream/tests/testQuantizeMinimumDuration though, so a separate PR + first_tOn: int = timedNoteList[-1][0] # ticks + last_tOff: int = timedNoteList[-1][1] # ticks pitches: list[pitch.Pitch] = [] - volumes = [] + volumes: list[volume.Volume] = [] - firstOn: MidiEvent = timedNoteList[0].event any_channel_10 = False # this is a format provided by the Stream conversion of # midi events; it pre-groups events for a chord together in nested pairs # of abs start time and the event object - for tOn, tOff, eOn in timedNoteList: - if eOn.channel == 10: + for _tOn, _tOff, midiOnEvent in timedNoteList: + if midiOnEvent.channel == 10: any_channel_10 = True p = pitch.Pitch() - p.midi = eOn.pitch + p.midi = midiOnEvent.pitch pitches.append(p) - v = volume.Volume(velocity=eOn.velocity) + v = volume.Volume(velocity=midiOnEvent.velocity) v.velocityIsRelative = False # velocity is absolute coming from MIDI volumes.append(v) @@ -665,9 +662,8 @@ def midiEventsToChord( returnClass = chord.Chord c = _constructOrUpdateNotRestSubclass( - firstOn, - tOn, - tOff, + first_tOn, + last_tOff, ticksPerQuarter, returnClass=returnClass ) @@ -679,7 +675,6 @@ def midiEventsToChord( try: i = PERCUSSION_MAPPER.midiPitchToInstrument(midi_pitch) except MIDIPercussionException: - # warnings.warn(str(mpe), TranslateWarning) i = instrument.UnpitchedPercussion() unp.storedInstrument = i c.add(unp) @@ -938,7 +933,9 @@ def midiEventToInstrument( except UnicodeDecodeError: warnings.warn( f'Unable to determine instrument from {event}; getting generic Instrument', - TranslateWarning) + TranslateWarning, + stacklevel=2, + ) i = instrument.Instrument() except instrument.InstrumentException: # Debug logging would be better than warning here @@ -1059,7 +1056,8 @@ def timeSignatureToMidiEvents(ts, includeDeltaTime=True) -> list[DeltaTime|MidiE if n > 255: warnings.warn( f'TimeSignature with numerator > 255 cannot be stored in MIDI. Ignoring {ts}', - TranslateWarning + TranslateWarning, + stacklevel=2 ) return [] @@ -1675,7 +1673,7 @@ def assignPacketsToChannels( # after processing, collect all channels used foundChannels = [] - for start, stop, usedChannel in list(uniqueChannelEvents): # a list + for _start, _stop, usedChannel in list(uniqueChannelEvents): if usedChannel not in foundChannels: foundChannels.append(usedChannel) # for ch in chList: @@ -1958,7 +1956,9 @@ def lyricTimingsFromEvents( except UnicodeDecodeError: warnings.warn( f'Unable to decode lyrics from {e} as {encoding}', - TranslateWarning) + TranslateWarning, + stacklevel=2, + ) return lyrics @@ -2460,7 +2460,8 @@ def channelInstrumentData( f'{inst} specified 1-indexed MIDI channel {thisChannel} ' f'but acceptable channels were {acceptableChannels}. ' 'Defaulting to channel 1.', - TranslateWarning) + TranslateWarning, + stacklevel=2) thisChannel = 1 channelsAssigned.add(thisChannel) channelByInstrument[inst.midiProgram] = thisChannel diff --git a/music21/musedata/__init__.py b/music21/musedata/__init__.py index d2226e415..0211863ed 100644 --- a/music21/musedata/__init__.py +++ b/music21/musedata/__init__.py @@ -602,7 +602,7 @@ def __iter__(self): def __next__(self): if self.index >= len(self.src): - raise StopIteration + raise StopIteration() # add one b/c end is inclusive mdr = MuseDataRecord(self.src[self.index], self.parent) self.index += 1 @@ -763,7 +763,7 @@ def __iter__(self): def __next__(self): if self.index >= len(self.boundaries): - raise StopIteration + raise StopIteration() start, end = self.boundaries[self.index] # add one b/c end is inclusive mdm = MuseDataMeasure(self.src[start:end + 1], self.parent) @@ -1584,7 +1584,7 @@ def addFile(self, fp: str | Iterable[str]) -> None: for fpInner in fpList: mdf = MuseDataFile() mdf.encoding = self.encoding - # environLocal.printDebug('processing MuseData file: %s' % fp) + # environLocal.printDebug(f'processing MuseData file: {fp}') mdf.open(fpInner) mdf.read() # process string and break into parts mdf.close() diff --git a/music21/musicxml/m21ToXml.py b/music21/musicxml/m21ToXml.py index 96d0caa97..a5016a7e3 100644 --- a/music21/musicxml/m21ToXml.py +++ b/music21/musicxml/m21ToXml.py @@ -413,7 +413,7 @@ def fromGeneralObject(self, obj: prebase.ProtoM21Object): if outObj is None: raise MusicXMLExportException( 'Cannot translate the object ' - + f'{self.generalObj} to a complete musicXML document; put it in a Stream first!' + f'{self.generalObj} to a complete musicXML document; put it in a Stream first!' ) return outObj @@ -424,8 +424,11 @@ def fromScore(self, sc): if self.makeNotation: sc.makeNotation(inPlace=True) if not sc.isWellFormedNotation(): - warnings.warn(f'{sc} is not well-formed; see isWellFormedNotation()', - category=MusicXMLWarning) + warnings.warn( + f'{sc} is not well-formed; see isWellFormedNotation()', + category=MusicXMLWarning, + stacklevel=2, + ) # sc.makeImmutable() return sc @@ -2865,9 +2868,7 @@ def fixupNotationMeasured(self): try: part.makeBeams(inPlace=True) except exceptions21.StreamException as se: # no measures or no time sig? - # incorrectly flagging MusicXMLWarning as not a Warning - # noinspection PyTypeChecker - warnings.warn(MusicXMLWarning, str(se)) + warnings.warn(MusicXMLWarning(se), stacklevel=2) if not part.streamStatus.tuplets: for m in measures: for m_or_v in [m, *m.voices]: @@ -5400,7 +5401,7 @@ def articulationToXmlArticulation( break if musicXMLArticulationName is None: musicXMLArticulationName = 'other-articulation' - # raise MusicXMLExportException('Cannot translate %s to musicxml' % articulationMark) + # raise MusicXMLExportException(f'Cannot translate {articulationMark} to musicxml') mxArticulationMark = Element(musicXMLArticulationName) if articulationMark.placement is not None: mxArticulationMark.set('placement', articulationMark.placement) @@ -6164,7 +6165,7 @@ def dynamicToXml(self, d: dynamics.Dynamic) -> Element: or whatever: >>> ppp = dynamics.Dynamic('ppp') - >>> print('%.2f' % ppp.volumeScalar) + >>> print(f'{ppp.volumeScalar:.2f}') 0.15 >>> ppp.style.relativeY = -10 @@ -6905,8 +6906,9 @@ def beamToXml(self, beamObject): mxBeam.text = 'forward hook' else: raise MusicXMLExportException( - 'partial beam defined without a proper direction set (set to %s)' % - beamObject.direction) + 'partial beam defined without a proper direction set ' + f'(set to {beamObject.direction})' + ) else: raise MusicXMLExportException( f'unexpected beam type encountered ({beamObject.type})' diff --git a/music21/musicxml/partStaffExporter.py b/music21/musicxml/partStaffExporter.py index abe52486a..7f383ec3b 100644 --- a/music21/musicxml/partStaffExporter.py +++ b/music21/musicxml/partStaffExporter.py @@ -267,7 +267,11 @@ def joinableGroups(self) -> list[StaffGroup]: joinable_components_list = flattenList(deduplicatedGroups) if len(set(joinable_components_list)) != len(joinable_components_list): warnings.warn( - MusicXMLWarning('Got overlapping StaffGroups; will not merge ANY groups.')) + MusicXMLWarning( + 'Got overlapping StaffGroups; will not merge ANY groups.' + ), + stacklevel=2, + ) return [] # Finally, store a reference to earlier siblings (if any) on PartExporters diff --git a/music21/musicxml/test_m21ToXml.py b/music21/musicxml/test_m21ToXml.py index 545c476d2..5feb1ce27 100644 --- a/music21/musicxml/test_m21ToXml.py +++ b/music21/musicxml/test_m21ToXml.py @@ -1131,12 +1131,12 @@ def testSimple(self): with io.open(fp, encoding='utf-8') as f: v2 = f.read() differ = list(difflib.ndiff(v.splitlines(), v2.splitlines())) - for i, l in enumerate(differ): - if l.startswith('-') or l.startswith('?') or l.startswith('+'): - if 'id=' in l: + for i, line in enumerate(differ): + if line.startswith('-') or line.startswith('?') or line.startswith('+'): + if 'id=' in line: continue if self.show: - print(l) + print(line) # for j in range(i - 1,i + 1): # print(differ[j]) # print('------------------') diff --git a/music21/musicxml/xmlSoundParser.py b/music21/musicxml/xmlSoundParser.py index df9f47b94..56c0f2d33 100644 --- a/music21/musicxml/xmlSoundParser.py +++ b/music21/musicxml/xmlSoundParser.py @@ -107,7 +107,7 @@ def setSoundTempo( qpm = common.numToIntOrFloat(float(mxSound.get('tempo', 0))) if qpm == 0: - warnings.warn('0 qpm tempo tag found, skipping.') + warnings.warn('0 qpm tempo tag found, skipping.', stacklevel=2) return mm = tempo.MetronomeMark(referent=duration.Duration(type='quarter'), number=None, diff --git a/music21/musicxml/xmlToM21.py b/music21/musicxml/xmlToM21.py index fee5e107d..75288d8a0 100644 --- a/music21/musicxml/xmlToM21.py +++ b/music21/musicxml/xmlToM21.py @@ -1165,10 +1165,10 @@ def partGroups(self): staffGroup.addSpannedElements(self.m21PartObjectsById[partIdTest]) foundOne = True - if foundOne is False: + if not foundOne: raise MusicXMLImportException( 'Cannot find part in m21PartObjectsById dictionary by Id:' - + f' {ke} \n Full Dict:\n {self.m21PartObjectsById!r} ') + f' {ke} \n Full Dict:\n {self.m21PartObjectsById!r} ') mxPartGroup = pgObj.mxPartGroup seta(staffGroup, mxPartGroup, 'group-name', 'name') # TODO: group-name-display @@ -1732,14 +1732,14 @@ def _adjustMidiData(mc): i = pm.midiPitchToInstrument(_adjustMidiData(midiUnpitchedText)) except MIDIPercussionException as mpe: # objects not yet existing in m21 such as Cabasa - warnings.warn(MusicXMLWarning(mpe)) + warnings.warn(MusicXMLWarning(mpe), stacklevel=2) i = instrument.UnpitchedPercussion() i.percMapPitch = _adjustMidiData(midiUnpitchedText) elif midiProgramText := strippedText(mxMidiProgram): try: i = instrument.instrumentFromMidiProgram(_adjustMidiData(midiProgramText)) except instrument.InstrumentException as ie: - warnings.warn(MusicXMLWarning(ie)) + warnings.warn(MusicXMLWarning(ie), stacklevel=2) # Invalid MIDI program, out of range 0-127 i = instrument.Instrument() seta(i, mxMIDIInstrument, 'midi-channel', transform=_adjustMidiData) @@ -2020,7 +2020,8 @@ def xmlMeasureToMeasure(self, mxMeasure: ET.Element) -> stream.Measure: warnings.warn( f'The following exception took place in m. {measureParser.measureNumber} in ' + f'part {self.stream.partName}.', - MusicXMLWarning + MusicXMLWarning, + stacklevel=2 ) raise e @@ -2105,7 +2106,9 @@ def updateTransposition(self, newTransposition: interval.Interval): # and create a Generic Instrument object rather than dying. warnings.warn( 'Received a transposition tag, but no instrument to put it on!', - MusicXMLWarning) + MusicXMLWarning, + stacklevel=2, + ) fakeInst = instrument.Instrument() self.activeInstrument = fakeInst self.stream.coreInsert(self.lastMeasureOffset, fakeInst) @@ -2266,7 +2269,8 @@ def adjustTimeAttributesFromMeasure(self, m: stream.Measure): f'Warning: measure {m.number} in part {self.stream.partName}' f'is overfull: {mHighestTime} > {lastTimeSignatureQuarterLength},' f'assuming {mOffsetShift} is correct.', - MusicXMLWarning + MusicXMLWarning, + stacklevel=2, ) elif (mHighestTime == 0.0 and not m.recurse().notesAndRests.getElementsNotOfClass('Harmony') @@ -2732,7 +2736,7 @@ def hasSystemLayout(): addPageLayout = hasPageLayout() addSystemLayout = hasSystemLayout() - addStaffLayout = not (mxPrint.find('staff-layout') is None) + addStaffLayout = mxPrint.find('staff-layout') is not None # --- now we know what we need to add, add em m = self.stream @@ -4345,7 +4349,11 @@ def xmlDirectionTypeToSpanners( 'Line', idFound, False)[0] # get first except IndexError: - warnings.warn('Line <' + mxObj.tag + '> stop without start', MusicXMLWarning) + warnings.warn( + 'Line <' + mxObj.tag + '> stop without start', + MusicXMLWarning, + stacklevel=2, + ) return [] sp.completeStatus = True @@ -5045,7 +5053,7 @@ def findM21VoiceFromXmlVoice( if useVoice is None: # pragma: no cover warnings.warn('Cannot put in an element with a missing voice tag when ' + 'no previous voice tag was given. Assuming voice 1... ', - MusicXMLWarning) + MusicXMLWarning, stacklevel=2) useVoice = 1 thisVoice: stream.Voice|None = None @@ -5058,13 +5066,13 @@ def findM21VoiceFromXmlVoice( else: warnings.warn( f'Cannot find voice {useVoice!r}; putting outside of voices.', - MusicXMLWarning) + MusicXMLWarning, stacklevel=2) warnings.warn( f'Current voiceIds: {list(self.voicesById)}', - MusicXMLWarning) + MusicXMLWarning, stacklevel=2) warnings.warn( f'Current voices: {list(m.voices)} in m. {m.number}', - MusicXMLWarning) + MusicXMLWarning, stacklevel=2) return thisVoice @@ -5513,7 +5521,7 @@ def setDirectionInDirectionType( mxDir, staffKey, totalOffset ) except MusicXMLImportException as excep: - warnings.warn(f'Could not import {tag}: {excep}', MusicXMLWarning) + warnings.warn(f'Could not import {tag}: {excep}', MusicXMLWarning, stacklevel=2) spannerList = [] for sp in spannerList: @@ -6353,7 +6361,10 @@ def xmlStaffLayoutFromStaffDetails( stl.staffType = stream.enums.StaffType(xmlText) except ValueError: warnings.warn( - f'Got an incorrect staff-type in details: {mxStaffType}', MusicXMLWarning) + f'Got an incorrect staff-type in details: {mxStaffType}', + MusicXMLWarning, + stacklevel=2, + ) # TODO: staff-tuning* # TODO: capo seta(stl, mxDetails, 'staff-size', transform=_floatOrIntStr) diff --git a/music21/note.py b/music21/note.py index 7a56c69bf..c6dd5c1bc 100644 --- a/music21/note.py +++ b/music21/note.py @@ -351,7 +351,7 @@ def syllabic(self, newSyllabic: SyllabicChoices): if newSyllabic not in SYLLABIC_CHOICES: raise LyricException( f'Syllabic value {newSyllabic!r} is not in ' - + f'note.SYLLABIC_CHOICES, namely: {SYLLABIC_CHOICES}' + f'note.SYLLABIC_CHOICES, namely: {SYLLABIC_CHOICES}' ) self._syllabic = newSyllabic diff --git a/music21/noteworthy/translate.py b/music21/noteworthy/translate.py index 7666fb8d4..86abeb7f7 100644 --- a/music21/noteworthy/translate.py +++ b/music21/noteworthy/translate.py @@ -218,7 +218,7 @@ def parseList(self, dataList): elif command == 'TimeSig': self.createTimeSignature(attributes) except Exception as e: - print('Cannot create object from "%s"' % (attributes)) + print(f'Cannot create object from "{attributes}"') raise NoteworthyTranslateException from e # Add the last Stuff @@ -904,7 +904,7 @@ def createDynamicVariance(self, attributes): g = dynamics.Diminuendo() else: pass - # raise NoteworthyTranslateException('Cannot get style from %s' % str(attributes)) + # raise NoteworthyTranslateException(f'Cannot get style from {attributes}') if g is not None: self.currentMeasure.append(g) diff --git a/music21/omr/correctors.py b/music21/omr/correctors.py index c6effb926..bc7c6fd45 100644 --- a/music21/omr/correctors.py +++ b/music21/omr/correctors.py @@ -188,7 +188,7 @@ def getVerticalProbabilityDistributionSinglePart(self, pn): >>> omrScore = converter.parse(omrPath) >>> ssOMR = omr.correctors.ScoreCorrector(omrScore) >>> allDists = ssOMR.getVerticalProbabilityDistributionSinglePart(1) - >>> ['%0.3f' % p for p in allDists] + >>> [f'{p:0.3f}' for p in allDists] ['0.571', '1.000', '0.667', '0.714'] ''' i = pn @@ -1005,8 +1005,11 @@ def differenceProbabilityForOneOpCode(self, opCodeTuple, source, destination=Non 'PFFPFF' >>> opCodes = vlnIIMH.getOpCodes(violaMH.hashString) >>> for oc in opCodes: - ... print('%30r : %.3f' % - ... (oc, vlnIIMH.differenceProbabilityForOneOpCode(oc, violaMH.hashString))) + ... prob = vlnIIMH.differenceProbabilityForOneOpCode( + ... oc, + ... violaMH.hashString, + ... ) + ... print(f'{oc!r:>30} : {prob:.3f}') ('equal', 0, 1, 0, 1) : 0.968 ('replace', 1, 2, 1, 2) : 0.009 ('equal', 2, 6, 2, 6) : 0.876 diff --git a/music21/pitch.py b/music21/pitch.py index 065a4bcd0..acb47ed56 100644 --- a/music21/pitch.py +++ b/music21/pitch.py @@ -2592,7 +2592,7 @@ def ps(self): >>> p = pitch.Pitch('c4') >>> p.microtone = 20 - >>> print('%.1f' % p.ps) + >>> print(f'{p.ps:.1f}') 60.2 Octaveless pitches use their .implicitOctave attributes: @@ -2912,8 +2912,8 @@ def nameWithOctave(self, value: str): octave = int(value[-1]) self.name = name self.octave = octave - except: - raise PitchException(f'Cannot set a nameWithOctave with {value!r}') + except (ValueError, PitchException) as e: + raise PitchException(f'Cannot set a nameWithOctave with {value!r}') from e @property def unicodeNameWithOctave(self) -> str: @@ -3713,7 +3713,7 @@ def harmonicFromFundamental(self, if target.ps <= fundamental.ps: raise PitchException( 'cannot find an equivalent harmonic for a fundamental ' - + f'({fundamental}) that is not above this Pitch ({self})' + f'({fundamental}) that is not above this Pitch ({self})' ) # up to the 32 harmonic diff --git a/music21/repeat.py b/music21/repeat.py index f0138c627..17bc473e5 100644 --- a/music21/repeat.py +++ b/music21/repeat.py @@ -61,7 +61,7 @@ class RepeatMark(prebase.ProtoM21Object): >>> s.append(PartialRepeat()) >>> repeats = s.getElementsByClass('RepeatMark') # not a Music21Object, so use quotes >>> if repeats: - ... print('Stream has %s repeat(s) in it' % (len(repeats))) + ... print(f'Stream has {len(repeats)} repeat(s) in it') Stream has 1 repeat(s) in it ''' @@ -877,7 +877,7 @@ def _stripRepeatBarlines(self, m, newType='double'): lb = m.leftBarline rb = m.rightBarline if lb is not None and 'music21.bar.Repeat' in lb.classSet: - # environLocal.printDebug(['inserting new barline: %s' % newStyle]) + # environLocal.printDebug([f'inserting new barline: {newStyle}']) m.leftBarline = bar.Barline(newType) if rb is not None and 'music21.bar.Repeat' in rb.classSet: m.rightBarline = bar.Barline(newType) @@ -940,11 +940,15 @@ def repeatBarsAreCoherent(self): environLocal.printDebug([f'Repeats are not balanced: countBalance: {countBalance}']) return False if startCount not in (endCount, endCount - 1): - environLocal.printDebug(['start count not the same as end count: %s / %s' % ( - startCount, endCount)]) + environLocal.printDebug([ + 'start count not the same as end count: ' + f'{startCount} / {endCount}' + ]) return False - # environLocal.printDebug(['matched start and end repeat barline count of: ', - # '%s/%s' % (startCount, endCount)]) + # environLocal.printDebug([ + # 'matched start and end repeat barline count of: ' + # f'{startCount}/{endCount}' + # ]) return True def _daCapoOrSegno(self): @@ -1276,7 +1280,7 @@ def findInnermostRepeatIndices(self, streamObj): # an end may be placed on the left barline; of the next measure # meaning that we only want up until the previous elif lb.direction == 'end': - # environLocal.printDebug(['found an end in left barline: %s' % lb]) + # environLocal.printDebug([f'found an end in left barline: {lb}']) if not startIndices: # get from first to this one barRepeatIndices = range(i) diff --git a/music21/roman.py b/music21/roman.py index b8be71dea..43f23238d 100644 --- a/music21/roman.py +++ b/music21/roman.py @@ -1234,10 +1234,12 @@ def romanNumeralFromChord( rn = RomanNumeral(rnString, keyObj, updatePitches=False, # correctRNAlterationForMinor() adds cautionary sixthMinor=Minor67Default.CAUTIONARY, seventhMinor=Minor67Default.CAUTIONARY) - except fbNotation.ModifierException as strerror: + except fbNotation.ModifierException as strerror: # pragma: no cover raise RomanNumeralException( - 'Could not parse {0} from chord {1} as an RN ' - 'in key {2}: {3}'.format(rnString, chordObj, keyObj, strerror)) # pragma: no cover + 'Could not parse ' + f'{rnString} from chord {chordObj} as an RN ' + f'in key {keyObj}: {strerror}' + ) from strerror # Is this linking them in an unsafe way? rn.pitches = chordObj.pitches @@ -3469,10 +3471,12 @@ def key(self, keyOrScale): # environLocal.printDebug(['got keyOrScale', keyOrScale]) try: keyClasses = keyOrScale.classes - except: # pragma: no cover + except AttributeError: # pragma: no cover raise RomanNumeralException( - 'Cannot call classes on object {0!r}, send only Key ' - 'or Scale Music21Objects'.format(keyOrScale)) + 'Cannot call classes on object ' + f'{keyOrScale!r}, send only Key ' + 'or Scale Music21Objects' + ) if 'Key' in keyClasses: # good to go if keyOrScale.tonicPitchNameWithCase not in _keyCache: diff --git a/music21/romanText/rtObjects.py b/music21/romanText/rtObjects.py index 24a5c82d8..1dd89acf7 100644 --- a/music21/romanText/rtObjects.py +++ b/music21/romanText/rtObjects.py @@ -824,8 +824,10 @@ def getOffset(self, timeSignature): try: post = timeSignature.getOffsetFromBeat(beat) except exceptions21.TimeSignatureException: - environLocal.printDebug(['bad beat specification: %s in a meter of %s' % ( - self.src, timeSignature)]) + environLocal.printDebug([ + 'bad beat specification: ' + f'{self.src} in a meter of {timeSignature}' + ]) post = 0.0 return post @@ -1110,8 +1112,8 @@ def splitAtHeader(self, lines): ''' # iterate over lines and find the first measure definition iStartBody = None - for i, l in enumerate(lines): - if reMeasureTag.match(l.strip()) is not None: + for i, line in enumerate(lines): + if reMeasureTag.match(line.strip()) is not None: # found a measure definition iStartBody = i break @@ -1164,11 +1166,13 @@ def tokenizeBody(self, lines): rtt = RTTagged(line) rtt.lineNumber = currentLineNumber post.append(rtt) - except Exception: + except Exception as exc: import traceback tracebackMessage = traceback.format_exc() - raise RTHandlerException('At line %s (%s) an exception was raised: \n%s' % ( - currentLineNumber, line, tracebackMessage)) + raise RTHandlerException( + f'At line {currentLineNumber} ({line}) an exception was raised:\n' + f'{tracebackMessage}' + ) from exc return post def tokenizeAtoms(self, line, container=None): diff --git a/music21/romanText/translate.py b/music21/romanText/translate.py index 57f98ce00..c2a9e14cd 100644 --- a/music21/romanText/translate.py +++ b/music21/romanText/translate.py @@ -198,11 +198,12 @@ def _copySingleMeasure(rtTagged, p, kCurrent): if mPast.number == target: try: m = copy.deepcopy(mPast) - except TypeError: # pragma: no cover + except TypeError as te: # pragma: no cover raise RomanTextTranslateException( f'Failed to copy measure {mPast.number}:' + ' did you perhaps parse an RTOpus object with romanTextToStreamScore ' - + 'instead of romanTextToStreamOpus?') + + 'instead of romanTextToStreamOpus?' + ) from te m.number = rtTagged.number[0] # update all keys for rnPast in m.getElementsByClass(roman.RomanNumeral): @@ -263,13 +264,13 @@ def _copyMultipleMeasures(rtMeasure: rtObjects.RTMeasure, if mPast.number in range(targetStart, targetEnd + 1): try: m = copy.deepcopy(mPast) - except TypeError: # pragma: no cover + except TypeError as te: # pragma: no cover raise RomanTextTranslateException( - 'Failed to copy measure {0} to measure range {1}-{2}: '.format( - mPast.number, targetStart, targetEnd) - + 'did you perhaps parse an RTOpus object with romanTextToStreamScore ' - + 'instead of romanTextToStreamOpus?') - + f'Failed to copy measure {mPast.number} to measure range ' + f'{targetStart}-{targetEnd}: did you perhaps parse an RTOpus ' + 'object with romanTextToStreamScore instead of ' + 'romanTextToStreamOpus?' + ) from te m.number = rtMeasure.number[0] + mPast.number - targetStart measures.append(m) # update all keys @@ -381,11 +382,11 @@ def translateTokens(self, tokens): for token in tokens: try: self.translateOneLineToken(token) - except Exception: + except Exception as exc: tracebackMessage = traceback.format_exc() raise RomanTextTranslateException( f'At line {token.lineNumber} for token {token}, ' - + f'an exception was raised: \n{tracebackMessage}') + f'an exception was raised: \n{tracebackMessage}') from exc p = self.p p.coreElementsChanged() @@ -661,8 +662,8 @@ def parseKeySignatureTag(self, rtTagged: rtObjects.RTTagged): try: dataVal = int(data) self.keySigCurrent = key.KeySignature(dataVal) - except ValueError: - raise RomanTextTranslateException(f'Cannot parse key signature: {data!r}') + except ValueError as ve: + raise RomanTextTranslateException(f'Cannot parse key signature: {data!r}') from ve self.setKeySigFromFirstKeyToken = False # environLocal.printDebug(['keySigCurrent:', keySigCurrent]) self.foundAKeySignatureSoFar = True @@ -734,9 +735,10 @@ def translateSingleMeasureAtom( elif isinstance(a, rtObjects.RTKeySignature): try: # this sets the keysignature but not the prefix text thisSig = a.getKeySignature() - except (exceptions21.Music21Exception, ValueError): # pragma: no cover + except (exceptions21.Music21Exception, ValueError) as ve: # pragma: no cover raise RomanTextTranslateException( - f'cannot get key from {a.src} in line {self.currentMeasureToken.src}') + f'cannot get key from {a.src} in line {self.currentMeasureToken.src}' + ) from ve # insert at beginning of measure if at beginning # -- for things like pickups. if m.number <= 1: @@ -752,13 +754,12 @@ def translateSingleMeasureAtom( # set new offset based on beat try: newOffset = a.getOffset(self.tsCurrent) - except ValueError: # pragma: no cover + except ValueError as ve: # pragma: no cover raise RomanTextTranslateException( - 'cannot properly get an offset from ' - + f'beat data {a.src}' - + 'under timeSignature {0} in line {1}'.format( - self.tsCurrent, - self.currentMeasureToken.src)) + f'cannot properly get an offset from beat data {a.src} ' + f'under timeSignature {self.tsCurrent} ' + f'in line {self.currentMeasureToken.src}' + ) from ve if (self.previousChordInMeasure is None and self.previousRn is not None and newOffset > 0): @@ -932,9 +933,10 @@ def setAnalyticKey(self, a): try: # this sets the key and the keysignature self.kCurrent, pl = _getKeyAndPrefix(a) self.prefixLyric += pl - except: # pragma: no cover + except (ValueError, exceptions21.Music21Exception) as ve: # pragma: no cover raise RomanTextTranslateException( - f'cannot get analytic key from {a.src} in line {self.currentMeasureToken.src}') + f'cannot get analytic key from {a.src} in line {self.currentMeasureToken.src}' + ) from ve self.setKeyChangeToken = True diff --git a/music21/romanText/tsvConverter.py b/music21/romanText/tsvConverter.py index bec984451..2444b1af6 100644 --- a/music21/romanText/tsvConverter.py +++ b/music21/romanText/tsvConverter.py @@ -13,7 +13,6 @@ ''' from __future__ import annotations -import abc import csv import fractions import pathlib @@ -155,7 +154,7 @@ def _float_or_frac(value): DCML_HEADERS = {1: DCML_V1_HEADERS, 2: DCML_V2_HEADERS} -class TabChordBase(abc.ABC): +class TabChordBase(): ''' Abstract base class for intermediate representation format for moving between tabular data and music21 chords. @@ -888,17 +887,17 @@ def _m21ToTsv_v2(self) -> list[list[str]]: thisEntry.numeral = thisRN.romanNumeral thisEntry.form = getForm(thisRN) # Strip any leading non-digits from figbass (e.g., M43 -> 43) - figbassm = re.match(r'^\D*(\d.*|)', thisRN.figuresWritten) + fig_bass_m = re.match(r'^\D*(\d.*|)', thisRN.figuresWritten) # implementing the following check according to the review - # at https://github.com/cuthbertLab/music21/pull/1267/files/a1ad510356697f393bf6b636af8f45e81ad6ccc8#r936472302 #pylint: disable=line-too-long + # at https://github.com/cuthbertLab/music21/pull/1267/ # but the match should always exist because either: # 1. there is a digit in the string, in which case it matches # because of the left side of the alternation operator # 2. there is no digit in the string, in which case it matches # because of the right side of the alternation operator # (an empty string) - if figbassm is not None: - thisEntry.figbass = figbassm.group(1) + if fig_bass_m is not None: + thisEntry.figbass = fig_bass_m.group(1) else: thisEntry.figbass = '' thisEntry.changes = None diff --git a/music21/scale/__init__.py b/music21/scale/__init__.py index 80e81d0ea..87cc34c74 100644 --- a/music21/scale/__init__.py +++ b/music21/scale/__init__.py @@ -353,7 +353,6 @@ def buildNetworkFromPitches(self, pitchList): for currentPitch, nextPitch in zip(pitchList, pitchList[1:]): intervalList.append(interval.Interval(currentPitch, nextPitch)) if pitchList[-1].name == pitchList[0].name: # the completion of the scale has been given. - # print('hi %s ' % pitchList) # this scale is only octave duplicating if the top note is exactly # 1 octave above the bottom; if it spans more than one octave, # all notes must be identical in each octave @@ -1753,12 +1752,6 @@ def pitchFromDegree( return post - # if 0 < degree <= self._abstract.getDegreeMaxUnique(): - # return self.getPitches()[degree - 1] - # else: - # raise('Scale degree is out of bounds: must be between 1 and %s.' % ( - # self._abstract.getDegreeMaxUnique())) - def pitchesFromScaleDegrees( self, degreeTargets, diff --git a/music21/scale/intervalNetwork.py b/music21/scale/intervalNetwork.py index 82a475b79..d36a70404 100644 --- a/music21/scale/intervalNetwork.py +++ b/music21/scale/intervalNetwork.py @@ -2201,7 +2201,7 @@ def realizeMinMax( # now find lowest and highest pitch minPitch = post[-1] maxPitch = post[0] - for p, nId in postPairs + prePairs: + for p, _nId in postPairs + prePairs: if p.ps < minPitch.ps: minPitch = p if p.ps > maxPitch.ps: diff --git a/music21/search/base.py b/music21/search/base.py index 0ddff09e8..3c07c3307 100644 --- a/music21/search/base.py +++ b/music21/search/base.py @@ -99,8 +99,10 @@ class SearchMatch(namedtuple('SearchMatch', ['elStart', 'els', 'index', 'iterato } def __repr__(self): - return 'SearchMatch(elStart={0}, els=len({1}), index={2}, iterator=[...])'.format( - repr(self.elStart), len(self.els), repr(self.index)) + es = self.elStart + el = len(self.els) + ix = self.index + return f'SearchMatch(elStart={es!r}, els=len({el}), index={ix!r}, iterator=[...])' class StreamSearcher: @@ -525,7 +527,7 @@ def approximateNoteSearch(thisStream, otherStreams): >>> o3.id = 'o3' >>> l = search.approximateNoteSearch(s, [o1, o2, o3]) >>> for i in l: - ... print('%s %r' % (i.id, i.matchProbability)) + ... print(f'{i.id} {i.matchProbability!r}') o1 0.666666... o3 0.333333... o2 0.083333... @@ -561,7 +563,7 @@ def approximateNoteSearchNoRhythm(thisStream, otherStreams): >>> o3.id = 'o3' >>> l = search.approximateNoteSearchNoRhythm(s, [o1, o2, o3]) >>> for i in l: - ... print('%s %r' % (i.id, i.matchProbability)) + ... print(f'{i.id} {i.matchProbability!r}') o1 0.83333333... o3 0.5 o2 0.1666666... @@ -597,7 +599,7 @@ def approximateNoteSearchOnlyRhythm(thisStream, otherStreams): >>> o3.id = 'o3' >>> l = search.approximateNoteSearchOnlyRhythm(s, [o1, o2, o3]) >>> for i in l: - ... print('%s %r' % (i.id, i.matchProbability)) + ... print(f'{i.id} {i.matchProbability!r}') o1 0.5 o3 0.33... o2 0.0 @@ -635,7 +637,7 @@ def approximateNoteSearchWeighted(thisStream, otherStreams): >>> o4.id = 'o4' >>> l = search.approximateNoteSearchWeighted(s, [o1, o2, o3, o4]) >>> for i in l: - ... print('%s %r' % (i.id, i.matchProbability)) + ... print(f'{i.id} {i.matchProbability!r}') o3 0.83333... o1 0.75 o4 0.75 @@ -1068,10 +1070,18 @@ def mostCommonMeasureRhythms(streamIn, transposeDiatonic=False): >>> bach = corpus.parse('bwv1.6') >>> sortedRhythms = search.mostCommonMeasureRhythms(bach) >>> for in_dict in sortedRhythms[0:3]: - ... print(f"no: {in_dict['number']} rhythmString: {in_dict['rhythmString']}") - ... print('bars: %r' % ([(m.number, - ... str(m.getContextByClass(stream.Part).id)) - ... for m in in_dict['measures']])) + ... number = in_dict['number'] + ... rhythmString = in_dict['rhythmString'] + ... measures = in_dict['measures'] + ... print( + ... f'no: {number} ' + ... f'rhythmString: {rhythmString}' + ... ) + ... bars = [ + ... (m.number, str(m.getContextByClass(stream.Part).id)) + ... for m in measures + ... ] + ... print(f'bars: {bars!r}') ... in_dict['rhythm'].show('text') ... print('-----') no: 34 rhythmString: PPPP diff --git a/music21/search/lyrics.py b/music21/search/lyrics.py index a873e668b..6b1e89856 100644 --- a/music21/search/lyrics.py +++ b/music21/search/lyrics.py @@ -54,10 +54,11 @@ class IndexedLyric(namedtuple( 'absoluteStart': '''the position, not in the current identifier, but in all the lyrics''', 'absoluteEnd': '''the end position in all the lyrics''' } + def __repr__(self): return (f'IndexedLyric(el={self.el!r}, start={self.start!r}, end={self.end!r}, ' - + f'measure={self.measure!r}, lyric={self.lyric!r}, text={self.text!r}, ' - + f'identifier={self.identifier!r})') + f'measure={self.measure!r}, lyric={self.lyric!r}, text={self.text!r}, ' + f'identifier={self.identifier!r})') def modify(self, **keywords): ''' @@ -101,8 +102,8 @@ class SearchMatch(namedtuple('SearchMatch', def __repr__(self): return (f'SearchMatch(mStart={self.mStart!r}, mEnd={self.mEnd!r}, ' - + f'matchText={self.matchText!r}, els={self.els!r}, indices=[...], ' - + f'identifier={self.identifier!r})') + f'matchText={self.matchText!r}, els={self.els!r}, indices=[...], ' + f'identifier={self.identifier!r})') class LyricSearcherException(Music21Exception): diff --git a/music21/sieve.py b/music21/sieve.py index ec66a11e6..d1b754ceb 100644 --- a/music21/sieve.py +++ b/music21/sieve.py @@ -860,8 +860,10 @@ def __init__(self, src, z=None): # assign self._residuals and do analysis try: self._process() - except AssertionError: - raise CompressionSegmentException('no Residual classes found for this z range') + except AssertionError as assertError: + raise CompressionSegmentException( + 'no Residual classes found for this z range' + ) from assertError def _zUpdate(self, z=None): # z must at least be a superset of match diff --git a/music21/sites.py b/music21/sites.py index fc44ca46f..a07d90384 100644 --- a/music21/sites.py +++ b/music21/sites.py @@ -255,8 +255,6 @@ def __deepcopy__(self, memo=None): newIdKey = None else: newIdKey = id(newSite.site) - # if newIdKey != idKey and oldSite.site != None: - # print('Error! %s %s' % (newIdKey, idKey)) newSite.siteIndex = oldSite.siteIndex newSite.globalSiteIndex = _singletonCounter() newSite.classString = oldSite.classString @@ -357,7 +355,10 @@ def add(self, obj, timeValue=None, idKey=None, classString=None): updateNotAdd = True # if idKey is not None: - # print('Updating idKey %s for object %s' % (idKey, id(obj))) + # print( + # 'Updating idKey ' + # f'{idKey} for object {id(obj)}' + # ) # environLocal.printDebug(['adding obj', obj, idKey]) # weak refs were being passed in __deepcopy__ calling this method @@ -396,7 +397,7 @@ def clear(self): def yieldSites(self, *, excludeNone: t.Literal[True], - sortByCreationTime: t.Union[bool, t.Literal['reverse']] = False, + sortByCreationTime: bool|t.Literal['reverse'] = False, priorityTarget=None, ) -> Generator[stream.Stream, None, None]: ... @@ -405,7 +406,7 @@ def yieldSites(self, def yieldSites(self, *, excludeNone: bool = False, - sortByCreationTime: t.Union[bool, t.Literal['reverse']] = False, + sortByCreationTime: bool|t.Literal['reverse'] = False, priorityTarget=None, ) -> Generator[stream.Stream|None, None, None]: ... @@ -413,7 +414,7 @@ def yieldSites(self, def yieldSites(self, *, excludeNone: bool = False, - sortByCreationTime: t.Union[bool, t.Literal['reverse']] = False, + sortByCreationTime: bool|t.Literal['reverse'] = False, priorityTarget=None, ) -> Generator[stream.Stream|None, None, None]: # noinspection PyDunderSlots @@ -663,7 +664,11 @@ def getObjByClass( priorityTarget=priorityTarget, excludeNone=True, ) # objs is a generator - # printMemo(memo, 'getObjByClass() called: looking at %s sites' % len(objs)) + # printMemo( + # memo, + # 'getObjByClass() called: looking at ' + # + f'{len(objs)} sites' + # ) classNameIsStr = isinstance(className, str) for obj in objs: # environLocal.printDebug(['memo', memo]) @@ -961,7 +966,7 @@ def remove(self, site): except Exception as e: raise SitesException( 'an entry for this object ' - + f'({site}) is not stored in this Sites object' + f'({site}) is not stored in this Sites object' ) from e def removeById(self, idKey): diff --git a/music21/spanner.py b/music21/spanner.py index a27efd732..ddc158865 100644 --- a/music21/spanner.py +++ b/music21/spanner.py @@ -207,8 +207,7 @@ class Spanner(base.Music21Object): equalityAttributes = ('spannerStorage',) def __init__(self, - *spannedElements: t.Union[base.Music21Object, - Sequence[base.Music21Object]], + *spannedElements: base.Music21Object|Sequence[base.Music21Object], **keywords): super().__init__(**keywords) @@ -424,8 +423,7 @@ def getSpannedElementIds(self): def addSpannedElements( self, - spannedElements: t.Union[Sequence[base.Music21Object], - base.Music21Object], + spannedElements: Sequence[base.Music21Object]|base.Music21Object, *otherElements: base.Music21Object, ): ''' @@ -464,10 +462,12 @@ def addSpannedElements( else: pass # it makes sense to not have multiple copies - # environLocal.printDebug(['''attempting to add an object (%s) that is - # already found in the SpannerStorage stream of spanner %s; - # this may not be an error.''' % (c, self)]) - + # environLocal.printDebug([ + # 'attempting to add an object ' + # f'({c}) that is already found in the SpannerStorage stream ' + # f'of spanner {self};\n' + # 'this may not be an error.' + # ]) self.spannerStorage.coreElementsChanged() def hasSpannedElement(self, spannedElement: base.Music21Object) -> bool: diff --git a/music21/stream/base.py b/music21/stream/base.py index 33cfa587f..88b6c76bb 100644 --- a/music21/stream/base.py +++ b/music21/stream/base.py @@ -668,11 +668,11 @@ def __getitem__(self, else: try: match = self.elements[k] - except IndexError: + except IndexError as ie: raise IndexError( f'attempting to access index {k} ' - + f'while elements is of size {len(self.elements)}' - ) + f'while elements is of size {len(self.elements)}' + ) from ie # setting active site as cautionary measure self.coreSelfActiveSite(match) return t.cast(M21ObjType, match) @@ -2185,10 +2185,10 @@ def elementOffset(self, element, returnSpecial=False): if isinstance(o, str) and returnSpecial is False and o in OffsetSpecial: try: return getattr(self, o) - except AttributeError: # pragma: no cover + except AttributeError as ae: # pragma: no cover raise base.SitesException( 'attempted to retrieve a bound offset with a string ' - + f'attribute that is not supported: {o}') + f'attribute that is not supported: {o}') from ae else: return o @@ -2296,15 +2296,15 @@ def insert(self, try: activeSite = item.activeSite offset = item.getOffsetBySite(activeSite) - except AttributeError: + except AttributeError as ae: raise StreamException(f'Cannot insert item {item!r} to stream ' - + '-- is it a music21 object?') + + '-- is it a music21 object?') from ae # if not common.isNum(offset): try: # using float conversion instead of isNum for performance offset = float(offset) - except (ValueError, TypeError): - raise StreamException(f'Offset {offset!r} must be a number.') + except (ValueError, TypeError) as ve: + raise StreamException(f'Offset {offset!r} must be a number.') from ve element = item @@ -2957,7 +2957,7 @@ def replace(self, Note that it does not work the other way: if we made the replacement on `s` then `sf`, the flattened representation, would not be changed, since `s` - does not derive from `sf` but vice-versa. + does not derive from `sf` but vice versa. With `recurse=True`, a stream can replace an element that is further down in the hierarchy. First let's set up a @@ -4318,9 +4318,9 @@ def hasMeasureNumberInformation(measureIterator: iterator.StreamIterator[Measure for m in measureIterator: try: mNumber = int(m.number) - except ValueError: # pragma: no cover + except ValueError as ve: # pragma: no cover # should never happen. - raise StreamException(f'found problematic measure for numbering: {m}') + raise StreamException(f'found problematic measure for numbering: {m}') from ve if mNumber != 0: return True return False @@ -5678,8 +5678,9 @@ def getTimeSignatures(self, *, # get a default and/or place default at zero if nothing at zero if returnDefault: if not post or post[0].offset > 0.0: - ts = meter.TimeSignature('%s/%s' % (defaults.meterNumerator, - defaults.meterDenominatorBeatType)) + ts = meter.TimeSignature( + f'{defaults.meterNumerator}/{defaults.meterDenominatorBeatType}' + ) post.insert(0, ts) # environLocal.printDebug(['getTimeSignatures(): final result:', post[0]]) return post @@ -5813,7 +5814,12 @@ def getInstrument(self, ) return post.first() - def invertDiatonic(self, inversionNote=note.Note('C4'), *, inPlace=False): + def invertDiatonic( + self, + inversionNote: note.Note|None = None, + *, + inPlace: bool = False + ): ''' inverts a stream diatonically around the given note (by default, middle C) @@ -5872,6 +5878,7 @@ def invertDiatonic(self, inversionNote=note.Note('C4'), *, inPlace=False): * Changed in v5: inPlace is False by default. ''' + inversionNoteReal: note.Note = inversionNote or note.Note('C4') if inPlace: returnStream = self else: @@ -5879,23 +5886,23 @@ def invertDiatonic(self, inversionNote=note.Note('C4'), *, inPlace=False): keySigSearch = returnStream.recurse().getElementsByClass(key.KeySignature) - quickSearch = True + ourKey: key.KeySignature | None if not keySigSearch: ourKey = key.Key('C') elif len(keySigSearch) == 1: ourKey = keySigSearch[0] else: ourKey = None # for might be undefined warning - quickSearch = False - inversionDNN = inversionNote.pitch.diatonicNoteNum - for n in returnStream[note.NotRest]: + inversionDNN = inversionNoteReal.pitch.diatonicNoteNum + for n in returnStream[note.Note]: n.pitch.diatonicNoteNum = (2 * inversionDNN) - n.pitch.diatonicNoteNum - if quickSearch: # use previously found + if ourKey: # use previously found n.pitch.accidental = ourKey.accidentalByStep(n.pitch.step) else: # use context search ksActive = n.getContextByClass(key.KeySignature) - n.pitch.accidental = ksActive.accidentalByStep(n.pitch.step) + if ksActive: + n.pitch.accidental = ksActive.accidentalByStep(n.pitch.step) if n.pitch.accidental is not None: n.pitch.accidental.displayStatus = None @@ -6037,10 +6044,10 @@ def repeatAppend(self, item, numberOfTimes): unused = item.isStream element = item # if not isinstance(item, music21.Music21Object): - except AttributeError: + except AttributeError as ae: # element = music21.ElementWrapper(item) raise StreamException('to put a non Music21Object in a stream, ' - + 'create a music21.ElementWrapper for the item') + + 'create a music21.ElementWrapper for the item') from ae # # if not an element, embed # if not isinstance(item, music21.Music21Object): # element = music21.ElementWrapper(item) @@ -6072,11 +6079,11 @@ def repeatInsert(self, item, offsets): unused = item.isStream element = item # if not isinstance(item, music21.Music21Object): - except AttributeError: + except AttributeError as ae: # if not an element, embed # element = music21.ElementWrapper(item) raise StreamException('to put a non Music21Object in a stream, ' - + 'create a music21.ElementWrapper for the item') + + 'create a music21.ElementWrapper for the item') from ae # if not isinstance(item, music21.Music21Object): # # if not an element, embed # element = music21.ElementWrapper(item) @@ -6393,8 +6400,8 @@ def chordifyOneMeasure(templateInner, streamToChordify): if quarterLength < 0: # pragma: no cover environLocal.warn( 'Something is wrong with the verticality ' - + f'in stream {templateInner!r}: {vert!r} ' - + f'its endTime {endTime} is less than its offset {offset}' + f'in stream {templateInner!r}: {vert!r} ' + f'its endTime {endTime} is less than its offset {offset}' ) chordOrRest = vert.makeElement(quarterLength, @@ -7048,7 +7055,7 @@ def makeNotation(self, try: makeNotation.makeBeams(returnStream, inPlace=True) except meter.MeterException as me: - warnings.warn(str(me)) + warnings.warn(str(me), stacklevel=2) # note: this needs to be after makeBeams, as placing this before # makeBeams was causing the duration's tuplet to lose its type setting @@ -7698,7 +7705,7 @@ def sorted(self): False >>> g = '' >>> for myElement in s: - ... g += '%s: %s; ' % (myElement.offset, myElement.name) + ... g += f'{myElement.offset}: {myElement.name}; ' >>> g '0.0: C#; 2.0: C#; 4.0: C#; 1.0: D-; 3.0: D-; 5.0: D-; ' >>> y = s.sorted() @@ -7706,7 +7713,7 @@ def sorted(self): True >>> g = '' >>> for myElement in y: - ... g += '%s: %s; ' % (myElement.offset, myElement.name) + ... g += f'{myElement.offset}: {myElement.name}; ' >>> g '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; ' >>> farRight = note.Note('E') @@ -7715,13 +7722,13 @@ def sorted(self): >>> y.insert(farRight) >>> g = '' >>> for myElement in y: - ... g += '%s: %s; ' % (myElement.offset, myElement.name) + ... g += f'{myElement.offset}: {myElement.name}; ' >>> g '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; 2.0: E; ' >>> z = y.sorted() >>> g = '' >>> for myElement in z: - ... g += '%s: %s; ' % (myElement.offset, myElement.name) + ... g += f'{myElement.offset}: {myElement.name}; ' >>> g '0.0: C#; 1.0: D-; 2.0: C#; 2.0: E; 3.0: D-; 4.0: C#; 5.0: D-; ' >>> z[2].name, z[3].name @@ -7904,9 +7911,7 @@ def flatten(self, retainContainers=False) -> t.Self: >>> g = '' >>> for myElement in s: - ... g += '%s: %s; ' % (myElement.offset, myElement.name) - ... - + ... g += f'{myElement.offset}: {myElement.name}; ' >>> g '0.0: C#; 2.0: C#; 4.0: C#; 1.0: D-; 3.0: D-; 5.0: D-; ' @@ -7916,9 +7921,7 @@ def flatten(self, retainContainers=False) -> t.Self: >>> g = '' >>> for myElement in y: - ... g += '%s: %s; ' % (myElement.offset, myElement.name) - ... - + ... g += f'{myElement.offset}: {myElement.name}; ' >>> g '0.0: C#; 1.0: D-; 2.0: C#; 3.0: D-; 4.0: C#; 5.0: D-; ' @@ -8577,10 +8580,8 @@ def _getSeconds(self): mm = ti.getSoundingMetronomeMark() offsetMetronomeMarkPairs.append([o, mm]) - for i, (o, mm) in enumerate(offsetMetronomeMarkPairs): - if i == 0 and o > 0.0: - getTempoFromContext = True - break # just need first + if offsetMetronomeMarkPairs and offsetMetronomeMarkPairs[0][0] > 0.0: + getTempoFromContext = True if getTempoFromContext: ti = self.getContextByClass('TempoIndication') @@ -8717,10 +8718,9 @@ def metronomeMarkBoundaries(self, srcObj=None): offsetPairs[0][1])) # add any remaining ranges, starting from the second; if last, # use the highest time as the boundary - for i, (o, mm) in enumerate(offsetPairs): - if i == 0: - continue # already added first - elif i == len(offsetPairs) - 1: # last index + for i in range(1, len(offsetPairs)): + # start at 1 since we have already added first + if i == len(offsetPairs) - 1: # last index mmBoundaries.append((offsetPairs[i][0], highestTime, offsetPairs[i][1])) else: # add with next boundary @@ -8978,9 +8978,10 @@ def beatAndMeasureFromOffset(self, searchOffset, fixZeros=True): 'beatAndMeasureFromOffset: could not find a time signature for that place.') try: myBeat = ts1.getBeatProportion(searchOffset - myMeas.offset) - except: + except (ValueError, IndexError, AttributeError, exceptions21.Music21Exception) as exc: raise StreamException( - 'beatAndMeasureFromOffset: offset is beyond the end of the piece') + 'beatAndMeasureFromOffset: offset is beyond the end of the piece' + ) from exc foundMeasureNumber = myMeas.number # deal with second half of partial measures @@ -9639,8 +9640,9 @@ def sliceByQuarterLengths(self, quarterLengthList, *, target=None, if not opFrac(sum(qlProcess)) == e.quarterLength: raise StreamException( - 'cannot map quarterLength list into element Duration: %s, %s' % ( - sum(qlProcess), e.quarterLength)) + 'cannot map quarterLength list into element Duration: ' + f'{sum(qlProcess)}, {e.quarterLength}' + ) post = e.splitByQuarterLengths(qlProcess, addTies=addTies) # remove e from the source @@ -12830,7 +12832,7 @@ def showVariantAsOssialikePart(self, containedPart, variantGroups, *, inPlace=Fa from music21 import variant # containedPart must be in self, or an exception is raised. - if not (containedPart in self): + if containedPart not in self: raise variant.VariantException(f'Could not find {containedPart} in {self}') if inPlace is True: @@ -12853,7 +12855,7 @@ def showVariantAsOssialikePart(self, containedPart, variantGroups, *, inPlace=Fa eClasses = e.classes if 'Variant' in eClasses: elementGroups = e.groups - if (not (variantGroup in elementGroups) + if (variantGroup not in elementGroups or e.lengthType in ['elongation', 'deletion']): newPart.remove(e) else: diff --git a/music21/stream/core.py b/music21/stream/core.py index 0dc3019f5..33748bd64 100644 --- a/music21/stream/core.py +++ b/music21/stream/core.py @@ -194,9 +194,9 @@ def coreSetElementOffset( try: # try first, for the general case of not OffsetSpecial. offset = opFrac(offset) # type: ignore - except TypeError: + except TypeError as te: if offset not in OffsetSpecial: # pragma: no cover - raise StreamException(f'Cannot set offset to {offset!r} for {element}') + raise StreamException(f'Cannot set offset to {offset!r} for {element}') from te idEl = id(element) if not addElement and idEl not in self._offsetDict: @@ -426,7 +426,7 @@ def coreGuardBeforeAddElement(self, element, *, checkRedundancy=True): if eInStream is element: raise StreamException( f'the object ({element!r}, id()={id(element)} ' - + f'is already found in this Stream ({self!r}, id()={id(self)})' + f'is already found in this Stream ({self!r}, id()={id(self)})' ) # something was old. delete from _offsetDict # environLocal.warn('stale object') diff --git a/music21/stream/filters.py b/music21/stream/filters.py index 296f3854f..473284b81 100644 --- a/music21/stream/filters.py +++ b/music21/stream/filters.py @@ -133,7 +133,7 @@ def reset(self): def __call__(self, item, iterator=None): if self.numToFind == 0: # short circuit -- we already have - raise StopIteration + raise StopIteration() if item in self.target: # would popping the item be faster? No: then can't use for IsNotFilter @@ -415,7 +415,7 @@ def isElementOffsetInRange(self, e, offset, *, stopAfterEnd=False) -> bool: if stopAfterEnd: # if sorted, optimize by breaking after exceeding offsetEnd # eventually we could do a binary search to speed up - raise StopIteration + raise StopIteration() return False dur = e.duration diff --git a/music21/stream/iterator.py b/music21/stream/iterator.py index 5318f2291..6040ab827 100644 --- a/music21/stream/iterator.py +++ b/music21/stream/iterator.py @@ -215,7 +215,7 @@ def __next__(self) -> M21ObjType: return e self.cleanup() - raise StopIteration + raise StopIteration() def __getattr__(self, attr): ''' @@ -1575,7 +1575,7 @@ def __init__(self, def __next__(self) -> list[M21ObjType]: # type: ignore if self.raiseStopIterationNext: - raise StopIteration + raise StopIteration() retElementList: list[M21ObjType] = [] # make sure that cleanup is not called during the loop @@ -1602,13 +1602,13 @@ def __next__(self) -> list[M21ObjType]: # type: ignore self.activeInformation['lastYielded'] = retElementList[0] return retElementList - except StopIteration: # from the while statement. + except StopIteration as parent_stop: # from the while statement. if retElementList: self.raiseStopIterationNext = True self.activeInformation['lastYielded'] = retElementList[0] return retElementList else: - raise StopIteration + raise StopIteration() from parent_stop def reset(self): ''' @@ -1855,7 +1855,7 @@ def __next__(self) -> M21ObjType: self.activeInformation['lastYielded'] = None # always clean this up, no matter what self.cleanup() - raise StopIteration + raise StopIteration() def reset(self): ''' diff --git a/music21/stream/makeNotation.py b/music21/stream/makeNotation.py index 693ae55f9..4b5d891d1 100644 --- a/music21/stream/makeNotation.py +++ b/music21/stream/makeNotation.py @@ -195,9 +195,10 @@ def makeBeams( if durSum > barQL: # environLocal.printDebug([ - # 'attempting makeBeams with a bar that contains durations - # that sum greater than bar duration (%s > %s)' % - # (durSum, barQL)]) + # 'attempting makeBeams with a bar that contains durations ' + # 'that sum greater than bar duration ' + # f'({durSum} > {barQL})' + # ]) continue # getBeams @@ -1304,8 +1305,10 @@ def makeTies( continue # skip elements that begin past measure boundary. # TODO: put them entirely in the next measure. # raise stream.StreamException( - # 'element (%s) has offset %s within a measure ' - # 'that ends at offset %s' % (e, eOffset, mEnd)) + # 'element ' + # f'({e}) has offset {eOffset} within a measure ' + # f'that ends at offset {mEnd}' + # ) qLenWithinMeasure = mEnd - eOffset e, eRemain = e.splitAtQuarterLength( diff --git a/music21/stream/tests.py b/music21/stream/tests.py index 0de826fb5..725ec6e14 100644 --- a/music21/stream/tests.py +++ b/music21/stream/tests.py @@ -7958,23 +7958,23 @@ def getS(): return ss s = getS() - l, r = s.splitAtQuarterLength(2, retainOrigin=True) - # if retain origin is true, l is the original - self.assertIs(l, s) - self.assertEqual(l.highestTime, 2) - self.assertEqual(len(l.notes), 2) - self.assertEqual(r.highestTime, 2) - self.assertEqual(len(r.notes), 2) + left, right = s.splitAtQuarterLength(2, retainOrigin=True) + # if retain origin is true, left is the original + self.assertIs(left, s) + self.assertEqual(left.highestTime, 2) + self.assertEqual(len(left.notes), 2) + self.assertEqual(right.highestTime, 2) + self.assertEqual(len(right.notes), 2) sPost = Stream() - sPost.append(l) - sPost.append(r) + sPost.append(left) + sPost.append(right) s = getS() l2, r2 = s.splitAtQuarterLength(2, retainOrigin=False) self.assertIsNot(l2, s) - self.assertEqual(l2.highestTime, l.highestTime) - self.assertEqual(len(l2), len(l)) + self.assertEqual(l2.highestTime, left.highestTime) + self.assertEqual(len(l2), len(left)) self.assertEqual(len(r2.notes), 2) s = getS() diff --git a/music21/style.py b/music21/style.py index 2167a5668..92901c349 100644 --- a/music21/style.py +++ b/music21/style.py @@ -457,7 +457,7 @@ def _setSize(self, value): value = common.numToIntOrFloat(value) except ValueError: pass # MusicXML font sizes can be CSS strings. - # raise TextFormatException('Not a supported size: %s' % value) + # raise TextFormatException(f'Not a supported size: {value}') self._fontSize = value fontSize = property(_getSize, @@ -623,7 +623,7 @@ class StyleMixin(common.SlottedObjectMixin): Not used by Music21Objects because of the added trouble in copying etc. so there is code duplication with base.Music21Object ''' - # anytime something is changed here, change in base.Music21Object and vice-versa + # anytime something is changed here, change in base.Music21Object and vice versa _styleClass = Style __slots__ = ('_style', '_editorial') @@ -653,12 +653,9 @@ def hasStyleInformation(self) -> bool: >>> lObj.hasStyleInformation True ''' - try: - self._style - except AttributeError: - pass - - return not (self._style is None) + if not hasattr(self, '_style'): + return False + return self._style is not None @property def style(self) -> Style: @@ -683,7 +680,7 @@ def style(self) -> Style: >>> acc.style.absoluteX is None True ''' - # anytime something is changed here, change in base.Music21Object and vice-versa + # anytime something is changed here, change in base.Music21Object and vice versa if self._style is None: styleClass = self._styleClass self._style = styleClass() @@ -712,7 +709,7 @@ def hasEditorialInformation(self) -> bool: >>> acc.hasEditorialInformation True ''' - return not (self._editorial is None) + return self._editorial is not None @property def editorial(self) -> editorial.Editorial: @@ -731,7 +728,7 @@ def editorial(self) -> editorial.Editorial: >>> acc.editorial }> ''' - # anytime something is changed here, change in base.Music21Object and vice-versa + # anytime something is changed here, change in base.Music21Object and vice versa from music21 import editorial if self._editorial is None: self._editorial = editorial.Editorial() diff --git a/music21/tablature.py b/music21/tablature.py index 2c019b425..f730d6e89 100644 --- a/music21/tablature.py +++ b/music21/tablature.py @@ -227,10 +227,10 @@ def getPitches(self) -> list[None|pitch.Pitch]: ''' if len(self.tuning) != self.numStrings: raise TablatureException( - 'Tuning must be set first, tuned for {0} notes, on a {1} string instrument'.format( - len(self.tuning), - self.numStrings - )) + 'Tuning must be set first, tuned for ' + f'{len(self.tuning)} notes, on a ' + f'{self.numStrings} string instrument' + ) pitchList: list[pitch.Pitch|None] = [None] * self.numStrings diff --git a/music21/tempo.py b/music21/tempo.py index 3d51e260e..10da94d87 100644 --- a/music21/tempo.py +++ b/music21/tempo.py @@ -1259,7 +1259,8 @@ def interpolateElements(element1, element2, sourceStream, >>> destStream2.insert(50.5, element2) >>> tempo.interpolateElements(element1, element2, sourceStream, destStream2) >>> for el in [eA, eB, eC]: - ... print('%.1f' % (el.getOffsetBySite(destStream2),)) + ... offset = float(el.getOffsetBySite(destStream2)) + ... print(f'{offset:.1f}') 20.2 30.3 40.4 @@ -1274,6 +1275,8 @@ def interpolateElements(element1, element2, sourceStream, Traceback (most recent call last): music21.tempo.TempoException: Could not find element with id ... ''' + # TODO: when python 3.13 is the minimum, get rid of the float around el.getOffsetBySite + # in the doctest above try: startOffsetSrc = element1.getOffsetBySite(sourceStream) except exceptions21.Music21Exception as e: @@ -1309,8 +1312,9 @@ def interpolateElements(element1, element2, sourceStream, else: raise TempoException( 'Could not find element ' - + f'{el!r} with id {el.id!r} ' - + 'in destinationStream and autoAdd is false') from e + f'{el!r} with id {el.id!r} ' + 'in destinationStream and autoAdd is false' + ) from e else: destinationOffset = (scaleAmount * (elOffsetSrc - startOffsetSrc)) + startOffsetDest el.setOffsetBySite(destinationStream, destinationOffset) diff --git a/music21/test/__init__.py b/music21/test/__init__.py index 9c680046d..10866d50b 100644 --- a/music21/test/__init__.py +++ b/music21/test/__init__.py @@ -1,5 +1,17 @@ from __future__ import annotations +__all__ = [ + 'base', + 'chord', + 'clef', + 'expressions', + 'interval', + 'metadata', + 'note', + 'pitch', + 'repeat', +] + from music21.test import test_base as base from music21.test import test_chord as chord from music21.test import test_clef as clef diff --git a/music21/test/commonTest.py b/music21/test/commonTest.py index b1922bd15..c8fa97f70 100644 --- a/music21/test/commonTest.py +++ b/music21/test/commonTest.py @@ -21,7 +21,6 @@ import sys import typing import types -import unittest import unittest.runner from unittest.signals import registerResult import warnings @@ -91,12 +90,12 @@ def testImports(): ''' # pylint: disable=unused-import try: - import scipy # type: ignore + import scipy # type: ignore # noqa: F401 except ImportError as e: raise ImportError('pip install scipy : needed for running test suites') from e try: - from Levenshtein import StringMatcher # type: ignore + from Levenshtein import StringMatcher # type: ignore # noqa: F401 except ImportError as e: raise ImportError('pip install python-Levenshtein : needed for running test suites') from e diff --git a/music21/test/coverageM21.py b/music21/test/coverageM21.py index 1fbe89f3a..2ea878894 100644 --- a/music21/test/coverageM21.py +++ b/music21/test/coverageM21.py @@ -52,7 +52,7 @@ def getCoverage(overrideVersion=False): for e in exclude_lines: cov.exclude(e, which='exclude') cov.start() - import music21 # pylint: disable=unused-import + import music21 # pylint: disable=unused-import # noqa: F401 except ImportError: cov = None else: diff --git a/music21/test/memoryUsage.py b/music21/test/memoryUsage.py index 1159cb7d1..816609476 100644 --- a/music21/test/memoryUsage.py +++ b/music21/test/memoryUsage.py @@ -7,8 +7,8 @@ try: # noinspection PyPackageRequirements import guppy # type: ignore - except ImportError: - raise exceptions21.Music21Exception('memoryUsage.py requires guppy') + except ImportError as ie: + raise exceptions21.Music21Exception('memoryUsage.py requires guppy') from ie hp = guppy.hpy() hp.setrelheap() diff --git a/music21/test/multiprocessTest.py b/music21/test/multiprocessTest.py index 65804e906..a63251a48 100644 --- a/music21/test/multiprocessTest.py +++ b/music21/test/multiprocessTest.py @@ -243,13 +243,16 @@ def printSummary(summaryOutput, timeStart, pathsToRun): elif moduleResponse.returnCode == 'UntrappedException': otherSummary.append(f'Untrapped Exception for unknown module: {moduleResponse.fp}') elif moduleResponse.returnCode == 'TrappedException': - otherSummary.append('Trapped Exception for module %s, at %s: %s' % - (moduleResponse.moduleName, - moduleResponse.fp, - moduleResponse.testRunner)) + otherSummary.append( + f'Trapped Exception for module {moduleResponse.moduleName}, ' + f'at {moduleResponse.fp}: ' + f'{moduleResponse.testRunner}' + ) elif moduleResponse.returnCode == 'LargeException': - otherSummary.append('Large Exception for file %s: %s' % - (moduleResponse.fp, moduleResponse.testResult)) + otherSummary.append( + f'Large Exception for file {moduleResponse.fp}: ' + f'{moduleResponse.testResult}' + ) elif moduleResponse.returnCode == 'ImportError': otherSummary.append(f'Import Error for {moduleResponse.fp}') elif moduleResponse.returnCode == 'NotInTree': @@ -258,33 +261,30 @@ def printSummary(summaryOutput, timeStart, pathsToRun): elif moduleResponse.returnCode == 'TestsRun': totalTests += moduleResponse.testsRun if moduleResponse.success: - successSummary.append('%s successfully ran %s tests in %s seconds' - % (moduleResponse.moduleName, - moduleResponse.testsRun, - moduleResponse.runTime)) + successSummary.append( + f'{moduleResponse.moduleName} successfully ran ' + f'{moduleResponse.testsRun} tests in ' + f'{moduleResponse.runTime} seconds' + ) else: errorsList = moduleResponse.errors # not the original errors list! see pickle note above failuresList = moduleResponse.failures errorsFoundSummary.append( '\n-----------------------------\n' - + '%s had %s ERRORS and %s FAILURES in %s tests after %s seconds:\n' % - (moduleResponse.moduleName, len(errorsList), - len(failuresList), moduleResponse.testsRun, moduleResponse.runTime) - + '-----------------------------\n') - + f'{moduleResponse.moduleName} had ' + f'{len(errorsList)} ERRORS and ' + f'{len(failuresList)} FAILURES in ' + f'{moduleResponse.testsRun} tests after ' + f'{moduleResponse.runTime} seconds:\n' + '-----------------------------\n' + ) for e in errorsList: outStr += e + '\n' errorsFoundSummary.append(str(e)) for f in failuresList: outStr += f + '\n' errorsFoundSummary.append(str(f)) - # for e in errorsList: - # print(e[0], e[1]) - # errorsFoundSummary.append('%s: %s' % (e[0], e[1])) - # for f in failuresList: - # print(f[0], f[1]) - # errorsFoundSummary.append('%s: %s' % (f[0], f[1])) else: otherSummary.append(f'Unknown return code {moduleResponse}') diff --git a/music21/test/testRunner.py b/music21/test/testRunner.py index 519be56bc..38e1954cb 100644 --- a/music21/test/testRunner.py +++ b/music21/test/testRunner.py @@ -252,8 +252,7 @@ def testHello(self): optionflags=optionflags, ) except ValueError as ve: # no docstrings - print('Problem in docstrings [usually a missing r value before ' - + f'the quotes:] {ve}') + print(f'Problem in docstrings [usually a missing r value before the quotes]: {ve}') s1 = unittest.TestSuite() verbosity = 1 diff --git a/music21/test/test_base.py b/music21/test/test_base.py index 070a34556..34d8d83a2 100644 --- a/music21/test/test_base.py +++ b/music21/test/test_base.py @@ -459,7 +459,7 @@ def testPickupMeasuresBuilt(self): self.assertEqual(m2.duration.quarterLength, 4.0) # we cannot get a bar duration b/c we have not associated a ts try: - m2.barDuration.quarterLength + m2.barDuration.quarterLength # noqa: B018 (useless expression) except exceptions21.StreamException: pass diff --git a/music21/test/treeYield.py b/music21/test/treeYield.py index 39ea72fa7..bd3276586 100644 --- a/music21/test/treeYield.py +++ b/music21/test/treeYield.py @@ -60,7 +60,7 @@ def run(self, obj, memo=None): tObj = type(obj) if tObj in self.nonIterables: pass - elif tObj == dict: + elif tObj is dict: for keyX in obj: dictTuple = ('dict', keyX) self.stackVals.append(dictTuple) @@ -93,8 +93,10 @@ def run(self, obj, memo=None): self.stackVals.append(objTuple) try: yield from self.run(gotValue, memo=memo) - except RuntimeError: - raise ValueError(f'Maximum recursion on:\n{self.currentLevel()}') + except RuntimeError as runError: + raise ValueError( + f'Maximum recursion on:\n{self.currentLevel()}' + ) from runError self.stackVals.pop() self.currentStack.pop() diff --git a/music21/tinyNotation.py b/music21/tinyNotation.py index d2ffd3dd9..591fca9d0 100644 --- a/music21/tinyNotation.py +++ b/music21/tinyNotation.py @@ -1523,7 +1523,7 @@ def testGetDefaultTokenMap(self) -> None: validTokenTypeCounts, ( 'Found unexpected token type in default token map:' - + f'{tokenType.__class__.__name__}.' + f'{tokenType.__class__.__name__}.' ) ) @@ -1534,8 +1534,8 @@ def testGetDefaultTokenMap(self) -> None: 0, ( 'Should provide a non-empty string for the regular ' - + 'expression in the default token map for tokens of type ' - + f'{tokenType.__class__.__name__}.' + 'expression in the default token map for tokens of type ' + f'{tokenType.__class__.__name__}.' ) ) diff --git a/music21/tree/core.py b/music21/tree/core.py index 408c0e3e9..940ff3bf4 100644 --- a/music21/tree/core.py +++ b/music21/tree/core.py @@ -242,20 +242,19 @@ def __init__(self, position, payload=None): # SPECIAL METHODS # def __repr__(self): - lcHeight = None + lch = None if self.leftChild: - lcHeight = self.leftChild.height - rcHeight = None + lch = self.leftChild.height + + rch = None if self.rightChild: - rcHeight = self.rightChild.height - - return '<{}: Start:{} Height:{} L:{} R:{}>'.format( - self.__class__.__name__, - self.position, - self.height, - lcHeight, - rcHeight - ) + rch = self.rightChild.height + + cn = self.__class__.__name__ + ps = self.position + ht = self.height + + return f'<{cn}: Start:{ps} Height:{ht} L:{lch} R:{rch}>' def moveAttributes(self, other): ''' diff --git a/music21/tree/node.py b/music21/tree/node.py index c9f2d2948..9b4aaba39 100644 --- a/music21/tree/node.py +++ b/music21/tree/node.py @@ -181,13 +181,14 @@ def __repr__(self): pos = self.position if hasattr(pos, 'shortRepr'): pos = pos.shortRepr() - return ''.format( - pos, - self.subtreeElementsStartIndex, - self.payloadElementIndex, - self.subtreeElementsStopIndex, - self.payload, - ) + + ps = pos + li = self.subtreeElementsStartIndex + pi = self.payloadElementIndex + ri = self.subtreeElementsStopIndex + pl = self.payload + + return f'' # PROPERTIES # @property diff --git a/music21/tree/timespanTree.py b/music21/tree/timespanTree.py index 0065a3f78..78a791c11 100644 --- a/music21/tree/timespanTree.py +++ b/music21/tree/timespanTree.py @@ -196,8 +196,7 @@ def index(self, span): >>> tsTree.insert(ts) >>> for timespan in ts: - ... print("%r %s" % (timespan, tsTree.index(timespan))) - ... + ... print(f'{timespan!r} {tsTree.index(timespan)}') 0 1 2 @@ -373,8 +372,7 @@ def iterateConsonanceBoundedVerticalities(self): ... for verticality in subsequence: ... verticalityChord = verticality.toChord() ... print(f'\t[{verticality.measureNumber}] ' - ... + f'{verticality}: {verticalityChord.isConsonant()}') - ... + ... f'{verticality}: {verticalityChord.isConsonant()}') Subsequence: [2] : True [2] : False @@ -651,8 +649,7 @@ def splitAt(self, offsets): () >>> for timespan in scoreTree.elementsOverlappingOffset(0.1): - ... print("%r, %s" % (timespan, timespan.part.id)) - ... + ... print(f'{timespan!r}, {timespan.part.id}') >, Soprano >, Tenor >, Bass @@ -663,8 +660,7 @@ def splitAt(self, offsets): >>> scoreTree.splitAt(0.1) >>> for timespan in scoreTree.elementsStartingAt(0.1): - ... print("%r, %s" % (timespan, timespan.part.id)) - ... + ... print(f'{timespan!r}, {timespan.part.id}') >, Soprano >, Tenor >, Bass @@ -718,7 +714,7 @@ def unwrapVerticalities(verticalities): ... print(part) ... horizontality = unwrapped[part] ... for timespan in horizontality: - ... print('\t%r' % timespan) + ... print(f'\t{timespan!r}') ... > diff --git a/music21/tree/toStream.py b/music21/tree/toStream.py index 38d617f99..6aea30cb1 100644 --- a/music21/tree/toStream.py +++ b/music21/tree/toStream.py @@ -94,7 +94,8 @@ def chordified(timespans, templateStream=None): # pragma: no cover if quarterLength < 0: raise TreeException( 'Something is wrong with the verticality ' - + f'{vert!r} its endTime {endTime:f} is less than its offset {offset:f}') + f'{vert!r} its endTime {endTime:f} is less than its offset {offset:f}' + ) element = vert.makeElement(quarterLength) measureList[measureIndex].append(element) return outputStream @@ -107,7 +108,8 @@ def chordified(timespans, templateStream=None): # pragma: no cover if quarterLength < 0: raise TreeException( 'Something is wrong with the verticality ' - + f'{vert!r}, its endTime {endTime:f} is less than its offset {offset:f}') + f'{vert!r}, its endTime {endTime:f} is less than its offset {offset:f}' + ) element = vert.makeElement(quarterLength) elements.append(element) diff --git a/music21/tree/trees.py b/music21/tree/trees.py index 07a8f04f8..840f4553e 100644 --- a/music21/tree/trees.py +++ b/music21/tree/trees.py @@ -16,7 +16,6 @@ from __future__ import annotations from math import inf -import typing as t import unittest import weakref @@ -439,7 +438,6 @@ def _updateParents(self, oldPosition, visitedParents=None): if parent is None or parent in visitedParents: continue visitedParents.add(parent) - parentPosition = parent.offset parent._removeElementAtPosition(self, oldPosition) # Trees don't have offsets currently raise NotImplementedError @@ -928,8 +926,10 @@ def __contains__(self, element): ''' try: offset = element.offset - except AttributeError: - raise ElementTreeException('element must be a Music21Object, i.e., must have offset') + except AttributeError as ae: + raise ElementTreeException( + 'element must be a Music21Object, i.e., must have offset' + ) from ae candidates = self.elementsStartingAt(offset) if element in candidates: return True diff --git a/music21/variant.py b/music21/variant.py index 78bd8f9ff..1ac59377a 100644 --- a/music21/variant.py +++ b/music21/variant.py @@ -24,7 +24,6 @@ from collections.abc import Sequence import copy import difflib -import typing as t import unittest from music21 import base @@ -93,9 +92,11 @@ class Variant(base.Music21Object): # this copies the init of Streams def __init__( self, - givenElements: t.Union[None, - base.Music21Object, - Sequence[base.Music21Object]] = None, + givenElements: ( + None + | base.Music21Object + | Sequence[base.Music21Object] + ) = None, name: str|None = None, givenElementsBehavior: GivenElementsBehavior = GivenElementsBehavior.OFFSETS, **music21ObjectKeywords, diff --git a/music21/vexflow/toMusic21j.py b/music21/vexflow/toMusic21j.py index 62ad8775b..f02da97dd 100644 --- a/music21/vexflow/toMusic21j.py +++ b/music21/vexflow/toMusic21j.py @@ -241,8 +241,8 @@ def getHTML(self, dataSplit, title=None, defaults=None): loadM21Formatted = self.getLoadTemplate(d) jsBodyScript = self.getJSBodyScript(dataSplit, d) formatted = self.templateHtml.format(title=title, - loadM21Template=loadM21Formatted, - jsBodyScript=jsBodyScript) + loadM21Template=loadM21Formatted, + jsBodyScript=jsBodyScript) return formatted def fromStream(self, thisStream, mode=None): diff --git a/music21/voiceLeading.py b/music21/voiceLeading.py index 95d3787eb..ca3b11334 100644 --- a/music21/voiceLeading.py +++ b/music21/voiceLeading.py @@ -202,11 +202,11 @@ def key(self, keyValue): isKey = (isinstance(keyValue, key.Key)) if isKey is False: raise AttributeError - except AttributeError: # pragma: no cover + except AttributeError as ae: # pragma: no cover raise VoiceLeadingQuartetException( 'got a key signature that is not a string or music21 Key ' - + f'object: {keyValue}' - ) + f'object: {keyValue}' + ) from ae self._key = keyValue def _setVoiceNote( @@ -1979,7 +1979,7 @@ def _getMelodicIntervals(self): tempListOne = self.noteList[:-1] tempListTwo = self.noteList[1:] melodicIntervalList = [] - for n1, n2 in zip(tempListOne, tempListTwo): + for n1, n2 in zip(tempListOne, tempListTwo, strict=True): if n1 and n2: melodicIntervalList.append(interval.Interval(n1, n2)) else: @@ -2355,7 +2355,7 @@ def __init__(self, chordList=(), **keywords): self._chordList.append(value) # else: # raise NChordLinearSegmentException( - # 'not a valid chord specification: %s' % value) + # f'not a valid chord specification: {value}') except AttributeError as e: # pragma: no cover raise NChordLinearSegmentException( f'not a valid chord specification: {value!r}' diff --git a/music21/volume.py b/music21/volume.py index 1b45fb315..f1415df45 100644 --- a/music21/volume.py +++ b/music21/volume.py @@ -141,10 +141,11 @@ def mergeAttributes(self, other): def getRealizedStr(self, useDynamicContext: dynamics.Dynamic|bool = True, useVelocity=True, - useArticulations: t.Union[bool, - articulations.Articulation, - Iterable[articulations.Articulation] - ] = True, + useArticulations: ( + bool + | articulations.Articulation + | Iterable[articulations.Articulation] + ) = True, baseLevel=0.5, clip=True): ''' @@ -165,9 +166,11 @@ def getRealized( self, useDynamicContext: bool|dynamics.Dynamic = True, useVelocity=True, - useArticulations: t.Union[ - bool, articulations.Articulation, Iterable[articulations.Articulation] - ] = True, + useArticulations: ( + bool + | articulations.Articulation + | Iterable[articulations.Articulation] + ) = True, baseLevel=0.5, clip=True, ): diff --git a/pyproject.toml b/pyproject.toml index d16731c5f..d5d6be4c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,3 +70,50 @@ include = [ "music21", "py.typed", # indicates source is typed. ] + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +# Flake8, pycodestyle, flake8-quotes + pyflakes +select = ['E', 'W', 'Q', 'F', 'B'] + +dummy-variable-rgx = "^(_.*|unused.*|i|j|counter|junk|dummy)" + +# add once allowed: E122 (continuation line missing indentation) and E124 (bracket align) + +ignore = [ + # space around | -- nice to omit in int|str + 'E227', + # 0 blank lines -- good test but something going wrong in new algorithm + 'E301', + # blank lines + 'E302', + # blank lines + 'E303', + # do not assign lambda -- okay by me. + 'E731', + # except and then raise without from -- should do soon. + 'B904', + # zip without strict= ... nice to turn this off someday, when code more mature + 'B905', +] + +[tool.ruff.lint.per-file-ignores] +'music21/abcFormat/testFiles.py' = ['E501'] # line-too-long +# when allowed again -- chord/tables omit E122, E127 +'music21/chord/tables.py' = ['E201', 'E202', 'E203', 'E221', 'E231', 'E241'] +'music21/common/__init__.py' = ['F403'] +'music21/features/__init__.py' = ['F403'] +'music21/humdrum/__init__.py' = ['E501'] # line-too-long +'music21/humdrum/testFiles.py' = ['E501'] # line-too-long +'music21/musicxml/testFiles.py' = ['E501'] # line-too-long +'music21/musicxml/testPrimitive.py' = ['E501'] # line-too-long +'music21/romanText/testFiles.py' = ['E501'] # line-too-long +'music21/search/__init__.py' = ['F403', 'F405'] # 405--star exports fine +'music21/variant.py' = ['E501'] # line-too-long + +[tool.ruff.lint.flake8-quotes] +inline-quotes = 'single' +multiline-quotes = 'single' +docstring-quotes = 'single'