1313from pathlib import Path
1414import hashlib
1515import logging
16- import attrs
1716from .utils import (
1817 classproperty ,
1918 fspaths_converter ,
5049logger = logging .getLogger ("fileformats" )
5150
5251
53- @attrs .define (repr = False )
5452class FileSet (DataType ):
5553 """
5654 The base class for all format types within the fileformats package. A generic
@@ -67,55 +65,45 @@ class FileSet(DataType):
6765 extras hook
6866 """
6967
70- fspaths : ty .FrozenSet [Path ] = attrs .field (default = None , converter = fspaths_converter )
71- _metadata : ty .Optional [ty .Dict [str , ty .Any ]] = attrs .field (
72- default = False ,
73- eq = False ,
74- order = False ,
75- )
76-
77- @_metadata .validator
78- def metadata_validator (self , _ , val ):
79- if val and not isinstance (val , dict ):
80- raise TypeError (
81- f"Fileset metadata value needs to be None or dict, not { val } ({ self .fspaths } )"
82- )
83-
84- # Explicitly set the Internet Assigned Numbers Authority (https://iana_mime.org) MIME
85- # type to None for any base classes that should not correspond to a MIME or MIME-like
86- # type.
87- iana_mime = None
88- ext = None
89- alternate_exts = ()
68+ # Class attributes
9069
9170 # Store converters registered by @converter decorator that convert to FileSet
9271 # NB: each class will have its own version of this dictionary
9372 converters = {}
9473
74+ # differentiate between Field and other DataType classes
9575 is_fileset = True
9676
97- def __hash__ (self ):
98- return hash (self .fspaths )
77+ # File extensions associated with file format
78+ ext = None
79+ alternate_exts = ()
9980
100- def __repr__ (self ):
101- return f"{ self .type_name } ('" + "', '" .join (str (p ) for p in self .fspaths ) + "')"
81+ # to be overridden in subclasses
82+ # Explicitly set the Internet Assigned Numbers Authority (https://iana_mime.org) MIME
83+ # type to None for any base classes that should not correspond to a MIME or MIME-like
84+ # type.
85+ iana_mime = None
10286
103- def __getitem__ (self , name ):
104- return self .metadata [name ]
87+ # Member attributes
88+ fspaths : ty .FrozenSet [Path ]
89+ _metadata : ty .Union [ty .Dict [str , ty .Any ], bool , None ]
10590
106- def __attrs_post_init__ (self ):
107- # Check required properties don't raise errors
108- for prop_name in self .required_properties ():
109- getattr (self , prop_name )
110- # Loop through all attributes and find methods marked by CHECK_ANNOTATION
111- for check in self .checks ():
112- getattr (self , check )()
91+ def __init__ (self , fspaths , metadata = False ):
92+ self ._validate_class ()
93+ self .fspaths = fspaths_converter (fspaths )
94+ self ._validate_fspaths ()
95+ self ._additional_fspaths ()
96+ if metadata and not isinstance (metadata , dict ):
97+ raise TypeError (
98+ f"Fileset metadata value needs to be None or dict, not { metadata } ({ self } )"
99+ )
100+ self ._metadata = metadata
101+ self ._validate_properties ()
113102
114- @fspaths .validator
115- def validate_fspaths (self , _ , fspaths ):
116- if not fspaths :
103+ def _validate_fspaths (self ):
104+ if not self .fspaths :
117105 raise FileFormatsError (f"No file-system paths provided to { self } " )
118- missing = [p for p in fspaths if not p or not p .exists ()]
106+ missing = [p for p in self . fspaths if not p or not p .exists ()]
119107 if missing :
120108 missing_str = "\n " .join (str (p ) for p in missing )
121109 msg = (
@@ -134,6 +122,36 @@ def validate_fspaths(self, _, fspaths):
134122 msg += "\n " .join (str (p ) for p in parent .iterdir ())
135123 raise FileNotFoundError (msg )
136124
125+ def _validate_class (self ):
126+ """Check that the class has been correctly defined"""
127+
128+ def _additional_fspaths (self ):
129+ """Additional checks to be performed on the file-system paths provided to the"""
130+
131+ def _validate_properties (self ):
132+ # Check required properties don't raise errors
133+ for prop_name in self .required_properties ():
134+ getattr (self , prop_name )
135+ # Loop through all attributes and find methods marked by CHECK_ANNOTATION
136+ for check in self .checks ():
137+ getattr (self , check )()
138+
139+ def __eq__ (self , other ) -> bool :
140+ return (
141+ isinstance (other , FileSet )
142+ and self .mime_like == other .mime_like
143+ and self .fspaths == other .fspaths
144+ )
145+
146+ def __ne__ (self , other ) -> bool :
147+ return not self .__eq__ (other )
148+
149+ def __hash__ (self ) -> int :
150+ return hash ((self .mime_like , self .fspaths ))
151+
152+ def __repr__ (self ) -> str :
153+ return f"{ self .type_name } ('" + "', '" .join (str (p ) for p in self .fspaths ) + "')"
154+
137155 @property
138156 def parent (self ) -> Path :
139157 "A common parent directory for all the top-level paths in the file-set"
@@ -1558,20 +1576,21 @@ def copy_to(self, *args, **kwargs):
15581576 _formats_by_name = None
15591577
15601578
1561- @attrs .define (slots = False , repr = False )
15621579class MockMixin :
15631580 """Strips out validation methods of a class, allowing it to be mocked in a way that
15641581 still satisfies type-checking"""
15651582
15661583 # Mirror fspaths here so we can unset its validator
1567- fspaths : ty .FrozenSet [Path ] = attrs .field (default = None , converter = fspaths_converter )
1584+ fspaths : ty .FrozenSet [Path ]
1585+
1586+ def _additional_fspaths (self ):
1587+ pass # disable implicit addition of related fspaths
15681588
1569- def __attrs_post_init__ (self ):
1570- pass
1589+ def _validate_fspaths (self ):
1590+ pass # disable validation of fspaths
15711591
1572- @fspaths .validator
1573- def validate_fspaths (self , _ , fspaths ):
1574- pass
1592+ def _validate_properties (self ):
1593+ pass # disable validation of properties
15751594
15761595 @classproperty
15771596 def type_name (cls ):
0 commit comments