@@ -73,14 +73,11 @@ class Metadata(Generic[T]):
7373 [Root, Timestamp, Snapshot, Targets]. The purpose of this is to allow
7474 type checking of the signed attribute in code using Metadata::
7575
76- root_md = Metadata.from_file("root.json", signed_type=Root)
77- # root_md type is now Metadata[Root]. This means signed and its
76+ root_md = Root.metadata_from_file("root.json")
77+ print(root_md.signed.consistent_snapshot)
78+ # root_md type is now Metadata[Root]. This means root_md.signed and its
7879 # attributes like consistent_snapshot are now statically typed and the
7980 # types can be verified by static type checkers and shown by IDEs
80- print(root_md.signed.consistent_snapshot)
81-
82- Using the signed_type argument in factory constructors is not required but
83- not doing so means T is not a specific type so static typing cannot happen.
8481
8582 Attributes:
8683 signed: A subclass of Signed, which has the actual metadata payload,
@@ -147,7 +144,6 @@ def from_file(
147144 filename : str ,
148145 deserializer : Optional [MetadataDeserializer ] = None ,
149146 storage_backend : Optional [StorageBackendInterface ] = None ,
150- signed_type : Optional [Type [T ]] = None ,
151147 ) -> "Metadata[T]" :
152148 """Loads TUF metadata from file storage.
153149
@@ -159,7 +155,6 @@ def from_file(
159155 storage_backend: An object that implements
160156 securesystemslib.storage.StorageBackendInterface. Per default
161157 a (local) FilesystemBackend is used.
162- signed_type: Optional; Expected type of deserialized signed object.
163158
164159 Raises:
165160 securesystemslib.exceptions.StorageError: The file cannot be read.
@@ -174,21 +169,19 @@ def from_file(
174169 storage_backend = FilesystemBackend ()
175170
176171 with storage_backend .get (filename ) as f :
177- return Metadata .from_bytes (f .read (), deserializer , signed_type )
172+ return Metadata .from_bytes (f .read (), deserializer )
178173
179174 @staticmethod
180175 def from_bytes (
181176 data : bytes ,
182177 deserializer : Optional [MetadataDeserializer ] = None ,
183- signed_type : Optional [Type [T ]] = None ,
184178 ) -> "Metadata[T]" :
185179 """Loads TUF metadata from raw data.
186180
187181 Arguments:
188182 data: metadata content as bytes.
189183 deserializer: Optional; A MetadataDeserializer instance that
190184 implements deserialization. Default is JSONDeserializer.
191- signed_type: Optional; Expected type of deserialized signed object.
192185
193186 Raises:
194187 tuf.api.serialization.DeserializationError:
@@ -205,14 +198,7 @@ def from_bytes(
205198
206199 deserializer = JSONDeserializer ()
207200
208- md = deserializer .deserialize (data )
209-
210- # Ensure deserialized signed type matches the requested type
211- if signed_type is not None and signed_type != type (md .signed ):
212- raise DeserializationError (
213- f"Expected { signed_type } , got { type (md .signed )} "
214- )
215- return md
201+ return deserializer .deserialize (data )
216202
217203 def to_dict (self ) -> Dict [str , Any ]:
218204 """Returns the dict representation of self."""
@@ -364,6 +350,60 @@ def to_dict(self) -> Dict[str, Any]:
364350 """Serialization helper that returns dict representation of self"""
365351 raise NotImplementedError
366352
353+ @classmethod
354+ @abc .abstractmethod
355+ def metadata_from_bytes (
356+ cls ,
357+ data : bytes ,
358+ deserializer : Optional [MetadataDeserializer ] = None ,
359+ ) -> Metadata :
360+ """Loads a Metadata object from bytes.
361+
362+ Like Metadata.from_bytes() but also raises DeserializationError if
363+ bytes does not contain the correct metadata type."""
364+ raise NotImplementedError
365+
366+ @classmethod
367+ @abc .abstractmethod
368+ def metadata_from_file (
369+ cls ,
370+ filename : str ,
371+ deserializer : Optional [MetadataDeserializer ] = None ,
372+ storage_backend : Optional [StorageBackendInterface ] = None ,
373+ ) -> Metadata :
374+ """Loads a Metadata object from file.
375+
376+ Like Metadata.from_file() but also raises DeserializationError if
377+ file does not contain the correct metadata type."""
378+ raise NotImplementedError
379+
380+ @classmethod
381+ def _metadata_from_bytes (
382+ cls , data : bytes , deserializer : Optional [MetadataDeserializer ]
383+ ) -> Metadata :
384+ """Like Metadata.from_bytes() but raises on wrong type"""
385+ metadata = Metadata .from_bytes (data , deserializer )
386+ if not isinstance (metadata .signed , cls ):
387+ raise DeserializationError (
388+ f"Expected { cls } , got { type (metadata .signed )} "
389+ )
390+ return metadata
391+
392+ @classmethod
393+ def _metadata_from_file (
394+ cls ,
395+ filename : str ,
396+ deserializer : Optional [MetadataDeserializer ],
397+ storage_backend : Optional [StorageBackendInterface ],
398+ ) -> Metadata :
399+ """Like Metadata.from_file() but raises on wrong type"""
400+ metadata = Metadata .from_file (filename , deserializer , storage_backend )
401+ if not isinstance (metadata .signed , cls ):
402+ raise DeserializationError (
403+ f"Expected { cls } , got { type (metadata .signed )} "
404+ )
405+ return metadata
406+
367407 @classmethod
368408 @abc .abstractmethod
369409 def from_dict (cls , signed_dict : Dict [str , Any ]) -> "Signed" :
@@ -632,6 +672,31 @@ def __init__(
632672 self .keys = keys
633673 self .roles = roles
634674
675+ @classmethod
676+ def metadata_from_bytes (
677+ cls ,
678+ data : bytes ,
679+ deserializer : Optional [MetadataDeserializer ] = None ,
680+ ) -> Metadata ["Root" ]:
681+ """Loads a Metadata[Root] from raw data.
682+
683+ Like Metadata.from_bytes() but also raises DeserializationError if
684+ bytes does not contain root metadata."""
685+ return cls ._metadata_from_bytes (data , deserializer )
686+
687+ @classmethod
688+ def metadata_from_file (
689+ cls ,
690+ filename : str ,
691+ deserializer : Optional [MetadataDeserializer ] = None ,
692+ storage_backend : Optional [StorageBackendInterface ] = None ,
693+ ) -> Metadata ["Root" ]:
694+ """Loads a Metadata[Root] from file.
695+
696+ Like Metadata.from_file() but also raises DeserializationError if file
697+ does not contain root metadata."""
698+ return cls ._metadata_from_file (filename , deserializer , storage_backend )
699+
635700 @classmethod
636701 def from_dict (cls , signed_dict : Dict [str , Any ]) -> "Root" :
637702 """Creates Root object from its dict representation."""
@@ -846,6 +911,31 @@ def from_dict(cls, signed_dict: Dict[str, Any]) -> "Timestamp":
846911 # All fields left in the timestamp_dict are unrecognized.
847912 return cls (* common_args , meta , signed_dict )
848913
914+ @classmethod
915+ def metadata_from_bytes (
916+ cls ,
917+ data : bytes ,
918+ deserializer : Optional [MetadataDeserializer ] = None ,
919+ ) -> Metadata ["Timestamp" ]:
920+ """Loads a Metadata[Timestamp] from raw data.
921+
922+ Like Metadata.from_bytes() but also raises DeserializationError if
923+ bytes does not contain timestamp metadata."""
924+ return cls ._metadata_from_bytes (data , deserializer )
925+
926+ @classmethod
927+ def metadata_from_file (
928+ cls ,
929+ filename : str ,
930+ deserializer : Optional [MetadataDeserializer ] = None ,
931+ storage_backend : Optional [StorageBackendInterface ] = None ,
932+ ) -> Metadata ["Timestamp" ]:
933+ """Loads a Metadata[Timestamp] from file.
934+
935+ Like Metadata.from_file() but also raises DeserializationError if file
936+ does not contain timestamp metadata."""
937+ return cls ._metadata_from_file (filename , deserializer , storage_backend )
938+
849939 def to_dict (self ) -> Dict [str , Any ]:
850940 """Returns the dict representation of self."""
851941 res_dict = self ._common_fields_to_dict ()
@@ -898,6 +988,31 @@ def from_dict(cls, signed_dict: Dict[str, Any]) -> "Snapshot":
898988 # All fields left in the snapshot_dict are unrecognized.
899989 return cls (* common_args , meta , signed_dict )
900990
991+ @classmethod
992+ def metadata_from_bytes (
993+ cls ,
994+ data : bytes ,
995+ deserializer : Optional [MetadataDeserializer ] = None ,
996+ ) -> Metadata ["Snapshot" ]:
997+ """Loads a Metadata[Snapshot] from raw data.
998+
999+ Like Metadata.from_bytes() but also raises DeserializationError if
1000+ bytes does not contain snapshot metadata."""
1001+ return cls ._metadata_from_bytes (data , deserializer )
1002+
1003+ @classmethod
1004+ def metadata_from_file (
1005+ cls ,
1006+ filename : str ,
1007+ deserializer : Optional [MetadataDeserializer ] = None ,
1008+ storage_backend : Optional [StorageBackendInterface ] = None ,
1009+ ) -> Metadata ["Snapshot" ]:
1010+ """Loads a Metadata[Snapshot] from file.
1011+
1012+ Like Metadata.from_file() but also raises DeserializationError if file
1013+ does not contain snapshot metadata."""
1014+ return cls ._metadata_from_file (filename , deserializer , storage_backend )
1015+
9011016 def to_dict (self ) -> Dict [str , Any ]:
9021017 """Returns the dict representation of self."""
9031018 snapshot_dict = self ._common_fields_to_dict ()
@@ -1161,6 +1276,31 @@ def from_dict(cls, signed_dict: Dict[str, Any]) -> "Targets":
11611276 # All fields left in the targets_dict are unrecognized.
11621277 return cls (* common_args , res_targets , delegations , signed_dict )
11631278
1279+ @classmethod
1280+ def metadata_from_bytes (
1281+ cls ,
1282+ data : bytes ,
1283+ deserializer : Optional [MetadataDeserializer ] = None ,
1284+ ) -> Metadata ["Targets" ]:
1285+ """Loads a Metadata[Targets] from raw data.
1286+
1287+ Like Metadata.from_bytes() but also raises DeserializationError if
1288+ bytes does not contain targets metadata."""
1289+ return cls ._metadata_from_bytes (data , deserializer )
1290+
1291+ @classmethod
1292+ def metadata_from_file (
1293+ cls ,
1294+ filename : str ,
1295+ deserializer : Optional [MetadataDeserializer ] = None ,
1296+ storage_backend : Optional [StorageBackendInterface ] = None ,
1297+ ) -> Metadata ["Targets" ]:
1298+ """Loads a Metadata[Targets] from file.
1299+
1300+ Like Metadata.from_file() but also raises DeserializationError if file
1301+ does not contain targets metadata."""
1302+ return cls ._metadata_from_file (filename , deserializer , storage_backend )
1303+
11641304 def to_dict (self ) -> Dict [str , Any ]:
11651305 """Returns the dict representation of self."""
11661306 targets_dict = self ._common_fields_to_dict ()
0 commit comments