-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #85 from ArcanaFramework/typed-collection
Added `TypedCollection` base class
- Loading branch information
Showing
10 changed files
with
270 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import typing as ty | ||
from pathlib import Path | ||
from abc import ABCMeta, abstractproperty | ||
from fileformats.core import FileSet, validated_property, mtime_cached_property | ||
from fileformats.core.decorators import classproperty | ||
from fileformats.core.exceptions import FormatMismatchError | ||
from fileformats.core.utils import get_optional_type | ||
|
||
|
||
class TypedCollection(FileSet, metaclass=ABCMeta): | ||
"""Base class for collections of files-sets of specific types either in a directory | ||
or a collection of file paths""" | ||
|
||
content_types: ty.Tuple[ | ||
ty.Union[ty.Type[FileSet], ty.Type[ty.Optional[FileSet]]], ... | ||
] = () | ||
|
||
@abstractproperty | ||
def content_fspaths(self) -> ty.Iterable[Path]: | ||
... # noqa: E704 | ||
|
||
@mtime_cached_property | ||
def contents(self) -> ty.List[FileSet]: | ||
contnts = [] | ||
for content_type in self.potential_content_types: | ||
assert content_type | ||
for p in self.content_fspaths: | ||
try: | ||
contnts.append(content_type([p], **self._load_kwargs)) | ||
except FormatMismatchError: | ||
continue | ||
return contnts | ||
|
||
@validated_property | ||
def _validate_required_content_types(self) -> None: | ||
not_found = set(self.required_content_types) | ||
if not not_found: | ||
return | ||
for fspath in self.content_fspaths: | ||
for content_type in list(not_found): | ||
if content_type.matches(fspath): | ||
not_found.remove(content_type) | ||
if not not_found: | ||
return | ||
assert not_found | ||
raise FormatMismatchError( | ||
f"Did not find the required content types, {not_found}, in {self}" | ||
) | ||
|
||
@classproperty | ||
def potential_content_types(cls) -> ty.Tuple[ty.Type[FileSet], ...]: | ||
content_types: ty.List[ty.Type[FileSet]] = [] | ||
for content_type in cls.content_types: # type: ignore[assignment] | ||
content_types.append(get_optional_type(content_type)) # type: ignore[arg-type] | ||
return tuple(content_types) | ||
|
||
@classproperty | ||
def required_content_types(cls) -> ty.Tuple[ty.Type[FileSet], ...]: | ||
content_types: ty.List[ty.Type[FileSet]] = [] | ||
for content_type in cls.content_types: # type: ignore[assignment] | ||
if ty.get_origin(content_type) is None: | ||
content_types.append(content_type) # type: ignore[arg-type] | ||
return tuple(content_types) | ||
|
||
@classproperty | ||
def unconstrained(cls) -> bool: | ||
"""Whether the file-format is unconstrained by extension, magic number or another | ||
constraint""" | ||
return super().unconstrained and not cls.content_types |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import typing as ty | ||
from .utils import ( | ||
fspaths_converter, | ||
) | ||
from .decorators import classproperty | ||
from .typing import FspathsInputType | ||
from .exceptions import ( | ||
FormatDefinitionError, | ||
) | ||
|
||
if ty.TYPE_CHECKING: | ||
from .fileset import FileSet | ||
|
||
|
||
class MockMixin: | ||
"""Strips out validation methods of a class, allowing it to be mocked in a way that | ||
still satisfies type-checking""" | ||
|
||
def __init__( | ||
self, | ||
fspaths: FspathsInputType, | ||
metadata: ty.Union[ty.Dict[str, ty.Any], bool, None] = False, | ||
): | ||
self.fspaths = fspaths_converter(fspaths) | ||
self._metadata = metadata | ||
|
||
@classproperty | ||
def type_name(cls) -> str: | ||
return cls.mocked.type_name | ||
|
||
def __bytes_repr__(self, cache: ty.Dict[str, ty.Any]) -> ty.Iterable[bytes]: | ||
yield from (str(fspath).encode() for fspath in self.fspaths) | ||
|
||
@classproperty | ||
def mocked(cls) -> "FileSet": | ||
"""The "true" class that the mocked class is based on""" | ||
return next(c for c in cls.__mro__ if not issubclass(c, MockMixin)) # type: ignore[no-any-return, attr-defined] | ||
|
||
@classproperty | ||
def namespace(cls) -> str: | ||
"""The "namespace" the format belongs to under the "fileformats" umbrella | ||
namespace""" | ||
mro: ty.Tuple[ty.Type] = cls.__mro__ # type: ignore | ||
for base in mro: | ||
if issubclass(base, MockMixin): | ||
continue | ||
try: | ||
return base.namespace # type: ignore | ||
except FormatDefinitionError: | ||
pass | ||
raise FormatDefinitionError( | ||
f"None of of the bases classes of {cls} ({mro}) have a valid namespace" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.