3737
3838if t .TYPE_CHECKING :
3939 from collections .abc import Iterable
40+ from music21 .metadata import Metadata
4041
4142
4243# -----------------------------------------------------------------------------
@@ -64,7 +65,8 @@ class MetadataEntry(prebase.ProtoM21Object):
6465 <music21.metadata.bundles.MetadataEntry 'bach_bwv66_6_mxl'>
6566
6667 The sourcePath of the metadata entry refers to the file path at which its
67- score file is found:
68+ score file is found, but it is usually a relative path to the top of the corpus
69+ directory. It is a pathlib object.
6870
6971 >>> metadataEntry.sourcePath
7072 PosixPath('bach/bwv66.6.mxl')
@@ -85,21 +87,28 @@ class MetadataEntry(prebase.ProtoM21Object):
8587 # INITIALIZER #
8688
8789 def __init__ (self ,
88- sourcePath = None ,
89- number = None ,
90- metadataPayload = None ,
91- corpusName = None ,
90+ sourcePath : str | pathlib . Path = '' ,
91+ number : int | None = None ,
92+ metadataPayload : Metadata | None = None ,
93+ corpusName : str = '' ,
9294 ):
93- self ._sourcePath = str (sourcePath )
95+ # only store strings internally; sourcePath is s
96+ self ._sourcePath : str = str (sourcePath )
9497 self ._number = number
9598 self ._metadataPayload = metadataPayload
9699 self ._corpusName = corpusName
97100
98101 # SPECIAL METHODS #
99-
100102 def __getnewargs__ (self ):
103+ '''
104+ This is all the information that is needed for pickling.
105+ Specifically do not include _corpusName.
106+
107+ Note do not pickle Pathlib objects, so make sure to do ._sourcePath
108+ not .sourcePath
109+ '''
101110 return (
102- self .sourcePath ,
111+ self ._sourcePath ,
103112 self .metadata ,
104113 self .number ,
105114 )
@@ -109,7 +118,8 @@ def _reprInternal(self):
109118
110119 def __fspath__ (self ):
111120 '''
112- for Py3.6+ to allow MetadataEntries to be used where file paths are being employed
121+ Allows MetadataEntries to be used where file paths are being employed,
122+ so it can be used with opening and closing, etc.
113123
114124 Returns self.sourcePath() as a string
115125
@@ -138,8 +148,14 @@ def search(self, query=None, field=None, **keywords):
138148
139149 # PUBLIC PROPERTIES #
140150
151+ # we seem to be storing everything as a property so that the object
152+ # is immutable after creating.
153+
141154 @property
142155 def corpusPath (self ):
156+ '''
157+ Returns the sourcePath as a string, with _number appended if it is not None.
158+ '''
143159 return MetadataBundle .corpusPathToKey (self .sourcePath , self .number )
144160
145161 @property
@@ -638,7 +654,7 @@ def corpus(self, newCorpus):
638654 self ._corpus = common .wrapWeakref (newCorpus )
639655
640656 @property
641- def filePath (self ):
657+ def filePath (self ) -> pathlib . Path | None :
642658 r'''
643659 The filesystem name of the cached metadata bundle, if the metadata
644660 bundle's name is not None.
@@ -829,7 +845,7 @@ def clear(self):
829845 self ._metadataEntries .clear ()
830846
831847 @staticmethod
832- def corpusPathToKey (filePath , number = None ):
848+ def corpusPathToKey (filePath : str | pathlib . Path , number : int | None = None ):
833849 r'''
834850 Given a file path or corpus path, return the metadata key:
835851
@@ -841,6 +857,12 @@ def corpusPathToKey(filePath, number=None):
841857 >>> key = mb.corpusPathToKey('corelli/opus3no1/1grave.xml')
842858 >>> key.endswith('corelli_opus3no1_1grave_xml')
843859 True
860+
861+ Numbers are appended if given
862+
863+ >>> key = mb.corpusPathToKey('corelli/opus3no1/1grave.xml', number=3)
864+ >>> key.endswith('corelli_opus3no1_1grave_xml_3')
865+ True
844866 '''
845867 if isinstance (filePath , pathlib .Path ):
846868 try :
@@ -866,6 +888,7 @@ def corpusPathToKey(filePath, number=None):
866888 corpusPath = corpusPath .replace (os .sep , '_' )
867889
868890 corpusPath = corpusPath .replace ('.' , '_' )
891+
869892 # append name to metadata path
870893 if number is not None :
871894 return f'{ corpusPath } _{ number } '
@@ -1315,18 +1338,38 @@ def write(self, filePath=None):
13151338 filePath = filePath or self .filePath
13161339 if self .filePath is not None :
13171340 filePath = self .filePath
1341+
13181342 environLocal .printDebug (['MetadataBundle: writing:' , filePath ])
13191343 storedCorpusClient = self ._corpus # no weakrefs allowed
13201344 self ._corpus = None
1321- uncompressed = pickle .dumps (self , protocol = 3 )
1322- # 3 is a safe protocol for some time to come.
1345+
1346+ # Protocol 5 is Python 3.8 and above.
1347+ uncompressed = pickle .dumps (self , protocol = 5 )
1348+
1349+ # # uncomment this and SafePickler when we next need to
1350+ # # figure out where Pathlib objects are sneaking into
1351+ # # the pickle -- we've had to rediagnose this too many times
1352+ # # to count.
1353+ # with open(filePath, 'wb') as f:
1354+ # SafePickler(f, protocol=5).dump(self)
1355+ #
1356+ # with open(filePath, 'rb') as f:
1357+ # uncompressed = f.read() # ridiculous...
13231358
13241359 with gzip .open (filePath , 'wb' ) as outFp :
13251360 outFp .write (uncompressed )
13261361 self ._corpus = storedCorpusClient
13271362
13281363 return self
13291364
1365+ # class SafePickler(pickle.Pickler):
1366+ # def persistent_id(self, obj):
1367+ # if isinstance(obj, pathlib.Path):
1368+ # raise TypeError(f'Pickling pathlib.Path objects is not allowed! {obj}')
1369+ # else:
1370+ # print(obj)
1371+ # return None # Default behavior for other objects
1372+
13301373
13311374_test_bundles : dict [str , MetadataBundle ] = {}
13321375
0 commit comments