diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad6cd87..58b5add 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,33 +9,29 @@ jobs: fail-fast: false matrix: include: - - python-version: "3.9" - tox-env: py39 - python-version: "3.10" tox-env: py310 - python-version: "3.11" tox-env: py311 - python-version: "3.12" tox-env: py312 - - python-version: "3.13.0-rc.1" + - python-version: "3.13" tox-env: py313 - - python-version: "pypy-3.9" - tox-env: pypy39 - python-version: "pypy-3.10" tox-env: pypy310 - - python-version: "3.12" + - python-version: "3.13" tox-env: mypy - - python-version: "3.12" + - python-version: "3.13" tox-env: lint + - python-version: "3.13" + tox-env: format steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5.2.0 + uses: actions/setup-python@v5.3.0 with: python-version: ${{ matrix.python-version }} - name: Pre-installation requirements - run: pip3 install -U pip wheel setuptools - - name: Install dependencies - run: pip3 install -Ur dev-requirements.txt + run: pip3 install -U pip wheel setuptools -r dev-requirements.txt - name: Run tests run: tox -e ${{ matrix.tox-env }} diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..0f0b02a --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,24 @@ +[mypy] +allow_untyped_globals = false +allow_redefinition = false +check_untyped_defs = true +disallow_any_decorated = false +disallow_any_generics = true +disallow_any_unimported = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +follow_untyped_imports = true +ignore_missing_imports = false +implicit_reexport = false +no_implicit_optional = true +pretty = true +show_error_codes = true +strict_optional = true +warn_no_return = true +warn_unreachable = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_return_any = true diff --git a/.pytest.ini b/.pytest.ini new file mode 100644 index 0000000..8282622 --- /dev/null +++ b/.pytest.ini @@ -0,0 +1,20 @@ +[pytest] +addopts = + -vv + --cov=binobj + --import-mode importlib +filterwarnings = + always::DeprecationWarning + + +[coverage:run] +branch = true +source = binobj +plugins = + coverage_pyver_pragma + +[coverage:report] +show_missing = true +skip_covered = true +fail_under = 96.9 +precision = 1 diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..acef061 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,52 @@ +target-version = "py310" + +[lint] +exclude = ["docs/*"] +select = ["ALL"] +extend-ignore = [ + "ANN401", + "COM812", + "CPY001", # Requires copyright notice in every file + "D105", + "D107", + "DTZ", + "EM", + "ERA001", + "FBT", + "FIX002", + "PLR2004", + "TD003", + "TRY003", +] + +[lint.pyupgrade] +keep-runtime-typing = true + +[lint.per-file-ignores] +"__init__.py" = ["F403", "PLC0414"] +"tests/*" = [ + "ANN", + "D", # Forces docstrings and such + "PLR6301", # Method could be a function, class method, or static method + "S101", + "S311", +] +"tests/pep526_*.py" = ["N802", "N803", "I002"] +"tests/full_examples/cpio_test.py" = ["PLR6301"] +"tests/validators_test.py" = ["PLR6301"] + +[lint.isort] +force-single-line = true +known-first-party = ["binobj"] +lines-after-imports = 2 +order-by-type = false +required-imports = ["from __future__ import annotations"] + +[lint.mccabe] +max-complexity = 9 + +[lint.flake8-annotations] +mypy-init-return = true + +[lint.pydocstyle] +convention = "google" diff --git a/LICENSE.txt b/LICENSE.txt index 3cf5c65..eedf5ba 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2017-2024, Diego Argueta +Copyright (c) 2017-2026, Diego Argueta All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 4abb8e2..b1e3391 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ PIP := python3 -m pip poetry.lock: pyproject.toml - poetry lock --no-update + # Poetry 2.0 removed --no-update + poetry lock --no-update || poetry lock touch $@ # Coverage file gets changed on every test run so we can use it to see when the @@ -32,6 +33,11 @@ setup: lint: $(SOURCEFILES) tox -e lint +.PHONY: format +format: + poetry run ruff check --select I --fix binobj tests + poetry run ruff format binobj tests + .PHONY: clean clean: -$(MAKE) -C $(DOCSDIR) clean diff --git a/README.rst b/README.rst index fca012d..e512096 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ binobj .. |build-status| image:: https://github.com/dargueta/binobj/actions/workflows/ci.yml/badge.svg :alt: Build status -.. |python-versions| image:: https://img.shields.io/badge/python-3.9,%203.10,%203.11,%203.12,%203.13-blue.svg +.. |python-versions| image:: https://img.shields.io/badge/python-3.10,%203.11,%203.12,%203.13-blue.svg :alt: Python versions .. |installs-month| image:: https://pepy.tech/badge/binobj/month @@ -50,7 +50,7 @@ The same example rewritten using ``binobj``: magic: Bytes = b"BM" file_size: UInt32 - _reserved: binobj.Bytes(const=b"\0\0\0\0", discard=True) + _reserved: binobj.Bytes(const=True, default=b"\0\0\0\0", discard=True) pixels_offset: UInt32 # Legacy DIB header @@ -81,7 +81,7 @@ System Requirements - This package will *not* work on a `mixed-endian`_ system. Those are pretty rare nowadays so chances are you won't have a problem. -- This has been tested on CPython 3.9-3.13, PyPy 3.9-3.10. +- This has been tested on CPython 3.10-3.13, and PyPy 3.10. .. _mixed-endian: https://en.wikipedia.org/wiki/Endianness#Mixed diff --git a/binobj/__init__.py b/binobj/__init__.py index 8374128..b09cef6 100644 --- a/binobj/__init__.py +++ b/binobj/__init__.py @@ -4,7 +4,6 @@ import importlib.metadata from typing import NamedTuple -from typing import Optional from .errors import * from .fields import * @@ -18,7 +17,7 @@ class VersionInfo(NamedTuple): major: int minor: int patch: int - suffix: Optional[str] + suffix: str | None @classmethod def from_string(cls, version: str) -> VersionInfo: @@ -29,11 +28,12 @@ def from_string(cls, version: str) -> VersionInfo: return cls(int(major), int(minor), int(patch), suffix or None) def __str__(self) -> str: + version = f"{self.major}.{self.minor}.{self.patch}" # Having a version suffix is rare for this library, so we'll tell pycoverage to # ignore this branch. if self.suffix: # pragma: no cover - return "%d.%d.%d-%s" % (self.major, self.minor, self.patch, self.suffix) - return "%d.%d.%d" % (self.major, self.minor, self.patch) + return version + f"-{self.suffix}" + return version __version__ = importlib.metadata.version("binobj") diff --git a/binobj/decorators.py b/binobj/decorators.py index 0a1948e..e215d1e 100644 --- a/binobj/decorators.py +++ b/binobj/decorators.py @@ -3,13 +3,11 @@ from __future__ import annotations import functools -from typing import Callable -from typing import Optional from typing import TYPE_CHECKING -from typing import Union if TYPE_CHECKING: # pragma: no cover + from collections.abc import Callable from collections.abc import Iterable from typing import Any @@ -29,13 +27,13 @@ class ValidatorMethodWrapper: """ def __init__( - self, func: Union[FieldValidator, StructValidator], field_names: Iterable[str] + self, func: FieldValidator | StructValidator, field_names: Iterable[str] ): self.func = func self.field_names = tuple(field_names or ()) functools.wraps(func)(self) - def __call__(self, *args: Any) -> Optional[bool]: + def __call__(self, *args: Any) -> bool | None: """Execute the wrapped function.""" return self.func(*args) diff --git a/binobj/errors.py b/binobj/errors.py index cf1dc89..3c25959 100644 --- a/binobj/errors.py +++ b/binobj/errors.py @@ -4,9 +4,7 @@ import typing from typing import Any -from typing import Optional from typing import TypeVar -from typing import Union import more_itertools as m_iter @@ -51,7 +49,7 @@ class Error(Exception): Do not throw this exception directly. """ - def __init__(self, message: Optional[str] = None, *args: object): + def __init__(self, message: str | None = None, *args: object): # If there is no error message, use the first line of the docstring. if message is None and hasattr(self, "__doc__") and self.__doc__: message = self.__doc__.splitlines()[0] @@ -83,16 +81,16 @@ class ConfigurationError(Error): The ``struct`` and ``obj`` arguments. """ - field: Optional[FieldOrName] - struct: Optional[StructOrName] + field: FieldOrName | None + struct: StructOrName | None obj: Any def __init__( self, - message: Optional[str] = None, + message: str | None = None, *, - field: Optional[FieldOrName] = None, - struct: Optional[StructOrName] = None, + field: FieldOrName | None = None, + struct: StructOrName | None = None, obj: object = None, ): if not (field or struct or obj): @@ -135,11 +133,11 @@ class SerializationError(Error): def __init__( self, - message: Optional[str] = None, + message: str | None = None, *, - struct: Optional[Struct] = None, - field: Optional[FieldOrName] = None, - value: Optional[T] = None, + struct: Struct | None = None, + field: FieldOrName | None = None, + value: T | None = None, ): super().__init__(message) self.struct = struct @@ -162,11 +160,11 @@ class DeserializationError(Error): def __init__( self, - message: Optional[str] = None, + message: str | None = None, *, - field: Optional[Field[Any]] = None, - data: Optional[bytes] = None, - offset: Optional[int] = None, + field: Field[Any] | None = None, + data: bytes | None = None, + offset: int | None = None, ): super().__init__(message) self.field = field @@ -185,9 +183,7 @@ class ValidationError(Error): The invalid value. """ - def __init__( - self, message: Optional[str] = None, *, field: Field[T], value: Optional[T] - ): + def __init__(self, message: str | None = None, *, field: Field[T], value: T | None): if not message: message = f"Invalid value for {field}: {value!r}" @@ -205,7 +201,7 @@ class FieldReferenceError(Error): The name of the field that failed to be referenced. """ - def __init__(self, message: Optional[str] = None, *, field: str): + def __init__(self, message: str | None = None, *, field: str): if not message: message = f"Attempted to reference a missing or undefined field: {field!r}" @@ -235,8 +231,8 @@ class ImmutableFieldError(IllegalOperationError): The ``field`` argument. """ - def __init__(self, *, field: Optional[Field[Any]] = None): - message: Optional[str] + def __init__(self, *, field: Field[Any] | None = None): + message: str | None if field is not None: message = f"Cannot assign to immutable field: {field!r}" else: @@ -374,9 +370,7 @@ class UnserializableValueError(SerializationError): Optional. The reason for the failure. """ - def __init__( - self, *, field: Field[T], value: Optional[T], reason: Optional[str] = None - ): + def __init__(self, *, field: Field[T], value: T | None, reason: str | None = None): if reason is not None: message = f"{field} can't serialize value: {reason}" else: @@ -407,7 +401,7 @@ class UnexpectedValueError(SerializationError): was unexpected. Don't pass :class:`~binobj.fields.base.Field` instances. """ - def __init__(self, *, struct: Struct, name: Union[str, Iterable[str]]): + def __init__(self, *, struct: Struct, name: str | Iterable[str]): self.names = set(m_iter.always_iterable(name)) msg = ( @@ -446,7 +440,7 @@ class ArraySizeError(SerializationError): """ def __init__( - self, *, field: Field[Any], n_expected: int, n_given: Optional[int] = None + self, *, field: Field[Any], n_expected: int, n_given: int | None = None ): if n_given is not None: if n_given > n_expected: @@ -473,7 +467,7 @@ class UnexpectedEOFError(DeserializationError): bytes. """ - def __init__(self, *, field: Optional[Field[Any]], size: int, offset: int): + def __init__(self, *, field: Field[Any] | None, size: int, offset: int): super().__init__( f"Unexpected EOF while trying to read {size} bytes at offset {offset}.", field=field, diff --git a/binobj/fields/base.py b/binobj/fields/base.py index 07f7fc7..669d7a6 100644 --- a/binobj/fields/base.py +++ b/binobj/fields/base.py @@ -11,25 +11,24 @@ import warnings from typing import Any from typing import BinaryIO -from typing import Callable from typing import ClassVar from typing import Generic -from typing import Optional from typing import overload from typing import TypeVar -from typing import Union import more_itertools as m_iter -from typing_extensions import Self from binobj import errors from binobj import helpers if typing.TYPE_CHECKING: # pragma: no cover + from collections.abc import Callable from collections.abc import Collection from collections.abc import Iterable + from typing_extensions import Self + from binobj.structures import Struct from binobj.structures import StructMetadata from binobj.typedefs import FieldValidator @@ -83,26 +82,23 @@ class Field(Generic[T]): fields declared in a normal class if it doesn't match the existing name. Only use this argument if you're building a struct programmatically. :param const: - A constant value this field is expected to take. It will always have this value - when dumped, and will fail validation if the field isn't this value when loaded. - Useful for reserved fields and file tags. + A boolean indicating if this field has a fixed value. It will always have this + value when dumped, and will fail validation if the field isn't this value when + loaded. Useful for reserved fields and file tags. This argument *must* be of the same type as the field, i.e. it must be a string for a :class:`~binobj.fields.stringlike.String`, an integer for an :class:`~binobj.fields.numeric.Integer`, and so on. :param default: The default value to use if a value for this field isn't passed to the struct - for serialization, or (deprecated) a callable taking no arguments that will - return a default value. + for serialization. This is mutually exclusive with ``factory``. This argument *must* be of the same type as the field, i.e. it must be a string for a :class:`~binobj.fields.stringlike.String`, an integer for an :class:`~binobj.fields.numeric.Integer`, and so on. - - .. deprecated:: 0.11.0 - Do not pass a factory function to this argument. Use ``factory`` instead. :param callable factory: - A callable taking no arguments that returns a default value for this field. + A callable taking no arguments that returns a default value for this field. This + is mutually exclusive with ``default``. :param bool discard: When deserializing, don't include this field in the returned results. This means that you won't be able to use the value for anything later. For example, if you @@ -111,7 +107,10 @@ class Field(Generic[T]): name_size = fields.UInt16(discard=True) filename = fields.StringZ(encoding="utf-8") _filename_padding = fields.Bytes( - const=b"\0", discard=True, present=lambda f, *_: f["name_size"] % 2 + const=True, + default=b"\0", + discard=True, + present=lambda f, *_: f["name_size"] % 2, ) this will crash with a :class:`KeyError` because ``name_size`` was discarded. @@ -186,9 +185,9 @@ class Field(Generic[T]): .. versionadded:: 0.9.0 - * The ``not_present_value`` argument. - * ``size`` has full support for :class:`Field`\s and field name values. This - used to be only supported by some fields, with others left out by accident. + * The ``not_present_value`` argument. + * ``size`` has full support for :class:`Field`\s and field name values. This + used to be only supported by some fields, with others left out by accident. .. versionchanged:: 0.9.0 ``null_value`` can now also be a deserialized value. For example, you could pass @@ -204,9 +203,12 @@ class Field(Generic[T]): .. versionadded:: 0.11.0 The ``factory`` argument. - .. deprecated:: 0.11.0 - Passing a factory function to ``default`` is now deprecated. Use ``factory`` - instead. + .. versionchanged:: 1.0.0 + + * ``const`` is now a boolean indicating if the field is a constant value or not. + The constant value is provided by ``default`` or ``factory``. + * A callable returning the default value must be passed to the ``factory`` + argument, not ``default``. """ __overrideable_attributes__: ClassVar[Collection[str]] = () @@ -233,11 +235,11 @@ class Field(Generic[T]): rather than ``Optional[str]``. """ - const: Union[T, _Undefined] - """The fixed value of a field, if applicable. + const: bool + """Indicates if the field has a fixed value, given by :attr:`default`. - This is mostly useful for fields that act as `magic numbers`_ or reserved fields - in a struct that should be set to nulls. + This is useful for fields that act as `magic numbers`_, or for or reserved fields in + a struct that should be set to nulls. .. _magic numbers: https://en.wikipedia.org/wiki/Magic_number_(programming) """ @@ -249,10 +251,12 @@ class Field(Generic[T]): nonetheless important to ensure the proper layout of the struct. """ - _default: Union[T, None, _Undefined] + _default: T | None | _Undefined """The default dump value for the field if the user doesn't pass a value in.""" - _compute_fn: Optional[Callable[[Field[T], StrDict], Optional[T]]] + _compute_fn: Callable[[Field[T], StrDict], T | None] | None + + _size: int | str | Field[int] | None def __new__(cls, *_args: object, **kwargs: object) -> Self: """Create a new instance, recording which keyword arguments were passed in. @@ -260,25 +264,26 @@ def __new__(cls, *_args: object, **kwargs: object) -> Self: Recording the explicit arguments is necessary so that a field can use the fallback values its container class gives for anything else. """ - instance = super().__new__(cls) + instance: Self = super().__new__(cls) instance.__explicit_init_args__ = frozenset(kwargs.keys()) return instance def __init__( # noqa: PLR0913 self, *, - name: Optional[str] = None, - const: Union[T, _Undefined] = UNDEFINED, - default: Union[T, None, Callable[[], Optional[T]], _Undefined] = UNDEFINED, - factory: Optional[Callable[[], Optional[T]]] = None, + name: str | None = None, + const: bool = False, + default: T | None | _Undefined = UNDEFINED, + factory: Callable[[], T | None] | None = None, discard: bool = False, - null_value: Union[bytes, _Default, _Undefined, T] = UNDEFINED, - size: Union[int, str, Field[int], None] = None, - validate: Union[FieldValidator, Iterable[FieldValidator]] = (), - present: Callable[[StrDict, Any, Optional[BinaryIO]], bool] = (lambda *_: True), - not_present_value: Union[T, None, _NotPresent] = NOT_PRESENT, + null_value: T | bytes | _Default | None = None, + size: int | str | Field[int] | None = None, + validate: FieldValidator | Iterable[FieldValidator] = (), + present: Callable[[StrDict, Any, BinaryIO | None], bool] = (lambda *_: True), + not_present_value: T | None | _NotPresent = NOT_PRESENT, ): self.const = const + self._default = default self.discard = discard self.null_value = null_value self.present = present @@ -293,34 +298,37 @@ def __init__( # noqa: PLR0913 "Do not pass values for both `default` and `factory`.", field=self ) - if default is UNDEFINED and const is not UNDEFINED: - # If no default is given but `const` is, set the default value to `const`. - self._default = const - elif callable(default): - warnings.warn( - "Passing a callable to `default` is deprecated. Use `factory` instead.", - DeprecationWarning, - stacklevel=2, + if callable(default): + raise TypeError( + "Do not use a callable as the default value. Pass this to the `factory`" + " argument instead." + ) + + if const and default is UNDEFINED and factory is None: + raise errors.ConfigurationError( + "A default value or factory must be provided if `const` is True.", + field=self, ) - self._default = UNDEFINED - self.factory = default - else: - self._default = default # These attributes are typically set by the struct containing the field after # the field's instantiated. - self.name = typing.cast(str, name) - self.index = typing.cast(int, None) - self.offset: Optional[int] = None + self.name = typing.cast("str", name) + self.index = typing.cast("int", None) + self.offset: int | None = None self._compute_fn = None - if size is not None or const is UNDEFINED: + if size is not None: self._size = size else: - self._size = self._size_for_value(const) + default_value = self.default + if default_value is None or default_value is UNDEFINED: + # `size` is null (the field is unsized) and there's no default value. + self._size = None + else: + self._size = self._size_for_value(default_value) @property - def size(self) -> Union[int, str, Field[int], None]: + def size(self) -> int | str | Field[int] | None: """The size of this field, in bytes. If the field is of variable size, such as a null-terminated string, this will be @@ -343,7 +351,7 @@ def bind_to_container( struct_info: StructMetadata, name: str, index: int, - offset: Optional[int] = None, + offset: int | None = None, ) -> None: """Bind this field to a Struct and apply any predefined defaults. @@ -401,7 +409,7 @@ def compute_value_for_dump( self, all_values: StrDict, context: object = None, - ) -> Union[T, None, _NotPresent]: + ) -> T | None | _NotPresent: """Calculate the value for this field upon dumping. :param dict all_values: @@ -440,7 +448,7 @@ def compute_value_for_dump( if self.name in all_values: # The value is already set in the struct so we don't need to do anything. value_to_dump = all_values[self.name] - elif self._default is not UNDEFINED: + elif self.default is not UNDEFINED: # The value is *not* set in the struct. Either this field must have a # default value, or it must be a computed field. Check for default values # here. @@ -459,7 +467,7 @@ def compute_value_for_dump( raise errors.MissingRequiredValueError(field=self) return value_to_dump # type: ignore[no-any-return] - def computes(self, method: Callable[[Field[T], StrDict], Optional[T]]) -> None: + def computes(self, method: Callable[[Field[T], StrDict], T | None]) -> None: """Decorator that marks a function as computing the value for a field. You can use this for automatically assigning values based on other fields. For @@ -498,7 +506,7 @@ def _assign_n_numbers(self, all_fields): raise errors.ConfigurationError( f"Can't define two computing functions for field {self!r}.", field=self ) - if self.const is not UNDEFINED: + if self.const: raise errors.ConfigurationError( "Cannot set compute function for a const field.", field=self ) @@ -520,18 +528,21 @@ def allow_null(self) -> bool: :type: bool """ - return self.null_value is not UNDEFINED + return self.null_value is not None @property - def default(self) -> Union[T, None, _Undefined]: + def default(self) -> T | None | _Undefined: """The default value of this field, or :data:`UNDEFINED`. .. versionchanged:: 0.6.1 If no default is defined but ``const`` is, this property returns the value for ``const``. + + .. versionchanged:: 1.0.0 + The return value of :attr:`factory` is cached. """ - if self.factory: - return self.factory() + if self.factory and self._default is UNDEFINED: + self._default = self.factory() return self._default @property @@ -540,16 +551,15 @@ def required(self) -> bool: :type: bool """ - return self.const is UNDEFINED and self.default is UNDEFINED + return (not self.const) and self.default is UNDEFINED - def _size_for_value(self, value: T) -> Optional[int]: # noqa: ARG002 + def _size_for_value(self, value: T) -> int | None: # noqa: ARG002 """Get the size of the serialized value, or ``None`` if it can't be computed. This is an ugly hack for computing ``size`` properly when only ``const`` is - given. It's *HIGHLY DISCOURAGED* to implement this function in your own field - subclasses, since it *must not* call :meth:`from_stream`, :meth:`from_bytes`, - :meth:`to_stream`, or :meth:`to_bytes`. Doing so could result in infinite - recursion. + given. Subclasses that override this function *must not* call + :meth:`from_stream`, :meth:`from_bytes`, :meth:`to_stream`, or :meth:`to_bytes`. + Doing so could result in infinite recursion. :param value: The value to serialize. @@ -562,7 +572,7 @@ def _size_for_value(self, value: T) -> Optional[int]: # noqa: ARG002 # Since this is called in the constructor we need to check to see if _size has # been assigned to yet. if self.has_fixed_size and hasattr(self, "_size"): - return typing.cast(int, self._size) + return typing.cast("int", self._size) return None def _get_expected_size(self, field_values: StrDict) -> int: # pragma: no cover @@ -659,8 +669,8 @@ def from_stream( # noqa: C901 self, stream: BinaryIO, context: object = None, - loaded_fields: Optional[StrDict] = None, - ) -> Union[T, None, _NotPresent]: + loaded_fields: StrDict | None = None, + ) -> T | None | _NotPresent: """Load data from the given stream. :param BinaryIO stream: @@ -691,7 +701,7 @@ def from_stream( # noqa: C901 # see if the *raw binary* form is null. This isn't an error UNLESS # null_value is `DEFAULT`. If null_value is DEFAULT and we can't # determine the size, then we're out of luck. - if self.null_value is DEFAULT: + if self.null_value is UNDEFINED: raise errors.CannotDetermineNullError(field=self) from None else: potential_null_bytes = helpers.peek_bytes(stream, len(null_repr)) @@ -723,7 +733,7 @@ def from_stream( # noqa: C901 loaded_value = None # TODO (dargueta): Change this to a validator instead. - if self.const is not UNDEFINED and loaded_value != self.const: + if self.const and loaded_value != self.default: raise errors.ValidationError(field=self, value=loaded_value) for validator in self.validators: @@ -736,8 +746,8 @@ def from_bytes( data: bytes, context: object = None, exact: bool = True, - loaded_fields: Optional[StrDict] = None, - ) -> Union[T, None, _NotPresent]: + loaded_fields: StrDict | None = None, + ) -> T | None | _NotPresent: """Load from the given byte string. :param bytes data: @@ -773,7 +783,7 @@ def from_bytes( @abc.abstractmethod def _do_load( self, stream: BinaryIO, context: object, loaded_fields: StrDict - ) -> Optional[T]: + ) -> T | None: """Load an object from the stream. :param BinaryIO stream: @@ -791,9 +801,9 @@ def _do_load( def to_stream( # noqa: C901 self, stream: BinaryIO, - data: Union[T, None, _Default] = DEFAULT, + data: T | None | _Default = DEFAULT, context: object = None, - all_fields: Optional[StrDict] = None, + all_fields: StrDict | None = None, ) -> None: """Convert the given data into bytes and write it to ``stream``. @@ -815,7 +825,7 @@ def to_stream( # noqa: C901 if data is DEFAULT: # Typecast is not entirely truthful; this may return UNDEFINED if the field # has no default value. - data = typing.cast(Optional[T], self.default) + data = typing.cast("T | None", self.default) if data is UNDEFINED or data is DEFAULT: raise errors.MissingRequiredValueError(field=self) @@ -833,7 +843,7 @@ def to_stream( # noqa: C901 serialized_value = buf.getvalue() current_size = len(serialized_value) if self.has_fixed_size: - expected_size = typing.cast(int, self.size) + expected_size = typing.cast("int", self.size) elif self.size is None: expected_size = current_size else: @@ -865,9 +875,9 @@ def _add_padding(self, serialized: bytes, to_size: int) -> bytes: # noqa: ARG00 def to_bytes( self, - data: Union[Optional[T], _Default] = DEFAULT, + data: T | None | _Default = DEFAULT, context: object = None, - all_fields: Optional[StrDict] = None, + all_fields: StrDict | None = None, ) -> bytes: """Convert the given data into bytes. @@ -907,7 +917,7 @@ def _do_dump( """ raise errors.UnserializableValueError(field=self, value=data) - def _get_null_repr(self, all_fields: Optional[StrDict] = None) -> bytes: + def _get_null_repr(self, all_fields: StrDict | None = None) -> bytes: """Return the serialized value for ``None``. We need this function because there's some logic involved in determining if @@ -925,45 +935,36 @@ def _get_null_repr(self, all_fields: Optional[StrDict] = None) -> bytes: if all_fields is None: all_fields = {} - if self.null_value is UNDEFINED: + if self.null_value is None: raise errors.UnserializableValueError( reason=f"`None` is not an acceptable value for {self}.", field=self, value=None, ) - if self.null_value not in (DEFAULT, None): - if isinstance(self.null_value, bytes): - return self.null_value - - # This is a bit of a hack. We can't call to_bytes() directly because that'll - # trigger infinite recursion if we do. Thus, we have to call the dumping - # function directly in all its ugly glory. - buf = io.BytesIO() - - # Note: We need the typecast here because MyPy doesn't correctly detect that - # we're filtering out DEFAULT in the `if` statement above. It thinks that - # null_value can still be DEFAULT, so we cast it to this field's type. - self._do_dump( - buf, - typing.cast(T, self.null_value), - context=None, - all_fields=all_fields, - ) - return buf.getvalue() + if isinstance(self.null_value, bytes): + return self.null_value - # User wants us to use all null bytes for the default null value. - try: - return b"\0" * self.get_expected_size(all_fields) - except errors.UndefinedSizeError: - raise errors.UnserializableValueError( - reason=f"Can't guess appropriate serialization of `None` for {self}" - " because it has no fixed size.", - field=self, - value=None, - ) from None + if self.null_value is DEFAULT: + # User wants us to use all null bytes for the default null value. + try: + return b"\0" * self.get_expected_size(all_fields) + except errors.UndefinedSizeError: + raise errors.UnserializableValueError( + reason=f"Can't guess appropriate serialization of `None` for {self}" + " because it has no fixed size.", + field=self, + value=None, + ) from None + + # This is a bit of a hack. We can't call to_bytes() directly because that'll + # trigger infinite recursion if we do. Thus, we have to call the dumping + # function directly in all its ugly glory. + buf = io.BytesIO() + self._do_dump(buf, self.null_value, context=None, all_fields=all_fields) + return buf.getvalue() def _read_exact_size( - self, stream: BinaryIO, loaded_fields: Optional[StrDict] = None + self, stream: BinaryIO, loaded_fields: StrDict | None = None ) -> bytes: """Read exactly the number of bytes this object takes up or crash. @@ -1003,7 +1004,7 @@ def __get__( @overload def __get__( self, instance: Struct, owner: type[Struct] - ) -> Optional[T]: ... # pragma: no cover + ) -> T | None: ... # pragma: no cover # This annotation is bogus and only here to make MyPy happy. See bug report here: # https://github.com/python/mypy/issues/9416 @@ -1019,8 +1020,8 @@ def __get__(self, instance, owner): # type: ignore[no-untyped-def] return instance.__values__[self.name] return self.compute_value_for_dump(instance) - def __set__(self, instance: Struct, value: Optional[T]) -> None: - if self._compute_fn or self.const is not UNDEFINED: + def __set__(self, instance: Struct, value: T | None) -> None: + if self._compute_fn or self.const: raise errors.ImmutableFieldError(field=self) for validator in self.validators: diff --git a/binobj/fields/containers.py b/binobj/fields/containers.py index 61954ee..4fe5cc9 100644 --- a/binobj/fields/containers.py +++ b/binobj/fields/containers.py @@ -3,6 +3,7 @@ from __future__ import annotations import typing +from collections.abc import Callable from collections.abc import Iterable from collections.abc import Mapping from collections.abc import MutableSequence @@ -10,14 +11,11 @@ from collections.abc import Sized from typing import Any from typing import BinaryIO -from typing import Callable -from typing import Optional from typing import overload +from typing import TypeAlias from typing import TypeVar -from typing import Union as _Union from typing_extensions import override -from typing_extensions import TypeAlias from binobj import errors from binobj.fields.base import Field @@ -35,7 +33,7 @@ HaltCheckFn: TypeAlias = Callable[ - ["Array[T]", BinaryIO, MutableSequence[Optional[T]], Any, StrDict], bool + ["Array[T]", BinaryIO, MutableSequence[T | None], Any, StrDict], bool ] """A function used to detect the end of an array when deserializing. @@ -59,7 +57,7 @@ ] -class Array(Field[list[Optional[T]]]): +class Array(Field[list[T | None]]): """An array of other serializable objects. :param Field component: @@ -97,8 +95,8 @@ def __init__( self, component: Field[T], *, - count: _Union[int, Field[int], str, None] = None, - halt_check: Optional[HaltCheckFn[T]] = None, + count: int | Field[int] | str | None = None, + halt_check: HaltCheckFn[T] | None = None, **kwargs: Any, ): super().__init__(**kwargs) @@ -107,7 +105,7 @@ def __init__( maybe_assign_name(self.component, self.name) if count is None or ( - isinstance(count, (int, str, Field)) and not isinstance(count, bool) + isinstance(count, int | str | Field) and not isinstance(count, bool) ): # The isinstance bool check is needed because `bool` is a subclass of `int`. self.count = count @@ -115,9 +113,9 @@ def __init__( raise TypeError("`count` must be an integer, string, or a `Field`.") if isinstance(self.count, int) and component.has_fixed_size: - self._size = self.count * typing.cast(int, component.size) + self._size = self.count * typing.cast("int", component.size) - def get_final_element_count(self, field_values: StrDict) -> Optional[int]: + def get_final_element_count(self, field_values: StrDict) -> int | None: """Calculate the number of elements in the array based on other fields' values. :param dict field_values: @@ -165,13 +163,13 @@ def get_final_element_count(self, field_values: StrDict) -> Optional[int]: f"Array size depends on field {name!r} but it wasn't found.", field=name, ) - return typing.cast(int, field_values[name]) + return typing.cast("int", field_values[name]) @staticmethod def should_halt( seq: Array[T], stream: BinaryIO, - values: list[Optional[T]], + values: list[T | None], context: object, # noqa: ARG004 loaded_fields: StrDict, ) -> bool: @@ -239,7 +237,7 @@ def should_halt( def _do_dump( self, stream: BinaryIO, - data: Iterable[Optional[T]], + data: Iterable[T | None], context: object, all_fields: StrDict, ) -> None: @@ -272,8 +270,8 @@ def _do_dump( def _dump_unsized( self, stream: BinaryIO, - data: Iterable[Optional[T]], - n_elems: Optional[int], + data: Iterable[T | None], + n_elems: int | None, context: object, all_fields: StrDict, ) -> None: @@ -299,7 +297,7 @@ def _dump_unsized( def _do_load( self, stream: BinaryIO, context: object, loaded_fields: StrDict - ) -> list[Optional[T]]: + ) -> list[T | None]: """Load a structure list from the given stream. :param BinaryIO stream: @@ -314,7 +312,7 @@ def _do_load( :return: The deserialized data. :rtype: list """ - result: list[Optional[T]] = [] + result: list[T | None] = [] while not self.halt_check(self, stream, result, context, loaded_fields): component = self.component.from_stream(stream, context, loaded_fields) if component is NOT_PRESENT: @@ -333,7 +331,7 @@ class Nested(Field[TStruct]): .. code-block:: python class Address(Struct): - ...Any + ... class Person(Struct): name = fields.StringZ() @@ -354,14 +352,14 @@ def __init__(self, struct_class: type[TStruct], *args: object, **kwargs: Any): def _do_dump( self, stream: BinaryIO, - data: _Union[StrDict, TStruct], + data: StrDict | TStruct, context: object, all_fields: StrDict, ) -> None: if isinstance(data, Struct): data.to_stream(stream, context) else: - instance = self.struct_class(**typing.cast(StrDict, data)) + instance = self.struct_class(**data) instance.to_stream(stream, context) @override diff --git a/binobj/fields/numeric.py b/binobj/fields/numeric.py index 45a4eb5..10bdfb5 100644 --- a/binobj/fields/numeric.py +++ b/binobj/fields/numeric.py @@ -21,7 +21,6 @@ from typing import Any from typing import BinaryIO from typing import Final - from typing import Optional from binobj.typedefs import EndianString from binobj.typedefs import StrDict @@ -73,7 +72,7 @@ def __init__( self, *, format_string: FloatFormat, - endian: Optional[EndianString] = None, + endian: EndianString | None = None, **kwargs: Any, ): super().__init__(size=struct.calcsize(format_string), **kwargs) @@ -104,7 +103,7 @@ def _do_load( def _do_dump( self, stream: BinaryIO, - data: Optional[float], + data: float | None, context: object, all_fields: StrDict, ) -> None: @@ -177,7 +176,7 @@ class Integer(Field[int]): def __init__( self, *, - endian: Optional[EndianString] = None, + endian: EndianString | None = None, signed: bool = True, **kwargs: Any, ): @@ -226,7 +225,7 @@ def __init__( self, *, vli_format: VarIntEncoding, - max_bytes: Optional[int] = None, + max_bytes: int | None = None, **kwargs: Any, ): encoding_info = varints.INTEGER_ENCODING_MAP.get(vli_format) @@ -402,7 +401,7 @@ def __init__( size: int, resolution: str = "s", tz_aware: bool = False, - endian: Optional[EndianString] = None, + endian: EndianString | None = None, signed: bool = True, **kwargs: Any, ): diff --git a/binobj/fields/stringlike.py b/binobj/fields/stringlike.py index 644d441..0b1d4e4 100644 --- a/binobj/fields/stringlike.py +++ b/binobj/fields/stringlike.py @@ -18,12 +18,17 @@ if TYPE_CHECKING: # pragma: no cover from typing import Any from typing import BinaryIO - from typing import Optional from binobj.typedefs import StrDict -__all__ = ["Bytes", "String", "StringZ", "UUID4", "UUIDFormat"] +__all__ = [ + "UUID4", + "Bytes", + "String", + "StringZ", + "UUIDFormat", +] class Bytes(Field[bytes]): @@ -32,7 +37,7 @@ class Bytes(Field[bytes]): @override def _do_load( self, stream: BinaryIO, context: object, loaded_fields: StrDict - ) -> Optional[bytes]: + ) -> bytes | None: return self._read_exact_size(stream, loaded_fields) @override @@ -81,11 +86,11 @@ def __init__( self, *, encoding: str = "iso-8859-1", - pad_byte: Optional[bytes] = None, + pad_byte: bytes | None = None, **kwargs: Any, ): if pad_byte is not None: - if not isinstance(pad_byte, (bytes, bytearray)): + if not isinstance(pad_byte, bytes | bytearray): raise errors.ConfigurationError( "`pad_byte` must be a bytes-like object.", field=self ) @@ -101,7 +106,7 @@ def __init__( @override def _do_load( self, stream: BinaryIO, context: object, loaded_fields: StrDict - ) -> Optional[str]: + ) -> str | None: """Load a fixed-length string from a stream.""" to_load = self._read_exact_size(stream, loaded_fields) return to_load.decode(self.encoding) @@ -159,7 +164,7 @@ class StringZ(String): def _do_load( self, stream: BinaryIO, context: object, loaded_fields: StrDict ) -> str: - max_bytes: Optional[int] + max_bytes: int | None try: max_bytes = self.get_expected_size(loaded_fields) except errors.UndefinedSizeError: @@ -245,7 +250,7 @@ def __init__( @override def _do_load( self, stream: BinaryIO, context: object, loaded_fields: StrDict - ) -> Optional[uuid.UUID]: + ) -> uuid.UUID | None: raw_data = stream.read(self.size) if len(raw_data) < self.size: raise errors.UnexpectedEOFError( diff --git a/binobj/helpers.py b/binobj/helpers.py index c5c33d5..6007bc7 100644 --- a/binobj/helpers.py +++ b/binobj/helpers.py @@ -4,7 +4,6 @@ import sys from typing import BinaryIO -from typing import Optional from typing import TYPE_CHECKING from binobj import errors @@ -20,7 +19,7 @@ def read_int( stream: BinaryIO, n_bytes: int, signed: bool = True, - endian: Optional[EndianString] = None, + endian: EndianString | None = None, ) -> int: """Read an integer from the given byte stream. @@ -57,44 +56,49 @@ def write_int( value: int, n_bytes: int, signed: bool = True, - endian: Optional[EndianString] = None, + endian: EndianString | None = None, ) -> None: """Write an integer to a stream. - :param BinaryIO stream: - The stream to write the integer to. - :param int value: - The integer to dump into the stream. - :param int n_bytes: - The number of bytes the integer should take up. Exactly this many bytes will be - written into the stream, so ensure that there's enough bits to represent - ``value``. - :param bool signed: - If ``True``, write this integer in two's-complement format. Otherwise, write it - as an unsigned integer. A negative ``value`` will trigger an - :class:`OverflowError` if this is ``False``. - :param str endian: - The endianness to use when writing the integer, either "big" or "little". If not - given, will default to the system's native byte order as given by - :data:`sys.byteorder`. - - :raise OverflowError: - ``value`` can't be represented only by ``n_bytes`` bytes. The number is too big, - or it's negative and ``signed`` is ``False``. + Arguments: + stream (BinaryIO): + The stream to write the integer to. + value (int): + The integer to dump into the stream. + n_bytes (int): + The number of bytes the integer should take up. Exactly this many bytes will + be written into the stream, so ensure that there's enough bits to represent + ``value``. + signed (bool): + If ``True``, write this integer in two's-complement format. Otherwise, write + it as an unsigned integer. A negative ``value`` will trigger an + :class:`OverflowError` if this is ``False``. + endian (str): + The endianness to use when writing the integer, either "big" or "little". If + not given, will default to the system's native byte order as given by + :data:`sys.byteorder`. + + Raises: + OverflowError: + ``value`` can't be represented only by ``n_bytes`` bytes. The number is too + big, or it's negative and ``signed`` is ``False``. """ if not endian: endian = sys.byteorder - stream.write(value.to_bytes(n_bytes, endian, signed=signed)) -def iter_bytes(stream: BinaryIO, max_bytes: Optional[int] = None) -> Iterator[bytes]: +def iter_bytes(stream: BinaryIO, max_bytes: int | None = None) -> Iterator[bytes]: """Wrap a stream in an iterator that yields individual bytes, not lines. - :param stream: - A stream opened in binary mode. - :param int max_bytes: - The maximum number of bytes to read. + Arguments: + stream (BinaryIO): + A stream opened in binary mode. + max_bytes (int): + The maximum number of bytes to read. + + Yields: + Individual bytes from the stream. .. versionadded:: 0.2.1 """ @@ -111,6 +115,12 @@ def iter_bytes(stream: BinaryIO, max_bytes: Optional[int] = None) -> Iterator[by def peek_bytes(stream: BinaryIO, count: int, short_read_okay: bool = True) -> bytes: """Read the next ``count`` bytes in the stream without moving the stream pointer. + Returns: + bytes: The data read. + + Raises: + EOFError: Fewer than `count` bytes were left in the input. + .. versionadded:: 0.9.0 """ here = stream.tell() @@ -121,6 +131,6 @@ def peek_bytes(stream: BinaryIO, count: int, short_read_okay: bool = True) -> by if not short_read_okay and len(data) < count: raise EOFError( - "Short read: expected %d bytes in stream, read %d." % (count, len(data)) + f"Short read: expected {count} bytes in stream, read {len(data)}." ) return data diff --git a/binobj/pep526.py b/binobj/pep526.py index 48b76f5..4809ea2 100644 --- a/binobj/pep526.py +++ b/binobj/pep526.py @@ -38,7 +38,6 @@ class MyStruct(binobj.Struct): import typing import warnings from typing import Any -from typing import Optional from typing import TypeVar from typing import Union @@ -67,6 +66,10 @@ def flatten_annotations(annotation: Any) -> list[Any]: This is very similar to :func:`more_itertools.always_iterable`, except for type annotations. Single annotations are converted to a one-element list; PEP-593 annotations are flattened into a list and returned. + + Returns: + The type annotation, with all sub-annotations (if any) expanded out into a flat + list. """ if not hasattr(annotation, "__metadata__"): return [annotation] @@ -97,7 +100,7 @@ class AnnotationInfo: This is here for forward compatibility and should be ignored for now. """ - has_default: Optional[bool] = None + has_default: bool | None = None default_value: Any = fields.UNDEFINED nullable: bool = False @@ -107,6 +110,31 @@ class AnnotationInfo: def from_annotation( cls, field_name: str, annotation: Any, struct_class: type[TStruct] ) -> Self: + """Factory method for construction from an arbitrary type annotation. + + Arguments: + field_name (str): + The name of the attribute this annotation is for. + annotation: + The type annotation. This can be anything, and doesn't need to represent + a BinObj field. + struct_class (type): + The class the attribute was defined on. + + Returns: + A new AnnotationInfo instance. + + Raises: + InvalidTypeAnnotationError: + The annotation is the union of two or more non-None types. This probably + means the caller intended to use a BinObj `Union`, not the Python native + :class:`typing.Union`. + + * If you want the union of two or more BinObj field types, you must use + :class:`binobj.fields.containers.Union`. + * If you want a BinObj field with additional annotations, use + :class:`typing.Annotated` and pass both to it. + """ type_class = annotation type_args = typing.get_args(annotation) nullable = type(None) in type_args @@ -149,8 +177,13 @@ def from_annotation( nullable=nullable, ) - def make_field_instance(self) -> Optional[Field[Any]]: - """Convert a type annotation to a Field object if it represents one.""" + def make_field_instance(self) -> Field[Any] | None: + """Convert a type annotation to a Field object if it represents one. + + Returns: + Field: The BinObj Field derived from this type annotation, if possible. + None: The type annotation is not a BinObj field definition. + """ if isinstance(self.type_class, type): # We got a class object. Could be a struct or field, ignore everything else. if issubclass(self.type_class, Struct): @@ -213,6 +246,23 @@ class MyStruct(binobj.Struct): baz: Annotated[datetime, Timestamp64(signed=False)] + Raises: + ConfigurationError: + A field has multiple BinObj annotations in its `Annotated` list. + + FieldRedefinedError: + A field was already defined in a superclass of this struct. + + NoDefinedFieldsError: + The class has no defined fields. This usually indicates missing type + annotations. + + MixedDeclarationsError: + The class has both fields defined by assignment and also by type annotation. + + Returns: + The original class, modified to include the struct definition. + .. versionadded:: 0.9.0 .. _PEP 526: https://www.python.org/dev/peps/pep-0526/ @@ -258,7 +308,7 @@ class MyStruct(binobj.Struct): field_instance.bind_to_container(meta, name, field_index, byte_offset) if byte_offset is not None and field_instance.has_fixed_size: - byte_offset += typing.cast(int, field_instance.size) + byte_offset += typing.cast("int", field_instance.size) else: byte_offset = None diff --git a/binobj/structures.py b/binobj/structures.py index 64bdb03..527503a 100644 --- a/binobj/structures.py +++ b/binobj/structures.py @@ -17,26 +17,28 @@ from typing import BinaryIO from typing import ClassVar from typing import Final -from typing import Optional from typing import overload from typing import Protocol +from typing import TypeGuard from typing import TypeVar from typing_extensions import Self -from typing_extensions import TypeGuard from binobj import decorators from binobj import errors from binobj import fields -from binobj.typedefs import MethodFieldValidator -from binobj.typedefs import MutableStrDict -from binobj.typedefs import StrDict -from binobj.typedefs import StructValidator __all__ = ["Struct", "is_struct"] +if typing.TYPE_CHECKING: # pragma: no cover + from binobj.typedefs import MethodFieldValidator + from binobj.typedefs import MutableStrDict + from binobj.typedefs import StrDict + from binobj.typedefs import StructValidator + + T = TypeVar("T") K = TypeVar("K") V = TypeVar("V") @@ -82,7 +84,7 @@ class StructMetadata: .. versionadded:: 0.9.0 """ - size_bytes: Optional[int] = 0 + size_bytes: int | None = 0 """The total size of the struct in bytes, if it's a fixed value. This is only used for classes declared using PEP 526 type annotations and should @@ -105,11 +107,19 @@ class StructMetadata: def from_class(cls, struct_class: type) -> Self: """Create a StructMetadata from any class defining BinObj fields. - :param type struct_class: - The class object to inspect. This doesn't need to be a subclass of - :class:`.Struct`, nor does it need to have any ancestors that are subclasses - of it. - :return: The computed metadata. + Arguments: + struct_class (type): + The class object to inspect. This doesn't need to be a subclass of + :class:`.Struct`, nor does it need to have any ancestors that are + subclasses of it. + + Returns: + The computed metadata. + + Raises: + MultipleInheritanceError: + ``struct_class`` has more than one parent class. BinObj currently does + not support resolving field ordering for structs with multiple parents. .. versionadded:: 0.12.0 """ @@ -174,19 +184,22 @@ def from_class(cls, struct_class: type) -> Self: return metadata def load_meta_options(self, meta: type) -> None: + """Update settings on this class from the given metadata class.""" self.argument_defaults = dict(getattr(meta, "argument_defaults", {})) - def get_size(self, field_values: Optional[StrDict] = None) -> Optional[int]: + def get_size(self, field_values: StrDict | None = None) -> int | None: """Get the size of a Struct. If the Struct contains variable-length fields, it may still be possible to derive the serialized size if the values of assigned fields are passed in via ``field_values``. - :param dict field_values: - Optional. A mapping of assigned field values, to aid inferring the size of - the struct if it's not a fixed size. - :return: + Arguments: + field_values: + Optional. A mapping of assigned field values, to aid inferring the size + of the struct if it's not a fixed size. + + Returns: The size of the struct in bytes, or ``None`` if it couldn't be computed. .. versionadded:: 0.12.0 @@ -204,7 +217,7 @@ def get_size(self, field_values: Optional[StrDict] = None) -> Optional[int]: def collect_assigned_fields( namespace: StrDict, class_metadata: StructMetadata, - byte_offset: Optional[int], + byte_offset: int | None, ) -> int: """Collect all fields defined by class variable assignment to a struct. @@ -221,6 +234,10 @@ def collect_assigned_fields( Returns: int: The number of fields found. + Raises: + FieldRedefinedError: + The struct defines a field with the same name as a field in its superclass. + .. versionadded:: 0.9.0 .. versionchanged:: 0.10.0 @@ -246,7 +263,7 @@ def collect_assigned_fields( item.bind_to_container(class_metadata, item_name, field_index, byte_offset) if byte_offset is not None and item.has_fixed_size: - byte_offset += typing.cast(int, item.size) + byte_offset += typing.cast("int", item.size) else: byte_offset = None @@ -293,14 +310,18 @@ def recursive_to_dicts(item: object) -> Any: This is used when a :class:`Struct` is converted to a dictionary. - :param item: - Anything. If it's an unsupported type it'll get returned as is. + Arguments: + item: + Anything. If it's an unsupported type it'll get returned as is. + + Returns: + If `item` is a :class:`Struct`, that struct as a dict. Otherwise, `item`. """ if isinstance(item, Struct): return item.to_dict() if isinstance(item, Mapping): return {recursive_to_dicts(k): recursive_to_dicts(v) for k, v in item.items()} - if isinstance(item, Sequence) and not isinstance(item, (str, bytes, bytearray)): + if isinstance(item, Sequence) and not isinstance(item, str | bytes | bytearray): return [recursive_to_dicts(v) for v in item] return item @@ -315,7 +336,7 @@ class HasStruct(Protocol): """ -class Struct(HasStruct): +class Struct(HasStruct): # noqa: PLW1641 """An ordered collection of fields and other structures. .. versionchanged:: 0.5.0 @@ -342,7 +363,8 @@ def __init__(self, **values: object): def validate_contents(self) -> None: """Validate the stored values in this struct. - :raise ~binobj.errors.ValidationError: Validation failed. + Raises: + ValidationError: Validation failed. .. versionadded:: 0.4.0 """ @@ -361,16 +383,17 @@ def validate_contents(self) -> None: # Validate the entirety of the struct. for struct_validator in self.__binobj_struct__.struct_validators: - struct_validator(self, typing.cast(StrDict, self)) + struct_validator(self, typing.cast("StrDict", self)) def to_stream(self, stream: BinaryIO, context: object = None) -> None: """Convert the given data into bytes and write it to ``stream``. - :param BinaryIO stream: - The stream to write the serialized data into. - :param context: - Additional data to pass to this method. Subclasses must ignore anything they - don't recognize. + Arguments: + stream (BinaryIO): + The stream to write the serialized data into. + context: + Additional data to pass to this method. Subclasses must ignore anything + they don't recognize. """ self.validate_contents() @@ -387,12 +410,13 @@ def to_stream(self, stream: BinaryIO, context: object = None) -> None: def to_bytes(self, context: object = None) -> bytes: """Convert the given data into bytes. - :param context: - Additional data to pass to this method. Subclasses must ignore anything they - don't recognize. + Arguments: + context: + Additional data to pass to this method. Subclasses must ignore anything + they don't recognize. - :return: The serialized data. - :rtype: bytes + Returns: + The serialized data. """ stream = io.BytesIO() self.to_stream(stream, context) @@ -405,13 +429,16 @@ def to_dict(self, keep_discardable: bool = False) -> dict[str, Any]: native Python types. As such, validation is *not* performed since that was done while loading. - :param bool keep_discardable: - If True, don't exclude fields marked with ``discard=True`` from the result. + Arguments: + keep_discardable (bool): + If True, don't exclude fields marked with ``discard=True`` from the + result. - :rtype: Dict[str, Any] + Returns: + The struct, loaded as a mapping of field names to their values. - :raise MissingRequiredValueError: - One or more fields don't have assigned values. + Raises: + ValueError: One or more fields don't have assigned values. .. versionchanged:: 0.3.0 This now recursively calls :meth:`.to_dict` on all nested structs and arrays @@ -438,6 +465,7 @@ def to_dict(self, keep_discardable: bool = False) -> dict[str, Any]: for field in self.__binobj_struct__.components.values(): if field.discard: del dct[field.name] + raise ValueError return recursive_to_dicts(dct) @@ -447,6 +475,9 @@ def _to_dict_whatever_possible(self) -> dict[str, Any]: We use this to get values for all computed fields as well as any fields that have no dependencies and can serialize themselves (e.g. Bytes and sized int fields). This allows us to resolve forward references. + + Returns: + The struct, loaded as a mapping of field names to their values. """ dct = {} @@ -454,7 +485,7 @@ def _to_dict_whatever_possible(self) -> dict[str, Any]: for field in self.__binobj_struct__.components.values(): try: dct[field.name] = field.compute_value_for_dump( - typing.cast(StrDict, self) + typing.cast("StrDict", self) ) except (errors.SerializationError, errors.UndefinedSizeError): # noqa: PERF203 pass @@ -476,29 +507,30 @@ def from_stream( cls, stream: BinaryIO, context: object = None, - init_kwargs: Optional[StrDict] = None, + init_kwargs: StrDict | None = None, ) -> Self: """Load a struct from the given stream. - :param BinaryIO stream: - The stream to load data from. - :param context: - Additional data to pass to the components' - :meth:`~binobj.fields.base.Field.from_stream` methods. Subclasses must - ignore anything they don't recognize. - :param dict init_kwargs: - Additional keyword arguments to pass to the struct's constructor, for - subclasses that take additional arguments beyond the fields that comprise - the struct. You can also use this to *override* field values; anything given - in here takes precedence over loaded values. - - :return: The loaded struct. + Arguments: + stream (BinaryIO): + A binary stream to get the data from. + context: + Additional data to pass to this method. Subclasses must ignore anything + they don't recognize. + init_kwargs: + Additional keyword arguments to pass to the struct's constructor, for + subclasses that take additional arguments beyond the fields that + comprise the struct. You can also use this to *override* field values; + anything given in here takes precedence over loaded values. + + Returns: + The loaded struct. .. versionadded:: 0.7.0 The ``init_kwargs`` argument. """ if init_kwargs: - results = typing.cast(MutableStrDict, copy.deepcopy(init_kwargs)) + results = typing.cast("MutableStrDict", copy.deepcopy(init_kwargs)) else: results = {} @@ -522,27 +554,33 @@ def from_bytes( data: bytes, context: object = None, exact: bool = True, - init_kwargs: Optional[StrDict] = None, + init_kwargs: StrDict | None = None, ) -> Self: """Load a struct from the given byte string. - :param bytes data: - A bytes-like object to get the data from. - :param context: - Additional data to pass to this method. Subclasses must ignore anything they - don't recognize. - :param bool exact: - ``data`` must contain exactly the number of bytes required. If not all the - bytes in ``data`` were used when reading the struct, throw an exception. - :param dict init_kwargs: - Additional keyword arguments to pass to the struct's constructor, for - subclasses that take additional arguments beyond the fields that comprise - the struct. You can also use this to *override* field values; anything given - in here takes precedence over loaded values. - - :return: The loaded struct. - :raise ExtraneousDataError: - ``exact`` is True and there's data left over at the end of the byte string. + Arguments: + data (bytes): + A bytes-like object to get the data from. + context: + Additional data to pass to this method. Subclasses must ignore anything + they don't recognize. + exact (bool): + ``data`` must contain exactly the number of bytes required. If not all + the bytes in ``data`` were used when reading the struct, throw an + exception. + init_kwargs: + Additional keyword arguments to pass to the struct's constructor, for + subclasses that take additional arguments beyond the fields that + comprise the struct. You can also use this to *override* field values; + anything given in here takes precedence over loaded values. + + Returns: + The loaded struct. + + Raises: + ExtraneousDataError: + ``exact`` is True and there's data left over at the end of the byte + string. .. versionadded:: 0.7.0 The ``init_kwargs`` argument. @@ -562,7 +600,7 @@ def from_bytes( def partial_load( cls, stream: BinaryIO, - last_field: Optional[str] = None, + last_field: str | None = None, context: object = None, ) -> Self: """Partially load this object, either until EOF or the named field. @@ -576,18 +614,24 @@ def partial_load( .. note:: Because the struct is only partially loaded, struct-level validators are - *not* executed. Individual fields still are. - - :param BinaryIO stream: - The stream to load from. - :param str last_field: - The name of the last field to load in the object. If given, enough bytes for - this and all previous fields *must* be present in the stream. - :param context: - Any object containing extra information to pass to the fields' - :meth:`~binobj.fields.base.Field.from_stream` method. - - :return: The loaded struct. + *not* executed. Validators for individual fields still are. + + Arguments: + stream (BinaryIO): + The stream to load from. + last_field (str): + The name of the last field to load in the object. If given, enough bytes + for this and all previous fields *must* be present in the stream. + context: + Any object containing extra information to pass to the fields' + :meth:`~binobj.fields.base.Field.from_stream` method. + + Returns: + The loaded struct. + + Raises: + ValueError: The struct has no field with the name given by `last_field`. + UnexpectedEOFError: Hit EOF before finishing reading the requested fields. """ if ( last_field is not None @@ -635,23 +679,26 @@ def get_field(cls, stream: BinaryIO, name: str, context: object = None) -> Any: including this field must be read. Unfortunately, this means that unrelated validation errors can be thrown if other fields have problems. - :param BinaryIO stream: - The stream to read from. It's assumed that the stream pointer is positioned - at the start of a struct. The stream pointer is returned to its original - position even if an exception occurred. - :param str name: - The name of the field to retrieve. - :param context: - Optional. Any object containing extra information to pass to the - :meth:`~binobj.fields.base.Field.from_stream` method of the field. For - fields located at a variable offset, this will be passed to the - :meth:`~binobj.fields.base.Field.from_stream` method of *each* field read. - - :return: The value of the field in the struct data. - - :raise UnexpectedEOFError: - The end of the stream was reached before the requested field could be - completely read. + Arguments: + stream (BinaryIO): + The stream to read from. It's assumed that the stream pointer is + positioned at the start of a struct. The stream pointer is returned to + its original position even if an exception occurred. + name (str): + The name of the field to retrieve. + context: + Optional. Any object containing extra information to pass to the + :meth:`~binobj.fields.base.Field.from_stream` method of the field. For + fields located at a variable offset, this will be passed to the + :meth:`~binobj.fields.base.Field.from_stream` method of *each* field + read. + + Returns: + The value of the field in the struct data. + + Raises: + ValueError: + There's no defined field with the given name. """ if name not in cls.__binobj_struct__.components: raise ValueError(f"{cls.__name__} doesn't have a field named {name!r}.") @@ -679,7 +726,7 @@ def get_field(cls, stream: BinaryIO, name: str, context: object = None) -> Any: return loaded_data[name] def partial_dump( - self, stream: BinaryIO, last_field: Optional[str] = None, context: object = None + self, stream: BinaryIO, last_field: str | None = None, context: object = None ) -> None: """Partially dump the object, up to and including the last named field. @@ -689,13 +736,18 @@ def partial_dump( If ``last_field`` isn't given, as many fields will be serialized as possible up to the first missing one. - :param BinaryIO stream: - The stream to dump into. - :param str last_field: - The name of the last field in the object to dump. - :param context: - Any object containing extra information to pass to the fields' - :meth:`~binobj.fields.base.Field.from_stream` methods. + Arguments: + stream (BinaryIO): + The stream to dump into. + last_field (str): + The name of the last field in the object to dump. + context: + Any object containing extra information to pass to the fields' + :meth:`~binobj.fields.base.Field.from_stream` methods. + + Raises: + MissingRequiredValueError: + One or more required fields don't have a value set. """ data = self.__values__ @@ -716,7 +768,7 @@ def partial_dump( return @classmethod - def get_size(cls) -> Optional[int]: + def get_size(cls) -> int | None: """Return the size of this struct in bytes, if possible. If there are variable-sized fields that can't be resolved, this function returns @@ -764,7 +816,7 @@ def __len__(self) -> int: for field in self.__binobj_struct__.components.values(): if field.has_fixed_size: - size += typing.cast(int, field.size) + size += typing.cast("int", field.size) else: try: size += field.get_expected_size(current_fields) @@ -790,7 +842,7 @@ def __eq__(self, other: object) -> bool: # defined. self_values = recursive_to_dicts({n: self[n] for n in list(self)}) - if not isinstance(other, (Struct, Mapping)): + if not isinstance(other, Struct | Mapping): return False other_values = recursive_to_dicts({n: other[n] for n in list(other)}) diff --git a/binobj/typedefs.py b/binobj/typedefs.py index 2af295f..b008944 100644 --- a/binobj/typedefs.py +++ b/binobj/typedefs.py @@ -3,40 +3,34 @@ from __future__ import annotations import typing +from collections.abc import Callable from collections.abc import Mapping from collections.abc import MutableMapping from typing import Any -from typing import Callable from typing import Literal -from typing import Optional -from typing import TypeVar from typing import Union if typing.TYPE_CHECKING: # pragma: no cover - from typing_extensions import TypeAlias + from typing import TypeAlias from binobj.fields.base import Field from binobj.structures import Struct -T_co = TypeVar("T_co", covariant=True, bound=object) - StrDict: TypeAlias = Mapping[str, Any] """Any mapping using strings as the keys.""" MutableStrDict: TypeAlias = MutableMapping[str, Any] """Any mutable mapping using strings for the keys.""" -FieldValidator: TypeAlias = Callable[["Field[Any]", Any], Optional[bool]] +FieldValidator: TypeAlias = Callable[["Field[Any]", Any], bool | None] """A function that, given a field, returns True if it's valid and False otherwise.""" -MethodFieldValidator: TypeAlias = Callable[ - ["Struct", "Field[Any]", Any], Optional[bool] -] +MethodFieldValidator: TypeAlias = Callable[["Struct", "Field[Any]", Any], bool | None] """A field validator that's a method in its containing struct.""" -StructValidator: TypeAlias = Callable[["Struct", StrDict], Optional[bool]] +StructValidator: TypeAlias = Callable[["Struct", StrDict], bool | None] """A classmethod that validates an entire struct.""" FieldOrName: TypeAlias = Union[str, "Field[Any]"] diff --git a/binobj/varints.py b/binobj/varints.py index b8d06c6..5b7853b 100644 --- a/binobj/varints.py +++ b/binobj/varints.py @@ -10,13 +10,12 @@ import math import typing from typing import BinaryIO -from typing import Callable from binobj import helpers if typing.TYPE_CHECKING: # pragma: no cover - from typing import Optional + from collections.abc import Callable from binobj.typedefs import EndianString @@ -50,17 +49,22 @@ class VarIntEncoding(enum.Enum): def _read_uint8(stream: BinaryIO) -> int: - """Read an unsigned 8-bit integer from the given byte stream.""" + """Read an unsigned 8-bit integer from the given byte stream. + + Returns: + An unsigned integer in the range [0, 256). + """ return helpers.read_int(stream, 1, signed=False) def encode_integer_compact(value: int) -> bytes: """Encode an integer with signed VLQ encoding. - :param int value: The value to encode. + Arguments: + value (int): The value to encode. - :return: The encoded integer. - :rtype: bytes + Returns: + The encoded integer. """ if value == 0: return b"\0" @@ -72,7 +76,7 @@ def encode_integer_compact(value: int) -> bytes: sign_bit = 0 n_bits = value.bit_length() - n_bytes = 1 + int(math.ceil((n_bits - 6) / 7)) + n_bytes = 1 + math.ceil((n_bits - 6) / 7) buf = bytearray(n_bytes) @@ -89,12 +93,13 @@ def encode_integer_compact(value: int) -> bytes: def decode_integer_compact(stream: BinaryIO) -> int: """Decode an integer with signed VLQ encoding. - :param BinaryIO stream: The stream to read from. + Arguments: + stream (BinaryIO): The stream to read from. - :return: The decoded integer. - :rtype: int + Returns: + The decoded integer. """ - sign: Optional[int] = None + sign: int | None = None value = 0 while True: @@ -115,11 +120,15 @@ def decode_integer_compact(stream: BinaryIO) -> int: def encode_integer_vlq(value: int) -> bytes: """Encode an integer with the unsigned VLQ encoding. - :param int value: - The value to encode. Must be a non-negative integer. + Arguments: + value (int): + The value to encode. Must be a non-negative integer. - :return: ``value`` encoded as a variable-length integer in VLQ format. - :rtype: bytes + Returns: + ``value`` encoded as a variable-length integer in VLQ format. + + Raises: + ValueError: ``value`` is negative. """ if value < 0: raise ValueError("The VLQ integer encoding doesn't support negative numbers.") @@ -128,7 +137,7 @@ def encode_integer_vlq(value: int) -> bytes: return b"\0" n_bits = value.bit_length() - n_bytes = int(math.ceil(n_bits / 7)) + n_bytes = math.ceil(n_bits / 7) buf = bytearray(n_bytes) for i in range(n_bytes - 1, -1, -1): @@ -142,10 +151,11 @@ def encode_integer_vlq(value: int) -> bytes: def decode_integer_vlq(stream: BinaryIO) -> int: """Decode an unsigned VLQ-encoded integer from the given stream. - :param BinaryIO stream: The stream to read from. + Arguments: + stream (BinaryIO): The stream to read from. - :return: The decoded integer. - :rtype: int + Returns: + The decoded integer. """ value = 0 while True: @@ -159,10 +169,14 @@ def decode_integer_vlq(stream: BinaryIO) -> int: def encode_integer_uleb128(value: int) -> bytes: """Encode an integer with unsigned LEB128 encoding. - :param int value: The value to encode. + Arguments: + value (int): The value to encode. + + Returns: + ``value`` encoded as a variable-length integer in ULEB128 format. - :return: ``value`` encoded as a variable-length integer in ULEB128 format. - :rtype: bytes + Raises: + ValueError: ``value`` is negative. """ if value < 0: raise ValueError( @@ -184,10 +198,11 @@ def encode_integer_uleb128(value: int) -> bytes: def decode_integer_uleb128(stream: BinaryIO) -> int: """Decode an unsigned LEB128-encoded integer from the given stream. - :param BinaryIO stream: The stream to read from. + Arguments: + stream (BinaryIO): The stream to read from. - :return: The decoded integer. - :rtype: int + Returns: + The decoded integer. """ value = 0 bits_read = 0 @@ -204,10 +219,11 @@ def decode_integer_uleb128(stream: BinaryIO) -> int: def encode_integer_leb128(value: int) -> bytes: """Encode an integer with signed LEB128 encoding. - :param int value: The value to encode. + Arguments: + value (int): The value to encode. - :return: ``value`` encoded as a variable-length integer in LEB128 format. - :rtype: bytes + Returns: + ``value`` encoded as a variable-length integer in LEB128 format. """ if value == 0: return b"\0" @@ -241,10 +257,11 @@ def encode_integer_leb128(value: int) -> bytes: def decode_integer_leb128(stream: BinaryIO) -> int: """Decode a signed LEB128-encoded integer from the given stream. - :param BinaryIO stream: The stream to read from. + Arguments: + stream (BinaryIO): The stream to read from. - :return: The decoded integer. - :rtype: int + Returns: + The decoded integer. """ starting_offset = stream.tell() value = decode_integer_uleb128(stream) diff --git a/dev-requirements.txt b/dev-requirements.txt index bd21f48..8e10f31 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,3 @@ Sphinx>=4.0 tox>=4.0 -poetry>=1.1.13 +poetry>=2.0 diff --git a/docs/source/CHANGELOG.rst b/docs/source/CHANGELOG.rst index 42d149a..938e904 100644 --- a/docs/source/CHANGELOG.rst +++ b/docs/source/CHANGELOG.rst @@ -1,6 +1,40 @@ Changelog ========= +1.0.0b0 +------- + +New Features +~~~~~~~~~~~~ + +* Official support for 3.13 and 3.14. +* Fields can be declared using ``typing.Annotated`` to allow type checkers to + work properly. + +Breaking Changes +~~~~~~~~~~~~~~~~ + +Dropped support for EOL Python 3.7, 3.8, 3.9, and soon-to-be-EOL 3.10. + +Migration Guide +~~~~~~~~~~~~~~~ + +Initializing ``Field`` +^^^^^^^^^^^^^^^^^^^^^^ + +1. Factory functions must be passed in ``factory``, not ``default``. +2. ``const`` is now a boolean indicating the field must have the default value. + +.. code-block:: python + + # Old way + UInt32(const=1234) + + # New way + UInt32(const=True, default=1234) + +The default value for ``null_value`` is now ``None``, not ``UNDEFINED``. + 0.12.0 ------ diff --git a/poetry.lock b/poetry.lock index 0de0feb..7ae973e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.1 and should not be changed by hand. [[package]] name = "colorama" @@ -6,6 +6,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -13,100 +15,133 @@ files = [ [[package]] name = "coverage" -version = "7.6.1" +version = "7.13.5" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, - {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, - {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, - {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, - {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, - {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, - {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, - {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, - {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, - {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, - {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, - {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, - {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, - {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, - {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, - {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, - {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, - {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, - {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, - {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, - {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, - {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, - {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, - {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, - {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, - {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, - {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, - {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, - {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, - {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, - {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, - {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, - {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, - {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, - {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, - {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, - {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, - {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, + {file = "coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5"}, + {file = "coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0"}, + {file = "coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58"}, + {file = "coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e"}, + {file = "coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d"}, + {file = "coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8"}, + {file = "coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf"}, + {file = "coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9"}, + {file = "coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028"}, + {file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"}, + {file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"}, + {file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"}, + {file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"}, + {file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"}, + {file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"}, + {file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"}, + {file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"}, + {file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"}, + {file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"}, + {file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"}, + {file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"}, + {file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"}, + {file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"}, + {file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"}, + {file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"}, + {file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"}, + {file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"}, + {file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"}, + {file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"}, + {file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"}, + {file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"}, + {file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"}, + {file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"}, + {file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"}, + {file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"}, + {file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"}, ] -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "coverage-pyver-pragma" -version = "0.3.3" +version = "0.4.0" description = "Plugin for Coverage.py to selectively ignore branches depending on the Python version." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "coverage_pyver_pragma-0.3.3-py3-none-any.whl", hash = "sha256:b6cb3f0196820841c6168dcb3cf0f6ffaf2f66167444c4f5951f163659b38a00"}, - {file = "coverage_pyver_pragma-0.3.3.tar.gz", hash = "sha256:8520148720238a66921182f52f45822d8191afcdd781e19a208bbc61600b4547"}, + {file = "coverage_pyver_pragma-0.4.0-py3-none-any.whl", hash = "sha256:c768f1e1d505ddff110a68b182c83f45fea6b684809283fc6382d7b8f76c306f"}, + {file = "coverage_pyver_pragma-0.4.0.tar.gz", hash = "sha256:789bcee4ab672f858b3dc147ebfcaf90a98f10720bb58c23cd949658542bba93"}, ] [package.dependencies] @@ -117,13 +152,14 @@ pyparsing = ">=2.4.7" [[package]] name = "domdf-python-tools" -version = "3.9.0" +version = "3.10.0" description = "Helpful functions for Python 🐍 🛠️" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ - {file = "domdf_python_tools-3.9.0-py3-none-any.whl", hash = "sha256:4e1ef365cbc24627d6d1e90cf7d46d8ab8df967e1237f4a26885f6986c78872e"}, - {file = "domdf_python_tools-3.9.0.tar.gz", hash = "sha256:1f8a96971178333a55e083e35610d7688cd7620ad2b99790164e1fc1a3614c18"}, + {file = "domdf_python_tools-3.10.0-py3-none-any.whl", hash = "sha256:5e71c1be71bbcc1f881d690c8984b60e64298ec256903b3147f068bc33090c36"}, + {file = "domdf_python_tools-3.10.0.tar.gz", hash = "sha256:2ae308d2f4f1e9145f5f4ba57f840fbfd1c2983ee26e4824347789649d3ae298"}, ] [package.dependencies] @@ -135,116 +171,210 @@ all = ["pytz (>=2019.1)"] dates = ["pytz (>=2019.1)"] [[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] -[package.extras] -test = ["pytest (>=6)"] - [[package]] -name = "importlib-metadata" -version = "8.4.0" -description = "Read metadata from Python packages" +name = "librt" +version = "0.8.1" +description = "Mypyc runtime library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation == \"CPython\"" files = [ - {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, - {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "librt-0.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81fd938344fecb9373ba1b155968c8a329491d2ce38e7ddb76f30ffb938f12dc"}, + {file = "librt-0.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5db05697c82b3a2ec53f6e72b2ed373132b0c2e05135f0696784e97d7f5d48e7"}, + {file = "librt-0.8.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d56bc4011975f7460bea7b33e1ff425d2f1adf419935ff6707273c77f8a4ada6"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdc0f588ff4b663ea96c26d2a230c525c6fc62b28314edaaaca8ed5af931ad0"}, + {file = "librt-0.8.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c2b54ff6717a7a563b72627990bec60d8029df17df423f0ed37d56a17a176b"}, + {file = "librt-0.8.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f1125e6bbf2f1657d9a2f3ccc4a2c9b0c8b176965bb565dd4d86be67eddb4b6"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8f4bb453f408137d7581be309b2fbc6868a80e7ef60c88e689078ee3a296ae71"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c336d61d2fe74a3195edc1646d53ff1cddd3a9600b09fa6ab75e5514ba4862a7"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:eb5656019db7c4deacf0c1a55a898c5bb8f989be904597fcb5232a2f4828fa05"}, + {file = "librt-0.8.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c25d9e338d5bed46c1632f851babf3d13c78f49a225462017cf5e11e845c5891"}, + {file = "librt-0.8.1-cp310-cp310-win32.whl", hash = "sha256:aaab0e307e344cb28d800957ef3ec16605146ef0e59e059a60a176d19543d1b7"}, + {file = "librt-0.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:56e04c14b696300d47b3bc5f1d10a00e86ae978886d0cee14e5714fafb5df5d2"}, + {file = "librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd"}, + {file = "librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965"}, + {file = "librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0"}, + {file = "librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e"}, + {file = "librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99"}, + {file = "librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe"}, + {file = "librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb"}, + {file = "librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b"}, + {file = "librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9"}, + {file = "librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a"}, + {file = "librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9"}, + {file = "librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d"}, + {file = "librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7"}, + {file = "librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921"}, + {file = "librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0"}, + {file = "librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a"}, + {file = "librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444"}, + {file = "librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d"}, + {file = "librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35"}, + {file = "librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583"}, + {file = "librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04"}, + {file = "librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363"}, + {file = "librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b"}, + {file = "librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d"}, + {file = "librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a"}, + {file = "librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79"}, + {file = "librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0"}, + {file = "librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f"}, + {file = "librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c"}, + {file = "librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3"}, + {file = "librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071"}, + {file = "librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78"}, + {file = "librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023"}, + {file = "librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730"}, + {file = "librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1"}, + {file = "librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e"}, + {file = "librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382"}, + {file = "librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994"}, + {file = "librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a"}, + {file = "librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4"}, + {file = "librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61"}, + {file = "librt-0.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3dff3d3ca8db20e783b1bc7de49c0a2ab0b8387f31236d6a026597d07fcd68ac"}, + {file = "librt-0.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08eec3a1fc435f0d09c87b6bf1ec798986a3544f446b864e4099633a56fcd9ed"}, + {file = "librt-0.8.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e3f0a41487fd5fad7e760b9e8a90e251e27c2816fbc2cff36a22a0e6bcbbd9dd"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bacdb58d9939d95cc557b4dbaa86527c9db2ac1ed76a18bc8d26f6dc8647d851"}, + {file = "librt-0.8.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d7ab1f01aa753188605b09a51faa44a3327400b00b8cce424c71910fc0a128"}, + {file = "librt-0.8.1-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4998009e7cb9e896569f4be7004f09d0ed70d386fa99d42b6d363f6d200501ac"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2cc68eeeef5e906839c7bb0815748b5b0a974ec27125beefc0f942715785b551"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0bf69d79a23f4f40b8673a947a234baeeb133b5078b483b7297c5916539cf5d5"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:22b46eabd76c1986ee7d231b0765ad387d7673bbd996aa0d0d054b38ac65d8f6"}, + {file = "librt-0.8.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:237796479f4d0637d6b9cbcb926ff424a97735e68ade6facf402df4ec93375ed"}, + {file = "librt-0.8.1-cp39-cp39-win32.whl", hash = "sha256:4beb04b8c66c6ae62f8c1e0b2f097c1ebad9295c929a8d5286c05eae7c2fc7dc"}, + {file = "librt-0.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:64548cde61b692dc0dc379f4b5f59a2f582c2ebe7890d09c1ae3b9e66fa015b7"}, + {file = "librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73"}, ] [[package]] name = "more-itertools" -version = "10.5.0" +version = "11.0.1" description = "More routines for operating on iterables, beyond itertools" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6"}, - {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, + {file = "more_itertools-11.0.1-py3-none-any.whl", hash = "sha256:eaf287826069452a8f61026c597eae2428b2d1ba2859083abbf240b46842ce6d"}, + {file = "more_itertools-11.0.1.tar.gz", hash = "sha256:fefaf25b7ab08f0b45fa9f1892cae93b9fc0089ef034d39213bce15f1cc9e199"}, ] [[package]] name = "mypy" -version = "1.11.2" +version = "1.20.0" description = "Optional static typing for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] +markers = "platform_python_implementation == \"CPython\"" files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, + {file = "mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8"}, + {file = "mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a"}, + {file = "mypy-1.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8426d4d75d68714abc17a4292d922f6ba2cfb984b72c2278c437f6dae797865"}, + {file = "mypy-1.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02cca0761c75b42a20a2757ae58713276605eb29a08dd8a6e092aa347c4115ca"}, + {file = "mypy-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3a49064504be59e59da664c5e149edc1f26c67c4f8e8456f6ba6aba55033018"}, + {file = "mypy-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebea00201737ad4391142808ed16e875add5c17f676e0912b387739f84991e13"}, + {file = "mypy-1.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80cf77847d0d3e6e3111b7b25db32a7f8762fd4b9a3a72ce53fe16a2863b281"}, + {file = "mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b"}, + {file = "mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367"}, + {file = "mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62"}, + {file = "mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0"}, + {file = "mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f"}, + {file = "mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e"}, + {file = "mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442"}, + {file = "mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214"}, + {file = "mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e"}, + {file = "mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651"}, + {file = "mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5"}, + {file = "mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78"}, + {file = "mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489"}, + {file = "mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33"}, + {file = "mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134"}, + {file = "mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c"}, + {file = "mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe"}, + {file = "mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f"}, + {file = "mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726"}, + {file = "mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69"}, + {file = "mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e"}, + {file = "mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948"}, + {file = "mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5"}, + {file = "mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188"}, + {file = "mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83"}, + {file = "mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2"}, + {file = "mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732"}, + {file = "mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef"}, + {file = "mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1"}, + {file = "mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436"}, + {file = "mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6"}, + {file = "mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526"}, + {file = "mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787"}, + {file = "mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb"}, + {file = "mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd"}, + {file = "mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e"}, + {file = "mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3"}, ] [package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" +librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=1.0.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] +native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +groups = ["dev"] +markers = "platform_python_implementation == \"CPython\"" files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] @@ -253,6 +383,7 @@ version = "8.4.0" description = "Simple yet flexible natural sorting in Python." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, @@ -264,39 +395,76 @@ icu = ["PyICU (>=1.0.0)"] [[package]] name = "packaging" -version = "24.1" +version = "26.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, + {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, ] +[[package]] +name = "pathspec" +version = "1.0.4" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation == \"CPython\"" +files = [ + {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, + {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, +] + +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] +tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] + [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyparsing" -version = "3.1.4" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" +version = "3.3.2" +description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, - {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, ] [package.extras] @@ -304,128 +472,123 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "8.3.2" +version = "9.0.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["dev"] files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1.0.1" +packaging = ">=22" pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} +pygments = ">=2.7.2" [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "5.0.0" +version = "7.1.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, + {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +testing = ["process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-randomly" -version = "3.15.0" +version = "4.0.1" description = "Pytest plugin to randomly order tests and control random.seed." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"}, - {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"}, + {file = "pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7"}, + {file = "pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8"}, ] [package.dependencies] -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} pytest = "*" [[package]] name = "ruff" -version = "0.6.4" +version = "0.15.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "ruff-0.6.4-py3-none-linux_armv6l.whl", hash = "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258"}, - {file = "ruff-0.6.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60"}, - {file = "ruff-0.6.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6"}, - {file = "ruff-0.6.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa"}, - {file = "ruff-0.6.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6"}, - {file = "ruff-0.6.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d"}, - {file = "ruff-0.6.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa"}, - {file = "ruff-0.6.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1"}, - {file = "ruff-0.6.4-py3-none-win32.whl", hash = "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523"}, - {file = "ruff-0.6.4-py3-none-win_amd64.whl", hash = "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58"}, - {file = "ruff-0.6.4-py3-none-win_arm64.whl", hash = "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14"}, - {file = "ruff-0.6.4.tar.gz", hash = "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212"}, + {file = "ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1"}, + {file = "ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7"}, + {file = "ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6"}, + {file = "ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840"}, + {file = "ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed"}, + {file = "ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71"}, + {file = "ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677"}, + {file = "ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c"}, + {file = "ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec"}, + {file = "ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d"}, + {file = "ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53"}, + {file = "ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2"}, ] [[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" +name = "setuptools" +version = "82.0.1" +description = "Most extensible Python build backend with support for C/C++ extension modules" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, + {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] [[package]] -name = "zipp" -version = "3.20.1" -description = "Backport of pathlib-compatible object wrapper for zip files" +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, - {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - [metadata] -lock-version = "2.0" -python-versions = "^3.9" -content-hash = "80dd5586279032acefabbe7ad59f719c156c00c1160e9f0e0ac451a9b945aff5" +lock-version = "2.1" +python-versions = ">=3.11" +content-hash = "8c846278f3ed6052368857f1bfa28b34bbc46586fa24a7fb6c8d381811251a03" diff --git a/pyproject.toml b/pyproject.toml index 8a1d5f0..a9b6e0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,26 +2,23 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Poetry - -[tool.poetry] +[project] name = "binobj" version = "0.12.0" description = "A Python library for reading and writing structured binary data." -authors = ["Diego Argueta <620513-dargueta@users.noreply.github.com>"] -readme = "README.rst" +authors = [ + { name = "Diego Argueta", email = "620513-dargueta@users.noreply.github.com" }, +] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Typing :: Typed", @@ -32,74 +29,34 @@ exclude = [ "docs", "tests", ] +requires-python = ">=3.11" +dependencies = [ + "more-itertools >= 4.0", + "typing-extensions >= 4.4", +] -[tool.poetry.dependencies] -python = "^3.9" -more-itertools = ">=4.0" -typing-extensions = ">=4.4" - -[tool.poetry.group.dev.dependencies] -# For reproducibility please pin all direct dependencies as tightly as possible, -# down to the patch version. -coverage = "7.6.1" -coverage-pyver-pragma = "0.3.3" -mypy = {version = "1.11.2", markers = "platform_python_implementation == 'CPython'"} -ruff = "0.6.4" -pytest = "8.3.2" -pytest-cov = "5.0.0" -pytest-randomly = "3.15.0" - -[tool.poetry.urls] +[project.urls] Homepage = "https://dargueta.github.io/binobj" Repository = "https://github.com/dargueta/binobj" Documentation = "https://dargueta.github.io/binobj" "Bug Tracker" = "https://github.com/dargueta/binobj/issues" - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Ruff - -[tool.ruff.lint] -exclude = ["docs/*"] -select = ["ALL"] -extend-ignore = [ - "ANN401", - "COM812", - "D105", - "D107", - "DTZ", - "EM", - "ERA001", - "FBT", - "FIX002", - "PLR2004", - "TD003", - "TRY003", -] - -[tool.ruff] -target-version = "py39" - -[tool.ruff.lint.pyupgrade] -keep-runtime-typing = true - -[tool.ruff.lint.per-file-ignores] -"__init__.py" = ["F403", "PLC0414"] -"tests/*" = ["ANN", "D", "S101", "S311"] -"tests/pep526_*.py" = ["N802", "N803", "I002"] - -[tool.ruff.lint.isort] -force-single-line = true -known-first-party = ["binobj"] -lines-after-imports = 2 -order-by-type = false -required-imports = ["from __future__ import annotations"] - -[tool.ruff.lint.mccabe] -max-complexity = 9 +# Poetry -[tool.ruff.lint.flake8-annotations] -mypy-init-return = true +[tool.poetry] +requires-poetry = ">=2.0" +readme = "README.rst" -[tool.ruff.lint.pydocstyle] -convention = "google" +[tool.poetry.group.dev.dependencies] +# For reproducibility please pin all direct dependencies as tightly as possible, +# down to the patch version. +coverage = "7.13.5" +coverage-pyver-pragma = "0.4.0" +mypy = {version = "1.20.0", markers = "platform_python_implementation == 'CPython'"} +ruff = "0.15.9" +pytest = "9.0.2" +pytest-cov = "7.1.0" +pytest-randomly = "4.0.1" +# Due to this bug https://stackoverflow.com/q/75307814 +setuptools = "*" diff --git a/tests/decorators_test.py b/tests/decorators_test.py index 996c7f0..3500fe7 100644 --- a/tests/decorators_test.py +++ b/tests/decorators_test.py @@ -48,7 +48,7 @@ def test_computes__const_field_fails(): with pytest.raises(errors.ConfigurationError): class Class(binobj.Struct): - blah = fields.UInt16(const=1234) + blah = fields.UInt16(const=True, default=1234) @blah.computes def _blah(self, _all_fields): diff --git a/tests/errors_test.py b/tests/errors_test.py index 3d8e782..01db8e5 100644 --- a/tests/errors_test.py +++ b/tests/errors_test.py @@ -32,8 +32,10 @@ def test_configurationerror__default_messages(field, struct, obj, message): ("blah", "1 unrecognized field(s) given to NoneType for serialization: 'blah'"), ( ("asdf", "ghjkl"), - "2 unrecognized field(s) given to NoneType for serialization: 'asdf'," - " 'ghjkl'", + ( + "2 unrecognized field(s) given to NoneType for serialization: 'asdf'," + " 'ghjkl'" + ), ), ( ("abc", "def", "abc"), diff --git a/tests/fields/base_test.py b/tests/fields/base_test.py index 7070e26..3947d7e 100644 --- a/tests/fields/base_test.py +++ b/tests/fields/base_test.py @@ -46,7 +46,7 @@ def test_const_sets_size__bytes(): In this case we assume the simple case where len(const) == size. """ - field = fields.Bytes(const=b"abcdef") + field = fields.Bytes(const=True, default=b"abcdef") assert field.size is not None, "Size wasn't set." assert field.size == 6, "Size is incorrect." @@ -61,7 +61,7 @@ def test_const_set_size__string_ascii(): """Passing `const` will set the size of a string correctly for single byte encodings. """ - field = fields.String(const="asdfghjkl") + field = fields.String(const=True, default="asdfghjkl") assert field.size is not None, "Size wasn't set." assert field.size == 9, "Size is incorrect." @@ -71,7 +71,7 @@ def test_const_set_size__string_utf16(): """Passing `const` will set the size of a string correctly for multi-byte encodings. """ - field = fields.String(const="asdf", encoding="utf-16-le") + field = fields.String(const=True, default="asdf", encoding="utf-16-le") assert field.size is not None, "Size wasn't set." assert field.size == 8, "Size is incorrect." @@ -81,21 +81,21 @@ def test_const_set_size__sized_int_works(): """Already-sized integers shouldn't have a problem with the size override code. """ - field = fields.Int64(const=1234567890) + field = fields.Int64(const=True, default=1234567890) assert field.size is not None, "Size wasn't set." assert field.size == 8, "Size is incorrect." def test_const_set_size__stringz(): """Variable-length strings MUST set their size with ``const``.""" - field = fields.StringZ(const="asdf") + field = fields.StringZ(const=True, default="asdf") assert field.size is not None, "Size wasn't set." assert field.size == 5, "Size is incorrect." def test_const_set_size__stringz_utf16(): """Variable-length strings MUST set their size with ``const``.""" - field = fields.StringZ(const="wxyz", encoding="utf-16") + field = fields.StringZ(const=True, default="wxyz", encoding="utf-16") assert field.size is not None, "Size wasn't set." assert field.size == 12, "Size is incorrect." @@ -167,9 +167,9 @@ def test_dump__field_only__no_value_no_default(): class BasicStructWithArray(binobj.Struct): """A basic structure with a sized array.""" - header = fields.Bytes(const=b"ABC") + header = fields.Bytes(const=True, default=b"ABC") numbers = fields.Array(fields.UInt16(endian="big"), count=2) - trailer = fields.Bytes(const=b"XYZ") + trailer = fields.Bytes(const=True, default=b"XYZ") def test_set_const_crashes__setattr(): @@ -327,7 +327,7 @@ def test_field_subclass_super_delegation(): class BasicPresentStruct(binobj.Struct): flags = fields.UInt16(endian="little") thing = fields.StringZ(present=lambda f, *_: f["flags"] & 0x8000) - other_thing = fields.UInt16(const=0x1234, endian="little") + other_thing = fields.UInt16(const=True, default=0x1234, endian="little") @flags.computes def _flags(self, all_values): @@ -401,13 +401,13 @@ class Meta: assert Test.unaffected.endian == sys.byteorder, "Explicit argument was overridden" +@pytest.mark.xfail def test_callable_default_warns(): """Passing a callable for `default` triggers a warning.""" with pytest.deprecated_call(): fields.Int32(name="whatever", default=lambda: 123) -@pytest.mark.xfail def test_callable_default_crashes(): """Passing a callable for `default` triggers a TypeError.""" with pytest.raises(TypeError): @@ -417,7 +417,7 @@ def test_callable_default_crashes(): def test_default_and_factory_triggers_error(): with pytest.raises( errors.ConfigurationError, - match="Do not pass values for both `default` and `factory`.", + match=r"Do not pass values for both `default` and `factory`\.", ): fields.Int8(name="asdf", default=123, factory=lambda: 123) diff --git a/tests/fields/containers_test.py b/tests/fields/containers_test.py index b8b953c..3bb6355 100644 --- a/tests/fields/containers_test.py +++ b/tests/fields/containers_test.py @@ -107,9 +107,9 @@ def test_array__dump_nested(): class BasicStructWithArray(binobj.Struct): - header = fields.String(const="ABC") + header = fields.String(const=True, default="ABC") numbers = fields.Array(fields.UInt16(endian="big"), count=2) - trailer = fields.String(const="XYZ") + trailer = fields.String(const=True, default="XYZ") def test_array__fixed_in_struct(): @@ -123,7 +123,11 @@ def test_array__fixed_in_struct(): def bswsa_should_halt(_seq, _stream, values, _context, _loaded_fields): - """Halting function for :attr:`BasicStructWithSentinelArray.numbers`.""" + """Halting function for :attr:`BasicStructWithSentinelArray.numbers`. + + Returns: + True if the end of the array has been reached, False otherwise. + """ if values and values[-1] == 0: # Hit sentinel, remove it from the end of the array. del values[-1] @@ -133,7 +137,7 @@ def bswsa_should_halt(_seq, _stream, values, _context, _loaded_fields): class BasicStructWithSentinelArray(binobj.Struct): numbers = fields.Array(fields.UInt8(), halt_check=bswsa_should_halt) - eof = fields.String(const="ABC") + eof = fields.String(const=True, default="ABC") def test_array__variable_length_sentinel_in_struct(): @@ -147,13 +151,13 @@ def test_array__variable_length_sentinel_in_struct(): class BasicStructWithArraySizeField(binobj.Struct): n_numbers = fields.UInt8() numbers = fields.Array(fields.UInt8(), count=n_numbers) - eof = fields.String(const="ABC") + eof = fields.String(const=True, default="ABC") class BasicStructWithArraySizeFieldAsName(binobj.Struct): n_numbers = fields.UInt8() numbers = fields.Array(fields.UInt8(), count="n_numbers") - eof = fields.String(const="ABC") + eof = fields.String(const=True, default="ABC") @pytest.mark.parametrize( @@ -180,7 +184,7 @@ def test_array__variable_length_forward_reference_crashes(): class _Crash(binobj.Struct): n_numbers = fields.UInt8() numbers = fields.Array(fields.UInt8(), count="eof") - eof = fields.String(const="ABC") + eof = fields.String(const=True, default="ABC") with pytest.raises(errors.FieldReferenceError): _Crash.from_bytes(b"\0\0ABC") @@ -288,12 +292,12 @@ def test_array__count_wrong_type(): class UnionItemA(binobj.Struct): - _id = fields.UInt8(const=0xFF) + _id = fields.UInt8(const=True, default=0xFF) value = fields.StringZ() class UnionItemB(binobj.Struct): - _id = fields.UInt8(const=0x7F) + _id = fields.UInt8(const=True, default=0x7F) other = fields.UInt16(endian="little") @@ -418,6 +422,6 @@ def test_union__dump_non_mapping_for_struct(): ) with pytest.raises( - TypeError, match="Cannot dump a non-Mapping-like object as a .+: 'foo'" + TypeError, match=r"Cannot dump a non-Mapping-like object as a .+: 'foo'" ): field.to_bytes("foo") diff --git a/tests/fields/numeric_test.py b/tests/fields/numeric_test.py index aa54bcd..fd0b378 100644 --- a/tests/fields/numeric_test.py +++ b/tests/fields/numeric_test.py @@ -38,7 +38,7 @@ def test_integer_no_specified_size(): def test_const_set_size__varint(): """Variable integers should set their size when ``const`` is defined.""" field = numeric.VariableLengthInteger( - vli_format=varints.VarIntEncoding.VLQ, const=987654321 + vli_format=varints.VarIntEncoding.VLQ, const=True, default=987654321 ) assert field.size == 5 diff --git a/tests/fields/stringlike_test.py b/tests/fields/stringlike_test.py index d80fda4..1b35132 100644 --- a/tests/fields/stringlike_test.py +++ b/tests/fields/stringlike_test.py @@ -12,7 +12,7 @@ from binobj.fields import stringlike -def test_bytes__dump_too_short(): +def test_bytes__dump_too_short() -> None: """Crash if we try dumping a Bytes field without enough bytes.""" field = stringlike.Bytes(size=4) @@ -20,7 +20,7 @@ def test_bytes__dump_too_short(): field.to_bytes(b"") -def test_bytes__dump_too_long(): +def test_bytes__dump_too_long() -> None: """Crash if we try dumping a Bytes field too many bytes.""" field = stringlike.Bytes(size=4) @@ -28,34 +28,34 @@ def test_bytes__dump_too_long(): field.to_bytes(b"!" * 11) -def test_bytes__null(): +def test_bytes__null() -> None: field = fields.Bytes(size=7, null_value=DEFAULT) assert field.to_bytes(None) == b"\x00" * 7 -def test_bytes__dump_null_default(): +def test_bytes__dump_null_default() -> None: field = stringlike.String(null_value=b"trash", default=None) assert field.to_bytes(None) == b"trash" -def test_bytes__dump_null_factory(): +def test_bytes__dump_null_factory() -> None: field = stringlike.Bytes(null_value=b"NULL", factory=lambda: None, size=4) assert field.to_bytes(None) == b"NULL" -def test_string__load_basic(): +def test_string__load_basic() -> None: """Basic test of loading a String""" field = stringlike.String(size=13, encoding="utf-8") assert field.from_bytes(b"\xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf") == r"¯\_(ツ)_/¯" -def test_string__dump_basic(): +def test_string__dump_basic() -> None: """Basic test of dumping a String""" field = stringlike.String(size=13, encoding="utf-8") assert field.to_bytes(r"¯\_(ツ)_/¯") == b"\xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf" -def test_string__dump_no_size(): +def test_string__dump_no_size() -> None: """Try dumping a string without its size set.""" field = stringlike.String() with pytest.raises(errors.UndefinedSizeError): @@ -63,65 +63,65 @@ def test_string__dump_no_size(): @pytest.mark.parametrize("null_value", ["NULL", b"NULL"]) -def test_string__dump_null_default(null_value): +def test_string__dump_null_default(null_value: str | bytes) -> None: field = stringlike.String(null_value=null_value, default=None, size=4) assert field.to_bytes(None) == b"NULL" @pytest.mark.parametrize("null_value", ["NULL", b"NULL"]) -def test_string__dump_null_factory(null_value): +def test_string__dump_null_factory(null_value: str | bytes) -> None: field = stringlike.String(null_value=null_value, factory=lambda: None, size=4) assert field.to_bytes(None) == b"NULL" @pytest.mark.parametrize("size_field", [fields.UInt8(name="size"), "size"]) -def test_string__dump_variable_size(size_field): +def test_string__dump_variable_size(size_field: fields.Field | str) -> None: """Dumping a field with variable size should work.""" field = stringlike.String(size=size_field) assert field.to_bytes("asdf", all_fields={"size": 4}) == b"asdf" -def test_string__dump_too_long_before_encoding(): +def test_string__dump_too_long_before_encoding() -> None: """Basic test of dumping a string that's too long into a String.""" field = stringlike.String(size=5, encoding="utf-8") with pytest.raises(errors.ValueSizeError): assert field.to_bytes("abcdefg") -def test_string__dump_too_long_after_encoding(): +def test_string__dump_too_long_after_encoding() -> None: """Test dumping a string that's too long only after encoding to bytes.""" field = stringlike.String(size=4, encoding="utf-8") with pytest.raises(errors.ValueSizeError): assert field.to_bytes("très") -def test_string__dump_too_short_before_encoding(): +def test_string__dump_too_short_before_encoding() -> None: """Basic test of dumping a string that's too short into a String.""" field = stringlike.String(size=5, encoding="utf-8") with pytest.raises(errors.ValueSizeError): assert field.to_bytes("a") -def test_string__dump_too_short_before_encoding__pad(): +def test_string__dump_too_short_before_encoding__pad() -> None: """Dumping a string that's too short before encoding is okay.""" field = stringlike.String(size=5, pad_byte=b" ") assert field.to_bytes("a") == b"a " -def test_string__dump_too_short_after_encoding__pad(): +def test_string__dump_too_short_after_encoding__pad() -> None: """Dumping a string that's too short but uses padding is okay.""" field = stringlike.String(size=8, pad_byte=b"\0", encoding="utf-32-be") assert field.to_bytes("a") == b"\0\0\0a\0\0\0\0" -def test_string__dump_too_long_before_encoding__pad(): +def test_string__dump_too_long_before_encoding__pad() -> None: """``pad_byte`` shouldn't prevent a crash if a string is too long.""" field = stringlike.String(size=5, pad_byte=b"?") with pytest.raises(errors.ValueSizeError): field.to_bytes("abcdefgh") -def test_string__dump_too_long_after_encoding__pad(): +def test_string__dump_too_long_after_encoding__pad() -> None: """``pad_byte`` shouldn't prevent a crash if a string is too long after encoding it.""" field = stringlike.String(size=3, pad_byte=b"?", encoding="utf-16-le") @@ -129,7 +129,7 @@ def test_string__dump_too_long_after_encoding__pad(): field.to_bytes("ab") -def test_stringz__dump_null_default(): +def test_stringz__dump_null_default() -> None: field = fields.StringZ(size=7, null_value=DEFAULT) assert field.to_bytes(None) == b"\x00" * 7 @@ -138,18 +138,20 @@ def test_stringz__dump_null_default(): "default_value_kwarg", [{"default": None}, {"factory": lambda: None}] ) @pytest.mark.parametrize("null_value", ["NULL", b"NULL\x00"]) -def test_stringz__unsized_dump_null_default(null_value, default_value_kwarg): +def test_stringz__unsized_dump_null_default( + null_value: str | bytes, default_value_kwarg: dict[str, object] +): field = stringlike.StringZ(null_value=null_value, **default_value_kwarg) assert field.to_bytes(None) == b"NULL\x00" -def test_stringz__dump_too_long(): +def test_stringz__dump_too_long() -> None: field = stringlike.StringZ(size=5) with pytest.raises(errors.ValueSizeError): field.to_bytes("asdfqwerty") -def test_stringz__dump_too_short(): +def test_stringz__dump_too_short() -> None: field = stringlike.StringZ(size=5) with pytest.raises(errors.ValueSizeError): field.to_bytes("a") @@ -160,59 +162,59 @@ class StringZStringStruct(Struct): data_field = stringlike.StringZ(size=size_field) -def test_stringz__dump_too_long__size_field(): +def test_stringz__dump_too_long__size_field() -> None: instance = StringZStringStruct(size_field=4, data_field="asdfqwerty") with pytest.raises(errors.ValueSizeError): instance.to_bytes() -def test_stringz__dump_too_short__size_field(): +def test_stringz__dump_too_short__size_field() -> None: instance = StringZStringStruct(size_field=4, data_field="a") with pytest.raises(errors.ValueSizeError): instance.to_bytes() -def test_string__load_null_default(): +def test_string__load_null_default() -> None: field = fields.String(size=7, null_value=DEFAULT) assert field.from_bytes(b"\x00" * 7) is None @pytest.mark.parametrize("null_value", [b"N\x00U\x00L\x00L\x00", "NULL"]) -def test_string__null_value(null_value): +def test_string__null_value(null_value: str | bytes) -> None: field = fields.String(size=8, null_value=null_value, encoding="utf-16-le") assert field.from_bytes(b"N\x00U\x00L\x00L\x00") is None -def test_string__pad_byte_wrong_type(): +def test_string__pad_byte_wrong_type() -> None: """Trying to pass a regular string as pad_byte will explode.""" with pytest.raises(errors.ConfigurationError): stringlike.String(size=4, pad_byte=" ") # type: ignore[arg-type] -def test_string__pad_byte_too_long(): +def test_string__pad_byte_too_long() -> None: """The padding byte must be exactly one byte.""" with pytest.raises(errors.ConfigurationError): stringlike.String(size=4, pad_byte=b"0123") -def test_string__pad_default(): +def test_string__pad_default() -> None: """The default value should be padded if necessary.""" field = stringlike.String(size=4, pad_byte=b" ", default="?") assert field.to_bytes() == b"? " -def test_stringz__pad(): +def test_stringz__pad() -> None: field = stringlike.StringZ(size=4, pad_byte=b" ") assert field.to_bytes("a") == b"a\x00 " -def test_stringz__load_basic(): +def test_stringz__load_basic() -> None: """Basic test of StringZ loading.""" field = stringlike.StringZ(encoding="utf-8") assert field.from_bytes(b"\xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf\0") == r"¯\_(ツ)_/¯" -def test_stringz__load_eof_before_null(): +def test_stringz__load_eof_before_null() -> None: """Crash if we hit the end of the data before we get a null byte.""" field = stringlike.StringZ(encoding="utf-8") with pytest.raises(errors.DeserializationError): @@ -225,7 +227,7 @@ def test_stringz__dump_basic(): assert field.to_bytes(r"¯\_(ツ)_/¯") == b"\xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf\0" -def test_stringz__dump_multibyte(): +def test_stringz__dump_multibyte() -> None: """Basic multibyte test dump.""" field = stringlike.StringZ(encoding="utf-32-le") assert ( @@ -234,7 +236,7 @@ def test_stringz__dump_multibyte(): ) -def test_stringz__dump_multibyte_with_bom(): +def test_stringz__dump_multibyte_with_bom() -> None: """Ensure multibyte encodings work with StringZ as well and the BOM isn't added before the null byte.""" field = stringlike.StringZ(encoding="utf-16") @@ -245,7 +247,7 @@ def test_stringz__dump_multibyte_with_bom(): assert field.to_bytes("AbCd") == b"\xfe\xff\x00A\x00b\x00C\x00d\x00\x00" -def test_stringz_load_multibyte(): +def test_stringz_load_multibyte() -> None: """Test loading multibyte strings with a terminating null.""" field = stringlike.StringZ(encoding="utf-16") assert field.from_bytes(b"\xff\xfeA\x00b\x00C\x00d\x00\x00\x00") == "AbCd" @@ -253,24 +255,24 @@ def test_stringz_load_multibyte(): @pytest.mark.parametrize("null_value", [b"NULL\x00", "NULL"]) -def test_stringz__load_null_value(null_value): +def test_stringz__load_null_value(null_value: str | bytes) -> None: field = stringlike.StringZ(null_value=null_value) assert field.from_bytes(b"NULL\x00") is None -def test_stringz__load_default_null_crashes(): +def test_stringz__load_default_null_crashes() -> None: field = stringlike.StringZ(null_value=DEFAULT) with pytest.raises(errors.CannotDetermineNullError): field.from_bytes(b"\x00") -def test_stringz__dump_default_null_crashes(): +def test_stringz__dump_default_null_crashes() -> None: field = stringlike.StringZ(null_value=DEFAULT) with pytest.raises(errors.UnserializableValueError): field.to_bytes(None) -def test_stringz__load_with_default(): +def test_stringz__load_with_default() -> None: field = stringlike.StringZ(default="abc123") assert field.from_bytes(b"qwerty\x00") == "qwerty" @@ -284,11 +286,13 @@ def test_stringz__load_with_default(): (stringlike.UUIDFormat.HEX_STRING, "hex"), ], ) -def test_uuid_round_trip(storage_format, accessor_name): +def test_uuid_round_trip( + storage_format: stringlike.UUIDFormat, accessor_name: str +) -> None: field = stringlike.UUID4(stored_as=storage_format) value = uuid.uuid4() serialized = field.to_bytes(value) - if accessor_name == "": + if not accessor_name: expected_value = str(value).encode("ascii") elif accessor_name == "hex": expected_value = value.hex.encode("ascii") @@ -301,7 +305,7 @@ def test_uuid_round_trip(storage_format, accessor_name): assert loaded == value, "Deserialized value is wrong" -def test_uuid_short_read(): +def test_uuid_short_read() -> None: field = stringlike.UUID4(stored_as=stringlike.UUIDFormat.CANONICAL_STRING) with pytest.raises(errors.UnexpectedEOFError): field.from_bytes(b"asdf") diff --git a/tests/full_examples/bmp_test.py b/tests/full_examples/bmp_test.py index 9ad33e1..b7f628e 100644 --- a/tests/full_examples/bmp_test.py +++ b/tests/full_examples/bmp_test.py @@ -22,16 +22,16 @@ class SimpleBMPFileHeader(binobj.Struct): is valid. """ - magic = fields.Bytes(const=b"BM", discard=True) + magic = fields.Bytes(const=True, default=b"BM", discard=True) file_size = fields.UInt32() - _reserved = fields.Bytes(const=b"\0\0\0\0", discard=True) + _reserved = fields.Bytes(const=True, default=b"\0\0\0\0", discard=True) pixels_offset = fields.UInt32() # Legacy DIB header (BITMAPINFOHEADER) - header_size = fields.UInt32(const=40, discard=True) + header_size = fields.UInt32(const=True, default=40, discard=True) image_width = fields.Int32() image_height = fields.Int32() - n_color_planes = fields.UInt16(const=1) + n_color_planes = fields.UInt16(const=True, default=1) n_bits_per_pixel = fields.UInt16() compression_method = fields.UInt32() bitmap_size = fields.UInt32() diff --git a/tests/full_examples/cpio_test.py b/tests/full_examples/cpio_test.py index b2106ff..4609022 100644 --- a/tests/full_examples/cpio_test.py +++ b/tests/full_examples/cpio_test.py @@ -19,7 +19,7 @@ class Meta: "endian": "little", } - magic = fields.UInt16(const=0o070707, discard=True) + magic = fields.UInt16(const=True, default=0o070707, discard=True) device_id = fields.UInt16(default=0) inumber = fields.UInt16(default=0) mode = fields.UInt16(default=0o644) @@ -32,7 +32,10 @@ class Meta: file_size = fields.UInt32() filename = fields.StringZ(encoding="utf-8") _filename_padding = fields.Bytes( - const=b"\0", discard=True, present=lambda f, *_: f["name_size"] % 2 + const=True, + default=b"\0", + discard=True, + present=lambda f, *_: f["name_size"] % 2, ) data = fields.Bytes(size=file_size) @@ -54,24 +57,21 @@ def test_dump__padding_null_behaves(filename): struct = CPIOFileHeader(filename=filename, data=file_data, modified_time=when) serialized = struct.to_bytes() - assert ( - serialized - == ( - b"\xc7\x71" # Magic - b"\x00\x00" # Device ID - b"\x00\x00" # inumber - b"\xa4\x01" # Mode - b"\x00\x00" # Owner UID - b"\x00\x00" # Owner GID - b"\x00\x00" # number of links - b"\x00\x00" # Device version - + int(when.timestamp()).to_bytes(4, "little") # Modified time - + len(filename_bytes).to_bytes(2, "little") # Filename size - + len(file_data).to_bytes(4, "little") # File size - + filename_bytes - + (b"\0" * (len(filename_bytes) % 2)) - + file_data # File contents - ) + assert serialized == ( + b"\xc7\x71" # Magic + b"\x00\x00" # Device ID + b"\x00\x00" # inumber + b"\xa4\x01" # Mode + b"\x00\x00" # Owner UID + b"\x00\x00" # Owner GID + b"\x00\x00" # number of links + b"\x00\x00" # Device version + + int(when.timestamp()).to_bytes(4, "little") # Modified time + + len(filename_bytes).to_bytes(2, "little") # Filename size + + len(file_data).to_bytes(4, "little") # File size + + filename_bytes + + (b"\0" * (len(filename_bytes) % 2)) + + file_data # File contents ) diff --git a/tests/full_examples/fat_test.py b/tests/full_examples/fat_test.py index 3bb3309..f60d3cd 100644 --- a/tests/full_examples/fat_test.py +++ b/tests/full_examples/fat_test.py @@ -9,7 +9,7 @@ class FAT12BootSector(binobj.Struct): - jump = fields.Bytes(const=b"\xeb\x3c\x90") + jump = fields.Bytes(const=True, default=b"\xeb\x3c\x90") oem_name = fields.String(size=8, default="mkdosfs", pad_byte=b" ", encoding="ascii") bytes_per_sector = fields.UInt16(default=512) sectors_per_cluster = fields.UInt8() @@ -24,13 +24,13 @@ class FAT12BootSector(binobj.Struct): num_hidden_sectors = fields.UInt32(default=0) total_logical_sectors_32 = fields.UInt32() drive_number = fields.UInt8() - _reserved = fields.Bytes(const=b"\0", discard=True) - _ex_boot_signature = fields.Bytes(const=b"\x29", discard=True) + _reserved = fields.Bytes(const=True, default=b"\0", discard=True) + _ex_boot_signature = fields.Bytes(const=True, default=b"\x29", discard=True) volume_id = fields.UInt32(factory=lambda: random.randrange(2**32)) volume_label = fields.String(size=11) fs_type = fields.String(size=8, default="FAT12", pad_byte=b" ", encoding="ascii") boot_code = fields.Bytes(size=448, default=b"\xcc" * 448) - boot_signature = fields.Bytes(const=b"\x55\xaa") + boot_signature = fields.Bytes(const=True, default=b"\x55\xaa") @property def total_logical_sectors(self): @@ -40,7 +40,7 @@ def total_logical_sectors(self): def total_logical_sectors(self, total_sectors): if total_sectors < 1 or total_sectors >= 2**32: raise ValueError( - "Total sectors must be in [1, 2^32). Got: %d" % total_sectors + f"Total sectors must be in [1, 2^32). Got: {total_sectors!r}" ) if total_sectors < 65535: self.total_logical_sectors_16 = total_sectors diff --git a/tests/full_examples/wav_test.py b/tests/full_examples/wav_test.py index 71ee0bd..ac9168f 100644 --- a/tests/full_examples/wav_test.py +++ b/tests/full_examples/wav_test.py @@ -17,9 +17,9 @@ class WAVFileHeader(binobj.Struct): - riff_header = fields.Bytes(const=b"RIFF") + riff_header = fields.Bytes(const=True, default=b"RIFF") size = fields.UInt32(endian="little") - file_format = fields.Bytes(const=b"WAVE") + file_format = fields.Bytes(const=True, default=b"WAVE") # Format and data chunks follow @@ -30,8 +30,8 @@ class Meta: "endian": "little", } - chunk_id = fields.Bytes(const=b"fmt ") - size = fields.UInt32(const=16) + chunk_id = fields.Bytes(const=True, default=b"fmt ") + size = fields.UInt32(const=True, default=16) audio_format = fields.UInt16() n_channels = fields.UInt16() sample_rate = fields.UInt32() @@ -54,7 +54,7 @@ def _block_alignment(self, all_fields): class WAVDataChunk(binobj.Struct): - chunk_id = fields.Bytes(const=b"data") + chunk_id = fields.Bytes(const=True, default=b"data") size = fields.UInt32(endian="little") audio_data = fields.Bytes(size=size) diff --git a/tests/pep526_no_future_test.py b/tests/pep526_no_future_test.py index 7673061..9fcc808 100644 --- a/tests/pep526_no_future_test.py +++ b/tests/pep526_no_future_test.py @@ -5,12 +5,9 @@ """ import random -import typing from typing import Annotated from typing import Any from typing import ClassVar -from typing import Optional -from typing import Union import pytest @@ -93,12 +90,12 @@ def test_typing_union_breaks(): @dataclass class _BrokenClass(binobj.Struct): - some_value: typing.Union[binobj.UInt32, binobj.UInt16] + some_value: binobj.UInt32 | binobj.UInt16 @dataclass class NullableFieldsStruct(binobj.Struct): - nullable: Optional[fields.Int32] + nullable: fields.Int32 | None not_nullable: fields.StringZ @@ -158,11 +155,12 @@ def test_nested_works( NestedFields: type[binobj.Struct], BasicClass: type[binobj.Struct] ): assert isinstance(NestedFields.basic, fields.Nested) - assert ( - NestedFields.basic.struct_class is BasicClass - ), f"{NestedFields.basic.struct_class=}, wanted {BasicClass!r}" + assert NestedFields.basic.struct_class is BasicClass, ( + f"{NestedFields.basic.struct_class=}, wanted {BasicClass!r}" + ) +@pytest.mark.xfail def test_passing_callable_triggers_warning(): with pytest.deprecated_call(): @@ -171,7 +169,6 @@ class _DeprecatedCallable(binobj.Struct): some_field: fields.Int32 = lambda: random.randrange(1024) -@pytest.mark.xfail def test_passing_callable_crashes(): with pytest.raises(TypeError): @@ -202,8 +199,8 @@ def test_pep593_annotated__with_union(): @dataclass class PEP593Class(binobj.Struct): - foo: Annotated[Union[int, float], fields.UInt32] = 123 - bar: Annotated[Union[int, str, None], fields.String(size=16)] + foo: Annotated[int | float, fields.UInt32] = 123 + bar: Annotated[int | str | None, fields.String(size=16)] ignore_me: int ignore_as_well: Annotated[int, bool] @@ -221,7 +218,7 @@ def test_pep593_annotated__not_as_first(): @dataclass class PEP593Class(binobj.Struct): foo: Annotated[int, float, fields.UInt32, bool] = 123 - bar: Annotated[Union[int, str], bool, fields.String(size=16)] + bar: Annotated[int | str, bool, fields.String(size=16)] ignore_me: int ignore_as_well: Annotated[int, bool] diff --git a/tests/pep526_test.py b/tests/pep526_test.py index 0335dc8..7f91109 100644 --- a/tests/pep526_test.py +++ b/tests/pep526_test.py @@ -8,12 +8,9 @@ import random import sys -import typing from typing import Annotated from typing import ClassVar -from typing import Optional from typing import TYPE_CHECKING -from typing import Union import pytest @@ -104,12 +101,12 @@ def test_typing_union_breaks(): @dataclass class _BrokenClass(binobj.Struct): - some_value: typing.Union[binobj.UInt32, binobj.UInt16] + some_value: binobj.UInt32 | binobj.UInt16 @dataclass class NullableFieldsStruct(binobj.Struct): - nullable: Optional[fields.Int32] + nullable: fields.Int32 | None not_nullable: fields.StringZ @@ -169,9 +166,9 @@ def test_nested_works( NestedFields: type[binobj.Struct], BasicClass: type[binobj.Struct] ): assert isinstance(NestedFields.basic, fields.Nested) - assert ( - NestedFields.basic.struct_class is BasicClass - ), f"{NestedFields.basic.struct_class=}, wanted {BasicClass!r}" + assert NestedFields.basic.struct_class is BasicClass, ( + f"{NestedFields.basic.struct_class=}, wanted {BasicClass!r}" + ) def test_passing_callable_triggers_warning(): @@ -213,8 +210,8 @@ def test_pep593_annotated__with_union(): @dataclass class PEP593Class(binobj.Struct): - foo: Annotated[Union[int, float], fields.UInt32] = 123 - bar: Annotated[Union[int, str, None], fields.String(size=16)] + foo: Annotated[int | float, fields.UInt32] = 123 + bar: Annotated[int | str | None, fields.String(size=16)] ignore_me: int ignore_as_well: Annotated[int, bool] @@ -232,7 +229,7 @@ def test_pep593_annotated__not_as_first(): @dataclass class PEP593Class(binobj.Struct): foo: Annotated[int, float, fields.UInt32, bool] = 123 - bar: Annotated[Union[int, str], bool, fields.String(size=16)] + bar: Annotated[int | str, bool, fields.String(size=16)] ignore_me: int ignore_as_well: Annotated[int, bool] diff --git a/tests/serialization_test.py b/tests/serialization_test.py index 54a99b7..7762614 100644 --- a/tests/serialization_test.py +++ b/tests/serialization_test.py @@ -45,13 +45,13 @@ def test_dump__use_default_value(): assert field.to_bytes() == b"\xde\xad\xbe\xef" +@pytest.mark.xfail def test_dump__use_default_callable_warns(): """Test dumping when the default value is a callable.""" with pytest.deprecated_call(): fields.UInt32(name="field", default=lambda: 0x1234, endian="big") -@pytest.mark.xfail def test_dump__use_default_callable_crashes(): """Test dumping when the default value is a callable.""" with pytest.raises(TypeError): @@ -215,7 +215,7 @@ def test_to_dict__crash_on_undefined(): class Basic(binobj.Struct): - abc = fields.Bytes(const=b"ABC") + abc = fields.Bytes(const=True, default=b"ABC") ghi = fields.Int32() jkl = fields.Int64(default=0xBADC0FFEE) mno = fields.String(size=2) diff --git a/tests/structures_test.py b/tests/structures_test.py index 78f6c4c..3357fc9 100644 --- a/tests/structures_test.py +++ b/tests/structures_test.py @@ -120,7 +120,7 @@ def test_partial_load__bad_column(): stream = io.BytesIO(b"zyxwvut\0\xba\xdc\x0f\xfe\xe1\x5b\xad\x01") with pytest.raises( - ValueError, match="BasicStruct doesn't have a field named 'lol'." + ValueError, match=r"BasicStruct doesn't have a field named 'lol'\." ): BasicStruct.partial_load(stream, "lol") @@ -317,7 +317,7 @@ class _Crash(BasicStruct): def test_load__discarded_fields_not_present(): class _Dumb(binobj.Struct): first = fields.String(size=2) - reserved = fields.Bytes(const=b"\0\0", discard=True) + reserved = fields.Bytes(const=True, default=b"\0\0", discard=True) last = fields.String(size=2) loaded = _Dumb().from_bytes(b"AB\0\0CD").to_dict() diff --git a/tests/validators_test.py b/tests/validators_test.py index 4640148..e043921 100644 --- a/tests/validators_test.py +++ b/tests/validators_test.py @@ -145,7 +145,7 @@ def validator(self, dct): ) class Subclass(Class): - const = fields.Bytes(const=b"asdf") + const = fields.Bytes(const=True, default=b"asdf") with pytest.raises(errors.ValidationError): Subclass(string="123", integer=456).to_bytes() @@ -165,7 +165,7 @@ def validator(self, field, value): raise errors.ValidationError("high", field=field, value=value) class Subclass(Class): - const = fields.Bytes(const=b"asdf") + const = fields.Bytes(const=True, default=b"asdf") @decorators.validates("string") def validator_2(self, field, value): diff --git a/tests/varints_test.py b/tests/varints_test.py index 0a7f78a..28f6a6c 100644 --- a/tests/varints_test.py +++ b/tests/varints_test.py @@ -20,7 +20,7 @@ def test_encode_vlq_basic(value, expected): def test_encode_vlq_negative_crashes(): """Passing a negative value to the VLQ encoder must always fail.""" with pytest.raises( - ValueError, match="The VLQ integer encoding doesn't support negative numbers." + ValueError, match=r"The VLQ integer encoding doesn't support negative numbers\." ): varints.encode_integer_vlq(-1) @@ -76,7 +76,7 @@ def test_encode_uleb128(value, expected): def test_encode_uleb128__negative_crashes(): with pytest.raises( ValueError, - match="The ULEB128 integer encoding doesn't support negative numbers.", + match=r"The ULEB128 integer encoding doesn't support negative numbers\.", ): assert varints.encode_integer_uleb128(-1) diff --git a/tox.ini b/tox.ini index 0a5a8a6..0e899e1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py39, py310, py311, py312, py313, pypy39, pypy310, lint, mypy +envlist = py311, py312, py313, py314, lint, mypy, format skip_missing_interpreters = true setenv = PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring @@ -8,7 +8,7 @@ setenv = allowlist_externals = poetry skip_install = true commands_pre = poetry install -commands = poetry run pytest --cov=binobj --import-mode importlib {posargs:tests} +commands = poetry run pytest {posargs:tests} passenv = COV_PYTHON_VERSION COV_PLATFORM @@ -16,58 +16,19 @@ passenv = [testenv:lint] allowlist_externals = poetry -basepython = python3.12 skip_install = true commands_pre = poetry install -commands = poetry run ruff check binobj tests +commands = poetry run ruff check -[testenv:mypy] +[testenv:format] allowlist_externals = poetry -basepython = python3.12 skip_install = true commands_pre = poetry install -commands = poetry run mypy --config-file tox.ini binobj - -[pytest] -addopts = - -vv - --cov=binobj - --cov-config=tox.ini -filterwarnings = - always::DeprecationWarning - -[coverage:run] -branch = true -source = binobj -plugins = - coverage_pyver_pragma - -[coverage:report] -show_missing = true -skip_covered = true -fail_under = 96.9 -precision = 1 - +commands = poetry run ruff format --check -[mypy] -allow_untyped_globals = false -allow_redefinition = false -check_untyped_defs = true -disallow_any_decorated = false -disallow_any_generics = true -disallow_any_unimported = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -ignore_missing_imports = false -implicit_reexport = false -no_implicit_optional = true -show_error_codes = true -strict_optional = true -warn_no_return = true -warn_unreachable = true -warn_unused_ignores = true -warn_redundant_casts = true -warn_return_any = true +[testenv:mypy] +allowlist_externals = poetry +basepython = python3.13 +skip_install = true +commands_pre = poetry install +commands = poetry run mypy binobj