diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 8905cf1..e1ef0ba 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -16,7 +16,7 @@ jobs: test: strategy: matrix: - os: [ubuntu-latest] + os: [macos-latest, ubuntu-latest, windows-latest] python-version: ["3.8", "3.12"] fail-fast: false runs-on: ${{ matrix.os }} @@ -39,52 +39,52 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Update build tools - run: python -m pip install --upgrade pip + run: python3 -m pip install --upgrade pip - name: Install Package - run: python -m pip install -e .[test] + run: python3 -m pip install -e .[test] -e ./extras[test] - name: Pytest - run: pytest -vvs ./fileformats + run: pytest -vvs --cov fileformats --cov-config .coveragerc --cov-report xml . + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v2 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} - test-extras: + build: + needs: [test] + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest] - python-version: ["3.8", "3.12"] - fail-fast: false - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash -l {0} + pkg: + - ["main", "."] + - ["extras", "./extras"] steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 - name: Unset header # checkout@v2 adds a header that makes branch protection report errors # because the Github action bot is not a collaborator on the repo run: git config --local --unset http.https://github.com/.extraheader - - name: Fetch tags - run: git fetch --prune --unshallow - - name: Disable etelemetry - run: echo "NO_ET=TRUE" >> $GITHUB_ENV - - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} - uses: actions/setup-python@v2 + - name: Set up Python + uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} - - name: Update build tools - run: python -m pip install --upgrade pip - - name: Install Package - run: python -m pip install -e . -e ./extras[test] - - name: Pytest - run: pytest -vvs --cov fileformats --cov-config .coveragerc --cov-report xml . - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + python-version: '3.12' + - name: Install build tools + run: python3 -m pip install build twine + - name: Build source and wheel distributions + run: python3 -m build ${{ matrix.pkg[1] }} + - name: Check distributions + run: twine check ${{ matrix.pkg[1] }}/dist/* + - uses: actions/upload-artifact@v3 with: - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} + name: built-${{ matrix.pkg[0] }} + path: ${{ matrix.pkg[1] }}/dist build-docs: runs-on: ubuntu-latest - + needs: [build] steps: - name: Checkout repository uses: actions/checkout@v2 @@ -98,7 +98,7 @@ jobs: python-version: 3.x - name: Install dependencies run: | - python -m pip install --upgrade pip + python3 -m pip install --upgrade pip pip install .[docs] - name: Build documentation run: | @@ -111,30 +111,14 @@ jobs: path: docs/build/html deploy: - needs: [test, test-extras, build-docs] + needs: [build, build-docs] runs-on: ubuntu-latest - strategy: - matrix: - pkg-dir: [".", "./extras"] steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - - name: Unset header - # checkout@v2 adds a header that makes branch protection report errors - # because the Github action bot is not a collaborator on the repo - run: git config --local --unset http.https://github.com/.extraheader - - name: Set up Python - uses: actions/setup-python@v4 + - name: Download build + uses: actions/download-artifact@v3 with: - python-version: '3.12' - - name: Install build tools - run: python -m pip install build twine - - name: Build source and wheel distributions - run: python -m build ${{ matrix.pkg-dir }} - - name: Check distributions - run: twine check ${{ matrix.pkg-dir }}/dist/* + name: built-main + path: dist - name: Check for PyPI token on tag id: deployable if: github.event_name == 'release' @@ -147,25 +131,38 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} - packages-dir: ${{ matrix.pkg-dir }}/dist - deploy-docs: - needs: [deploy] + deploy-extras: + needs: [build, deploy] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Download build + uses: actions/download-artifact@v3 with: - submodules: recursive - fetch-depth: 0 - - name: Unset header - # checkout@v2 adds a header that makes branch protection report errors - # because the Github action bot is not a collaborator on the repo - run: git config --local --unset http.https://github.com/.extraheader + name: built-extras + path: dist + - name: Check for PyPI token on tag + id: deployable + if: github.event_name == 'release' + env: + EXTRAS_PYPI_API_TOKEN: "${{ secrets.EXTRAS_PYPI_API_TOKEN }}" + run: if [ -n "$EXTRAS_PYPI_API_TOKEN" ]; then echo "DEPLOY=true" >> $GITHUB_OUTPUT; fi + - name: Upload to PyPI + if: steps.deployable.outputs.DEPLOY + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.EXTRAS_PYPI_API_TOKEN }} + + deploy-docs: + needs: [build-docs, deploy] + runs-on: ubuntu-latest + steps: - name: Download built docs uses: actions/download-artifact@v3 with: name: built-docs - path: docs/build/html + path: docs-build - name: Check for PyPI token on tag id: deployable if: github.event_name == 'release' @@ -177,7 +174,7 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GHPAGES_DEPLOY_KEY }} - publish_dir: docs/build/html + publish_dir: docs-build # Deploy on tags if PYPI_API_TOKEN is defined in the repository secrets. # Secrets are not accessible in the if: condition [0], so set an output variable [1] diff --git a/README.rst b/README.rst index 82c9726..58485f0 100644 --- a/README.rst +++ b/README.rst @@ -10,9 +10,9 @@ FileFormats .. image:: https://img.shields.io/pypi/v/fileformats.svg :target: https://pypi.python.org/pypi/fileformats/ :alt: Latest Version -.. image:: https://img.shields.io/badge/docs-latest-brightgreen +.. image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat :target: https://arcanaframework.github.io/fileformats/ - :alt: docs + :alt: Documentation Status *Fileformats* provides a library of file-format types implemented as Python classes. diff --git a/extras/README.rst b/extras/README.rst index 70c0e5b..f8d8887 100644 --- a/extras/README.rst +++ b/extras/README.rst @@ -4,6 +4,9 @@ FileFormats Extras :target: https://github.com/arcanaframework/fileformats-extras/actions/workflows/tests.yml .. image:: https://codecov.io/gh/arcanaframework/fileformats-extras/branch/main/graph/badge.svg?token=UIS0OGPST7 :target: https://codecov.io/gh/arcanaframework/fileformats-extras +.. image:: https://img.shields.io/pypi/pyversions/fileformats-extras.svg + :target: https://pypi.python.org/pypi/fileformats-extras/ + :alt: Supported Python versions .. image:: https://img.shields.io/pypi/v/fileformats-extras.svg :target: https://pypi.python.org/pypi/fileformats-extras/ :alt: Latest Version diff --git a/extras/fileformats/extras/application/archive.py b/extras/fileformats/extras/application/archive.py index deefafc..a7f6aa7 100644 --- a/extras/fileformats/extras/application/archive.py +++ b/extras/fileformats/extras/application/archive.py @@ -10,7 +10,7 @@ import pydra.engine.specs from fileformats.generic import FsObject from fileformats.core.utils import set_cwd -from fileformats.core import mark, FileSet +from fileformats.core import hook, FileSet from fileformats.application import Zip, Tar, TarGzip @@ -47,10 +47,10 @@ Compressed = FileSet.type_var("Compressed") -@mark.converter(source_format=FsObject, target_format=Tar) -@mark.converter(source_format=FsObject, target_format=TarGzip, compression="gz") -@mark.converter(source_format=Compressed, target_format=Tar[Compressed]) -@mark.converter( +@hook.converter(source_format=FsObject, target_format=Tar) +@hook.converter(source_format=FsObject, target_format=TarGzip, compression="gz") +@hook.converter(source_format=Compressed, target_format=Tar[Compressed]) +@hook.converter( source_format=Compressed, target_format=TarGzip[Compressed], compression="gz" ) @pydra.mark.task @@ -102,10 +102,10 @@ def create_tar( return Path(out_file) -@mark.converter(source_format=Tar, target_format=FsObject) -@mark.converter(source_format=TarGzip, target_format=FsObject) -@mark.converter(source_format=Tar[Compressed], target_format=Compressed) -@mark.converter(source_format=TarGzip[Compressed], target_format=Compressed) +@hook.converter(source_format=Tar, target_format=FsObject) +@hook.converter(source_format=TarGzip, target_format=FsObject) +@hook.converter(source_format=Tar[Compressed], target_format=Compressed) +@hook.converter(source_format=TarGzip[Compressed], target_format=Compressed) @pydra.mark.task @pydra.mark.annotate({"return": {"out_file": Path}}) def extract_tar( @@ -136,8 +136,8 @@ def extract_tar( return extracted[0] -@mark.converter(source_format=FsObject, target_format=Zip) -@mark.converter(source_format=Compressed, target_format=Zip[Compressed]) +@hook.converter(source_format=FsObject, target_format=Zip) +@hook.converter(source_format=Compressed, target_format=Zip[Compressed]) @pydra.mark.task @pydra.mark.annotate( { @@ -198,8 +198,8 @@ def create_zip( return Path(out_file) -@mark.converter(source_format=Zip, target_format=FsObject) -@mark.converter(source_format=Zip[Compressed], target_format=Compressed) +@hook.converter(source_format=Zip, target_format=FsObject) +@hook.converter(source_format=Zip[Compressed], target_format=Compressed) @pydra.mark.task @pydra.mark.annotate({"return": {"out_file": Path}}) def extract_zip(in_file: Zip, extract_dir: Path) -> Path: diff --git a/extras/fileformats/extras/application/serialization.py b/extras/fileformats/extras/application/serialization.py index 462004a..cd2a000 100644 --- a/extras/fileformats/extras/application/serialization.py +++ b/extras/fileformats/extras/application/serialization.py @@ -4,12 +4,12 @@ import yaml import pydra.mark import pydra.engine.specs -from fileformats.core import mark +from fileformats.core import hook from fileformats.application import DataSerialization, Json, Yaml -@mark.converter(target_format=Json, output_format=Json) -@mark.converter(target_format=Yaml, output_format=Yaml) +@hook.converter(target_format=Json, output_format=Json) +@hook.converter(target_format=Yaml, output_format=Yaml) @pydra.mark.task @pydra.mark.annotate({"return": {"out_file": DataSerialization}}) def convert_data_serialization( diff --git a/extras/fileformats/extras/image/converters.py b/extras/fileformats/extras/image/converters.py index 3a7232d..c7f171c 100644 --- a/extras/fileformats/extras/image/converters.py +++ b/extras/fileformats/extras/image/converters.py @@ -3,15 +3,15 @@ import tempfile import pydra.mark import pydra.engine.specs -from fileformats.core import mark +from fileformats.core import hook from fileformats.image.raster import RasterImage, Bitmap, Gif, Jpeg, Png, Tiff -@mark.converter(target_format=Bitmap, output_format=Bitmap) -@mark.converter(target_format=Gif, output_format=Gif) -@mark.converter(target_format=Jpeg, output_format=Jpeg) -@mark.converter(target_format=Png, output_format=Png) -@mark.converter(target_format=Tiff, output_format=Tiff) +@hook.converter(target_format=Bitmap, output_format=Bitmap) +@hook.converter(target_format=Gif, output_format=Gif) +@hook.converter(target_format=Jpeg, output_format=Jpeg) +@hook.converter(target_format=Png, output_format=Png) +@hook.converter(target_format=Tiff, output_format=Tiff) @pydra.mark.task @pydra.mark.annotate({"return": {"out_file": RasterImage}}) def convert_image( diff --git a/extras/pyproject.toml b/extras/pyproject.toml index f2b5202..ed690b0 100644 --- a/extras/pyproject.toml +++ b/extras/pyproject.toml @@ -34,7 +34,6 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", diff --git a/fileformats/application/serialization.py b/fileformats/application/serialization.py index 2e95986..fdedc9e 100644 --- a/fileformats/application/serialization.py +++ b/fileformats/application/serialization.py @@ -2,7 +2,7 @@ import typing as ty from random import Random from pathlib import Path -from ..core import mark, DataType +from ..core import hook, DataType from ..core.mixin import WithClassifiers from ..generic import File from ..core.exceptions import FormatMismatchError @@ -41,12 +41,12 @@ class DataSerialization(WithClassifiers, File): iana_mime = None - @mark.extra + @hook.extra def load(self): """Load the contents of the file into a dictionary""" raise NotImplementedError - @mark.extra + @hook.extra def save(data): """Serialise a dictionary to a new file""" raise NotImplementedError @@ -68,7 +68,7 @@ class Json(DataSerialization): ext = ".json" allowed_classifiers = (JsonSchema, InformalSchema) - @mark.check + @hook.check def load(self): try: with open(self.fspath) as f: diff --git a/fileformats/core/converter.py b/fileformats/core/converter.py index a50b6f8..fea6c7c 100644 --- a/fileformats/core/converter.py +++ b/fileformats/core/converter.py @@ -99,7 +99,7 @@ def register_converter( source_format: type, converter_tuple: ty.Tuple[ty.Callable, ty.Dict[str, ty.Any]], ): - """Registers a converter task within a class attribute. Called by the @fileformats.mark.converter + """Registers a converter task within a class attribute. Called by the @fileformats.hook.converter decorator. Parameters diff --git a/fileformats/core/fileset.py b/fileformats/core/fileset.py index c79177d..61246b2 100644 --- a/fileformats/core/fileset.py +++ b/fileformats/core/fileset.py @@ -35,7 +35,7 @@ FileFormatsExtrasPkgNotCheckedError, ) from .datatype import DataType -from . import mark +from . import hook try: from typing import Self @@ -188,7 +188,7 @@ def metadata(self) -> ty.Dict[str, ty.Any]: metadata = None return metadata - @mark.extra + @hook.extra def read_metadata(self) -> ty.Dict[str, ty.Any]: """Reads any metadata associated with the fileset and returns it as a dict""" raise NotImplementedError @@ -518,7 +518,7 @@ def register_converter( converter_tuple: ty.Tuple[ty.Callable, ty.Dict[str, ty.Any]], ): """Registers a converter task within a class attribute. Called by the - @fileformats.mark.converter decorator. + @fileformats.hook.converter decorator. Parameters ---------- @@ -844,7 +844,7 @@ def sample( if not dest_dir: dest_dir = Path(tempfile.mkdtemp()) # Need to use mock to get an instance in order to use the singledispatch-based - # mark.extra decorator + # hook.extra decorator mock = cls.mock() fspaths = mock.generate_sample_data(dest_dir, seed, stem) try: @@ -857,7 +857,7 @@ def sample( ) return obj - @mark.extra + @hook.extra def generate_sample_data( self, dest_dir: Path, seed: int = 0, stem: str = None ) -> ty.Iterable[Path]: diff --git a/fileformats/core/mark.py b/fileformats/core/hook.py similarity index 99% rename from fileformats/core/mark.py rename to fileformats/core/hook.py index 88c02aa..317b535 100644 --- a/fileformats/core/mark.py +++ b/fileformats/core/hook.py @@ -39,7 +39,7 @@ def check(method): Parameters ---------- method : Function - the method to mark as a check + the method to hook as a check """ method.__annotations__[DataType.CHECK_ANNOTATION] = None return method diff --git a/fileformats/core/mixin.py b/fileformats/core/mixin.py index 31ca169..f861d73 100644 --- a/fileformats/core/mixin.py +++ b/fileformats/core/mixin.py @@ -2,7 +2,7 @@ import re import typing as ty from collections import defaultdict -from . import mark +from . import hook from .fileset import FileSet from .utils import classproperty, describe_task, to_mime_format_name from .converter import SubtypeVar @@ -30,7 +30,7 @@ class WithMagicNumber: binary: bool magic_number: ty.Union[str, bytes] - @mark.check + @hook.check def check_magic_number(self): if self.binary and isinstance(self.magic_number, str): magic_bytes = bytes.fromhex(self.magic_number) @@ -74,7 +74,7 @@ class WithMagicVersion: magic_pattern_offset = 0 magic_pattern_maxlength = None - @mark.required + @hook.required @property def version(self) -> ty.Union[str, ty.Tuple[str]]: read_length = ( @@ -169,7 +169,7 @@ class MyFileFormatWithSeparateHeader(WithSeparateHeader, MyFileFormat): def nested_types(cls): return (cls.header_type,) - @mark.required + @hook.required @property def header(self): return self.header_type(self.select_by_ext(self.header_type)) @@ -200,7 +200,7 @@ class MyFileFormatWithSideCars(WithSideCars, MyFileFormat): the file-formats of the expected side-car files """ - @mark.required + @hook.required @property def side_cars(self): return [tp(self.select_by_ext(tp)) for tp in self.side_car_types] @@ -555,7 +555,7 @@ def register_converter( source_format: type, converter_tuple: ty.Tuple[ty.Callable, ty.Dict[str, ty.Any]], ): - """Registers a converter task within a class attribute. Called by the @fileformats.mark.converter + """Registers a converter task within a class attribute. Called by the @fileformats.hook.converter decorator. Parameters diff --git a/fileformats/core/tests/test_classifiers.py b/fileformats/core/tests/test_classifiers.py index adec893..9b2779e 100644 --- a/fileformats/core/tests/test_classifiers.py +++ b/fileformats/core/tests/test_classifiers.py @@ -3,7 +3,7 @@ import pytest import pydra.mark from fileformats.core import from_mime, DataType, FileSet -from fileformats.core.mark import converter +from fileformats.core.hook import converter from fileformats.application import Zip from fileformats.generic import DirectoryContaining from fileformats.field import Array, Integer, Decimal, Text, Boolean diff --git a/fileformats/core/tests/test_converter.py b/fileformats/core/tests/test_converter.py index 05076b0..f2293f8 100644 --- a/fileformats/core/tests/test_converter.py +++ b/fileformats/core/tests/test_converter.py @@ -4,7 +4,7 @@ import pytest from pydra.engine.specs import File from fileformats.testing import Foo, Bar, Baz, Qux -from fileformats.core import mark +from fileformats.core import hook from fileformats.core.exceptions import FormatConversionError from conftest import write_test_file @@ -18,7 +18,7 @@ def foo_bar_converter(): work_dir = Path(tempfile.mkdtemp()) - @mark.converter + @hook.converter @pydra.mark.task @pydra.mark.annotate({"return": {"out_file": Bar}}) def foo_bar_converter_(in_file: Foo): @@ -31,7 +31,7 @@ def foo_bar_converter_(in_file: Foo): def baz_bar_converter(): work_dir = Path(tempfile.mkdtemp()) - @mark.converter(out_file="out") + @hook.converter(out_file="out") @pydra.mark.task @pydra.mark.annotate({"return": {"out": Bar}}) def baz_bar_converter_(in_file: Baz): @@ -84,7 +84,7 @@ def FooQuxConverter(): name="Output", fields=output_fields, bases=(specs.ShellOutSpec,) ) - @mark.converter(source_format=Foo, target_format=Qux) + @hook.converter(source_format=Foo, target_format=Qux) class FooQuxConverter_(ShellCommandTask): input_spec = FooQux_input_spec diff --git a/fileformats/core/tests/test_detection.py b/fileformats/core/tests/test_detection.py index 8063799..2925c0c 100644 --- a/fileformats/core/tests/test_detection.py +++ b/fileformats/core/tests/test_detection.py @@ -167,7 +167,7 @@ def test_nested_directories_fail2(work_dir): # ext = ".foo" -# @mark.required +# @hook.required # @property # def bar(self): # return self.select_by_ext(".bar") diff --git a/fileformats/core/tests/test_general.py b/fileformats/core/tests/test_general.py index 8faecc4..e57cb19 100644 --- a/fileformats/core/tests/test_general.py +++ b/fileformats/core/tests/test_general.py @@ -1,10 +1,11 @@ from pathlib import Path +import platform import pytest from fileformats.generic import File from fileformats.field import Integer, Boolean, Decimal, Array, Text from fileformats.testing import Foo from fileformats.core.exceptions import FileFormatsError, FormatMismatchError -from fileformats.core import mark +from fileformats.core import hook from conftest import write_test_file @@ -22,7 +23,11 @@ class TestFile(File): def test_file_repr(): - assert repr(Foo.mock()) == "Foo('/mock/foo.foo')" + if platform.system() == "Windows": + expected = f"Foo('{Path().cwd().drive}\\mock\\foo.foo')" + else: + expected = "Foo('/mock/foo.foo')" + assert repr(Foo.mock()) == expected def test_field_repr(): @@ -157,12 +162,12 @@ def test_header_overwrite(work_dir): class YFile(ImageWithInlineHeader): - @mark.required + @hook.required @property def y(self): return self.metadata["y"] - @mark.check + @hook.check def y_value(self): if self.y <= 10: raise FormatMismatchError(f"'y' property is not > 10 ({self.y})") diff --git a/fileformats/core/tests/test_mixin.py b/fileformats/core/tests/test_mixin.py index 6ed1dbe..8293ca5 100644 --- a/fileformats/core/tests/test_mixin.py +++ b/fileformats/core/tests/test_mixin.py @@ -1,6 +1,6 @@ from conftest import write_test_file from fileformats.generic import File -from fileformats.core import mark +from fileformats.core import hook from fileformats.core.mixin import WithMagicNumber, WithSeparateHeader, WithSideCars from fileformats.core.exceptions import FormatMismatchError @@ -48,7 +48,7 @@ class FileWithSeparateHeader(WithSeparateHeader, File): image_type = "sample-image-type" binary = False - @mark.check + @hook.check def check_image_type(self): if self.metadata[self.image_type_key] != self.image_type: raise FormatMismatchError( @@ -121,7 +121,7 @@ class FileWithSideCars(WithSideCars, ImageWithInlineHeader): experiment_type_key = "experiment-type" experiment_type = "sample-experiment-type" - @mark.check + @hook.check def check_image_type(self): """Loaded from inline-header""" if self.metadata[self.image_type_key] != self.image_type: @@ -130,7 +130,7 @@ def check_image_type(self): f"found {self.metadata[self.image_type_key]}" ) - @mark.check + @hook.check def check_experiment_type(self): """Loaded from side-car""" if self.metadata["header"][self.experiment_type_key] != self.experiment_type: diff --git a/fileformats/core/tests/test_test_extras.py b/fileformats/core/tests/test_test_extras.py index 522b2b5..f087d94 100644 --- a/fileformats/core/tests/test_test_extras.py +++ b/fileformats/core/tests/test_test_extras.py @@ -1,4 +1,5 @@ from pathlib import Path +import platform from fileformats.core.fileset import MockMixin from fileformats.testing import Foo @@ -11,6 +12,10 @@ def test_sample(): def test_mock(): mock = Foo.mock() - assert mock.fspath == Path("/mock/foo.foo") + if platform.system() == "Windows": + expected = Path(f"{Path().cwd().drive}\\mock\\foo.foo") + else: + expected = Path("/mock/foo.foo") + assert mock.fspath == expected assert not mock.fspath.exists() assert isinstance(mock, MockMixin) diff --git a/fileformats/core/tests/test_utils.py b/fileformats/core/tests/test_utils.py index 23e3145..8db8df1 100644 --- a/fileformats/core/tests/test_utils.py +++ b/fileformats/core/tests/test_utils.py @@ -7,7 +7,7 @@ import typing as ty import time import pytest -from fileformats.core import FileSet, mark +from fileformats.core import FileSet, hook from fileformats.generic import File, Directory, FsObject from fileformats.core.mixin import WithSeparateHeader from fileformats.core.utils import find_matching, to_mime, from_mime, from_paths @@ -260,12 +260,12 @@ class LuigiMario(File): ext = ".luigi.mario" class DoubleMario(FileSet): - @mark.required + @hook.required @property def bar(self): return Mario(self.select_by_ext(Mario)) - @mark.required + @hook.required @property def luigi_bar(self): return LuigiMario(self.select_by_ext(LuigiMario)) diff --git a/fileformats/generic/__init__.py b/fileformats/generic/__init__.py index 9707836..98ea046 100644 --- a/fileformats/generic/__init__.py +++ b/fileformats/generic/__init__.py @@ -10,7 +10,7 @@ FormatMismatchError, UnconstrainedExtensionException, ) -from fileformats.core import mark +from fileformats.core import hook from fileformats.core.utils import classproperty, gen_filename from fileformats.core.mixin import WithClassifiers @@ -18,7 +18,7 @@ class FsObject(FileSet, os.PathLike): "Generic file-system object, can be either a file or a directory" - @mark.required + @hook.required @property def fspath(self): if len(self.fspaths) > 1: @@ -55,7 +55,7 @@ class File(FsObject): binary = True is_dir = False - @mark.required + @hook.required @property def fspath(self): fspath = self.select_by_ext() @@ -154,7 +154,7 @@ class Directory(FsObject): content_types = () - @mark.required + @hook.required @property def fspath(self): # fspaths are checked for existence with the exception of mock classes @@ -200,7 +200,7 @@ def unconstrained(cls) -> bool: constraint""" return super().unconstrained and not cls.content_types - @mark.check + @hook.check def validate_contents(self): if not self.content_types: return @@ -238,7 +238,7 @@ def contents(self): except FormatMismatchError: continue - @mark.check + @hook.check def validate_contents(self): if not self.content_types: return diff --git a/fileformats/image/raster.py b/fileformats/image/raster.py index 2e1659f..d5137e1 100644 --- a/fileformats/image/raster.py +++ b/fileformats/image/raster.py @@ -1,5 +1,5 @@ from fileformats.core.mixin import WithMagicNumber -from fileformats.core import mark +from fileformats.core import hook from fileformats.core.exceptions import FormatMismatchError from .base import Image @@ -8,11 +8,11 @@ class RasterImage(Image): iana_mime = None binary = True - @mark.extra + @hook.extra def read_data(self): raise NotImplementedError - @mark.extra + @hook.extra def write_data(self, data_array): raise NotImplementedError @@ -58,7 +58,7 @@ class Tiff(RasterImage): magic_number_le = "49492A00" magic_number_be = "4D4D002A" - @mark.check + @hook.check def endianness(self): read_magic = self.read_contents(len(self.magic_number_le) // 2) if read_magic == bytes.fromhex(self.magic_number_le): diff --git a/pyproject.toml b/pyproject.toml index 915f437..72096f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", ] dynamic = ["version"]