1414"""
1515
1616import os
17- import sys
1817import wave
1918import struct
2019import os .path
2524from collections import OrderedDict , namedtuple
2625from base64 import standard_b64encode as base64encode
2726from base64 import standard_b64decode as base64decode
27+ from typing import Any , BinaryIO , Callable , Iterable , Tuple , Union
2828
2929import logging
3030log = logging .Logger (__name__ )
3131
32- if sys .version_info [0 ] > 2 :
33- unicode = str
34- basestring = str
35-
3632
3733__version__ = '1.0.16.dev0'
3834
@@ -70,7 +66,7 @@ class tzoffset(tzinfo):
7066 """
7167
7268 def __init__ (self , offset ):
73- if isinstance (offset , basestring ):
69+ if isinstance (offset , str ):
7470 # offset as ISO string '-07:00', '-0700', or '-07' format
7571 if len (offset ) < 4 :
7672 vals = offset , '00' # eg '-07'
@@ -98,7 +94,7 @@ def __repr__(self):
9894 return self .tzname (None )
9995
10096
101- def parse_timestamp (s ):
97+ def parse_timestamp (s ) -> datetime :
10298 """
10399 Parse a string in supported subset of ISO 8601 / RFC 3331 format to :class:`datetime.datetime`.
104100 The timestamp will be timezone-aware of a TZ is specified, or timezone-naive if in "local" fmt.
@@ -183,7 +179,7 @@ class GuanoFile(object):
183179 'Timestamp' : lambda value : value .isoformat () if value else '' ,
184180 }
185181
186- def __init__ (self , file = None , strict = False ):
182+ def __init__ (self , file : Union [ str , BinaryIO ] = None , strict = False ):
187183 """
188184 Create a GuanoFile instance which represents a single file's GUANO metadata.
189185 If the file already contains GUANO metadata, it will be parsed immediately. If not, then
@@ -199,12 +195,12 @@ def __init__(self, file=None, strict=False):
199195 :raises ValueError: if the specified file doesn't represent a valid .WAV or if its
200196 existing GUANO metadata is broken
201197 """
202- if isinstance (file , basestring ):
198+ if isinstance (file , str ):
203199 self .filename = file
204200 self ._file = None
205201 else :
206202 self .filename = file .name if hasattr (file , 'name' ) else None
207- self ._file = file # a file-like object
203+ self ._file : BinaryIO = file # a file-like object
208204
209205 self .strict_mode = strict
210206
@@ -218,7 +214,7 @@ def __init__(self, file=None, strict=False):
218214 if self ._file or (self .filename and os .path .isfile (self .filename )):
219215 self ._load ()
220216
221- def _coerce (self , key , value ) :
217+ def _coerce (self , key : str , value : str ) -> Any :
222218 """Coerce a value from its Unicode representation to a specific data type"""
223219 if key in self ._coersion_rules :
224220 try :
@@ -230,9 +226,9 @@ def _coerce(self, key, value):
230226 log .warning ('Failed coercing "%s": %s' , key , e )
231227 return value # default should already be a Unicode string
232228
233- def _serialize (self , key , value ) :
229+ def _serialize (self , key : str , value : Any ) -> str :
234230 """Serialize a value from its real representation to GUANO Unicode representation"""
235- serialize = self ._serialization_rules .get (key , unicode )
231+ serialize = self ._serialization_rules .get (key , str )
236232 try :
237233 return serialize (value )
238234 except (ValueError , TypeError ) as e :
@@ -292,7 +288,7 @@ def _load(self):
292288
293289 def _parse (self , metadata_str ):
294290 """Parse metadata and populate our internal mappings"""
295- if not isinstance (metadata_str , unicode ):
291+ if not isinstance (metadata_str , str ):
296292 try :
297293 metadata_str = metadata_str .decode ('utf-8' )
298294 except UnicodeDecodeError as e :
@@ -314,7 +310,7 @@ def _parse(self, metadata_str):
314310 return self
315311
316312 @classmethod
317- def from_string (cls , metadata_str , * args , ** kwargs ):
313+ def from_string (cls , metadata_str , * args , ** kwargs ) -> 'GuanoFile' :
318314 """
319315 Create a :class:`GuanoFile` instance from a GUANO metadata string
320316
@@ -328,7 +324,7 @@ def from_string(cls, metadata_str, *args, **kwargs):
328324 return GuanoFile (* args , ** kwargs )._parse (metadata_str )
329325
330326 @classmethod
331- def register (cls , namespace , keys , coerce_function , serialize_function = str ):
327+ def register (cls , namespace : str , keys : Union [ str , Iterable [ str ]], coerce_function : Callable , serialize_function : Callable = str ):
332328 """
333329 Configure the GUANO parser to recognize new namespaced keys.
334330
@@ -339,14 +335,14 @@ def register(cls, namespace, keys, coerce_function, serialize_function=str):
339335 :param serialize_function: an optional function for serializing the value to UTF-8 string
340336 :type serialize_function: callable
341337 """
342- if isinstance (keys , basestring ):
338+ if isinstance (keys , str ):
343339 keys = [keys ]
344340 for k in keys :
345341 full_key = namespace + '|' + k if namespace else k
346342 cls ._coersion_rules [full_key ] = coerce_function
347343 cls ._serialization_rules [full_key ] = serialize_function
348344
349- def _split_key (self , item ):
345+ def _split_key (self , item ) -> Tuple [ str , str ] :
350346 if isinstance (item , tuple ):
351347 namespace , key = item [0 ], item [1 ]
352348 elif '|' in item :
@@ -355,11 +351,11 @@ def _split_key(self, item):
355351 namespace , key = '' , item
356352 return namespace , key
357353
358- def __getitem__ (self , item ):
354+ def __getitem__ (self , item ) -> Any :
359355 namespace , key = self ._split_key (item )
360356 return self ._md [namespace ][key ]
361357
362- def get (self , item , default = None ):
358+ def get (self , item , default = None ) -> Any :
363359 try :
364360 return self [item ]
365361 except KeyError :
@@ -375,7 +371,7 @@ def __setitem__(self, key, value):
375371 self ._md [namespace ] = {}
376372 self ._md [namespace ][key ] = value
377373
378- def __contains__ (self , item ):
374+ def __contains__ (self , item ) -> bool :
379375 namespace , key = self ._split_key (item )
380376 return namespace in self ._md and key in self ._md [namespace ]
381377
@@ -385,21 +381,20 @@ def __delitem__(self, key):
385381 if not self ._md [namespace ]:
386382 del self ._md [namespace ]
387383
388- def __bool__ (self ):
384+ def __bool__ (self ) -> bool :
389385 return bool (self ._md )
390- __nonzero__ = __bool__ # py2
391386
392- def __repr__ (self ):
393- return '%s(%s)' % (self .__class__ .__name__ , self ._file )
387+ def __repr__ (self ) -> str :
388+ return '%s(%s)' % (self .__class__ .__name__ , self .filename or self . _file )
394389
395- def get_namespaces (self ):
390+ def get_namespaces (self ) -> list :
396391 """
397392 Get list of all namespaces represented by this metadata.
398393 This includes the 'GUANO' namespace, and the '' (empty string) namespace for well-known fields.
399394 """
400- return self ._md .keys ()
395+ return list ( self ._md .keys () )
401396
402- def items (self , namespace = None ):
397+ def items (self , namespace : str = None ) -> Iterable [ Tuple [ str , Any ]] :
403398 """Iterate over (key, value) for entire metadata or for specified namespace of fields"""
404399 if namespace is not None :
405400 for k , v in self ._md [namespace ].items ():
@@ -410,17 +405,17 @@ def items(self, namespace=None):
410405 k = '%s|%s' % (namespace , k ) if namespace else k
411406 yield k , v
412407
413- def items_namespaced (self ):
408+ def items_namespaced (self ) -> Iterable [ Tuple [ str , str , Any ]] :
414409 """Iterate over (namespace, key, value) for entire metadata"""
415410 for namespace , data in self ._md .items ():
416411 for k , v in data .items ():
417412 yield namespace , k , v
418413
419- def well_known_items (self ):
414+ def well_known_items (self ) -> Iterable [ Tuple [ str , Any ]] :
420415 """Iterate over (key, value) for all the well-known (defined) fields"""
421416 return self .items ('' )
422417
423- def to_string (self ):
418+ def to_string (self ) -> str :
424419 """Represent the GUANO metadata as a Unicode string"""
425420 lines = []
426421 for namespace , data in self ._md .items ():
@@ -430,7 +425,7 @@ def to_string(self):
430425 lines .append (u'%s: %s' % (k , v ))
431426 return u'\n ' .join (lines )
432427
433- def serialize (self , pad = '\n ' ):
428+ def serialize (self , pad = '\n ' ) -> bytes :
434429 """Serialize the GUANO metadata as UTF-8 encoded bytes"""
435430 md_bytes = bytearray (self .to_string (), 'utf-8' )
436431 if pad is not None and len (md_bytes ) % 2 :
@@ -439,7 +434,7 @@ def serialize(self, pad='\n'):
439434 return md_bytes
440435
441436 @property
442- def wav_data (self ):
437+ def wav_data (self ) -> bytes :
443438 """Actual audio data from the wav `data` chunk. Lazily loaded and cached."""
444439 if not self ._wav_data_size :
445440 raise ValueError ()
@@ -452,7 +447,7 @@ def wav_data(self):
452447 return self ._wav_data
453448
454449 @wav_data .setter
455- def wav_data (self , data ):
450+ def wav_data (self , data : bytes ):
456451 self ._wav_data_size = len (data )
457452 self ._wav_data = data
458453
@@ -527,11 +522,5 @@ def __exit__(self, *excinfo):
527522 pass
528523
529524
530- # This ugly hack prevents a warning if application-level code doesn't configure logging
531- if sys .version_info [0 ] > 2 :
532- NullHandler = logging .NullHandler
533- else :
534- class NullHandler (logging .Handler ):
535- def emit (self , record ):
536- pass
537- log .addHandler (NullHandler ())
525+ # prevents a warning if application-level code doesn't configure logging
526+ log .addHandler (logging .NullHandler ())
0 commit comments