diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3bdc963..e09f947 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,10 @@ name: CI -on: [push, pull_request] +on: + push: + pull_request: + workflow_dispatch: + workflow_call: jobs: build: @@ -8,7 +12,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest', 'windows-latest'] - python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] + python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] runs-on: ${{ matrix.os }} name: OS ${{ matrix.os }}, Python ${{ matrix.python-version }} @@ -35,6 +39,7 @@ jobs: - name: Install pyright run: | npm install -g pyright + pyright --version # Install this package - name: Install this package @@ -61,3 +66,30 @@ jobs: - name: Run pyright run: | pyright + + create_wheel_and_sdist: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + architecture: x64 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install wheel build + + - name: Build wheel and sdist + run: | + python -m build + + - name: Upload wheel and sdist as artifact + uses: actions/upload-artifact@v4 + with: + name: Package-Distributions-construct-typing + path: dist/ \ No newline at end of file diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4e1ef42..ea14263 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,6 +1,3 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - name: Upload Python Package on: @@ -8,24 +5,26 @@ on: types: [created] jobs: - deploy: + create_wheel_and_sdist: + name: create_wheel_and_sdist + uses: ./.github/workflows/main.yml + deploy: + needs: [ create_wheel_and_sdist ] runs-on: ubuntu-latest + + environment: pypi + permissions: + id-token: write # IMPORTANT: this permission is mandatory for Trusted Publishing steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + + - name: Download artifacts + uses: actions/download-artifact@v4 with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + name: Package-Distributions-construct-typing + path: ./dist + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index b3d4398..1d9e0fe 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,6 @@ dmypy.json example_737 example_888 example_ksy.ksy + +# Test stuff +devtest/ \ No newline at end of file diff --git a/README.md b/README.md index b11989c..c4bac18 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +## Modified version of "construct-typing" module used in my projects. +This modification features: +- **[EnhancedDataclassMixin](https://github.com/waszil/construct-typing/commit/479b51344bfd95149596a75ee574ac2e63c032df) with additional features** +- **ConstantOrContextLambda2 type** +- **Typing for Subconstruct** +- **Type hint for Computed** +- **Switch typing fixes** + +The original README.md file was described down below: # construct-typing [![PyPI](https://img.shields.io/pypi/v/construct-typing)](https://pypi.org/project/construct-typing/) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/construct-typing) diff --git a/construct-stubs/__init__.pyi b/construct-stubs/__init__.pyi index 858384d..2cad48a 100644 --- a/construct-stubs/__init__.pyi +++ b/construct-stubs/__init__.pyi @@ -3,6 +3,10 @@ from construct.debug import * from construct.expr import * from construct.lib import * from construct.version import * +from construct import lib + +__author__: str +__version__: str #=============================================================================== # exposed names diff --git a/construct-stubs/core.pyi b/construct-stubs/core.pyi index 392c8fc..7ed1af6 100644 --- a/construct-stubs/core.pyi +++ b/construct-stubs/core.pyi @@ -17,6 +17,10 @@ from construct.lib import ( ListType, RebufferedBytesIO, ) +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.aead import AESCCM, AESGCM, ChaCha20Poly1305 +from cryptography.hazmat.primitives.ciphers.modes import Mode +from typing_extensions import Buffer, TypeAlias # unfortunately, there are a few duplications with "typing", e.g. Union and Optional, which is why the t. prefix must be used everywhere @@ -25,11 +29,7 @@ from construct.lib import ( # - Higher Kinded Types: https://github.com/python/typing/issues/548 # - Higher Kinded Types: https://sobolevn.me/2020/10/higher-kinded-types-in-python -# The type checkers mypy and pyright/pylance unfortunately work a little bit different with __init__ and __new__. -# For supporting some constructs (eg. Enum, NamedTuple, Slicing) in mypy the __init__ self parameter has to have a -# type hint. But for supporting pyright/pylance, the same type hint has to be used as the return type of __new__. -# (see discussion here: https://github.com/python/typeshed/issues/4846). - +ReadableBuffer: TypeAlias = Buffer StreamType = t.IO[bytes] FilenameType = t.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] PathType = str @@ -70,6 +70,7 @@ class RawCopyError(ConstructError): ... class RotationError(ConstructError): ... class ChecksumError(ConstructError): ... class CancelParsing(ConstructError): ... +class CipherError(ConstructError): ... # =============================================================================== # used internally @@ -89,6 +90,17 @@ def stream_size(stream: StreamType) -> int: ... def stream_iseof(stream: StreamType) -> bool: ... def evaluate(param: ConstantOrContextLambda2[T], context: Context) -> T: ... +class BytesIOWithOffsets(io.BytesIO): + @staticmethod + def from_reading( + stream: StreamType, length: int, path: PathType + ) -> BytesIOWithOffsets: ... + def __init__( + self, contents: bytes, parent_stream: StreamType, offset: int + ) -> None: ... + def tell(self) -> int: ... + def seek(self, offset: int, whence: int = ...) -> int: ... + # =============================================================================== # abstract constructs # =============================================================================== @@ -100,7 +112,7 @@ class Construct(t.Generic[ParsedType, BuildTypes]): docs: str flagbuildnone: bool parsed: t.Optional[t.Callable[[ParsedType, Context], None]] - def parse(self, data: bytes, **contextkw: ContextKWType) -> ParsedType: ... + def parse(self, data: ReadableBuffer, **contextkw: ContextKWType) -> ParsedType: ... def parse_stream( self, stream: StreamType, **contextkw: ContextKWType ) -> ParsedType: ... @@ -110,15 +122,17 @@ class Construct(t.Generic[ParsedType, BuildTypes]): def build(self, obj: BuildTypes, **contextkw: ContextKWType) -> bytes: ... def build_stream( self, obj: BuildTypes, stream: StreamType, **contextkw: ContextKWType - ) -> bytes: ... + ) -> None: ... def build_file( self, obj: BuildTypes, filename: FilenameType, **contextkw: ContextKWType - ) -> bytes: ... + ) -> None: ... def sizeof(self, **contextkw: ContextKWType) -> int: ... def compile( self, filename: FilenameType = ... ) -> Construct[ParsedType, BuildTypes]: ... - def benchmark(self, sampledata: bytes, filename: FilenameType = ...) -> str: ... + def benchmark( + self, sampledata: ReadableBuffer, filename: FilenameType = ... + ) -> str: ... def export_ksy( self, schemaname: str = ..., filename: FilenameType = ... ) -> str: ... @@ -134,20 +148,22 @@ class Construct(t.Generic[ParsedType, BuildTypes]): self, other: t.Union[str, bytes, t.Callable[[ParsedType, Context], None]], ) -> Renamed[ParsedType, BuildTypes]: ... - def __add__( - self, other: Construct[t.Any, t.Any] - ) -> Struct[Container[t.Any], t.Optional[t.Dict[str, t.Any]]]: ... - def __rshift__( - self, other: Construct[t.Any, t.Any] - ) -> Sequence[ListContainer[t.Any], t.Optional[t.List[t.Any]]]: ... - def __getitem__( - self, count: t.Union[int, t.Callable[[Context], int]] - ) -> Array[ + def __add__(self, other: Construct[t.Any, t.Any]) -> Struct: ... + def __rshift__(self, other: Construct[t.Any, t.Any]) -> Sequence: ... + def __getitem__(self, count: t.Union[int, t.Callable[[Context], int]]) -> Array[ ParsedType, BuildTypes, - ListContainer[ParsedType], - t.List[BuildTypes], ]: ... + def _parse( + self, stream: StreamType, context: Context, path: PathType + ) -> ParsedType: ... + def _parsereport( + self, stream: StreamType, context: Context, path: PathType + ) -> ParsedType: ... + def _build( + self, obj: BuildTypes, stream: StreamType, context: Context, path: PathType + ) -> int: ... + def _sizeof(self, context: Context, path: PathType) -> int: ... @t.type_check_only class Context(Container[t.Any]): @@ -174,25 +190,13 @@ class Subconstruct( ): subcon: Construct[SubconParsedType, SubconBuildTypes] @t.overload - def __new__( - cls, subcon: Construct[SubconParsedType, SubconBuildTypes] - ) -> Subconstruct[ - SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes - ]: ... - @t.overload - def __new__( - cls, *args: t.Any, **kwargs: t.Any - ) -> Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]: ... - @t.overload def __init__( - self: Subconstruct[ - SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes - ], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], ) -> None: ... @t.overload - def __init__( - self: Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes], + def __init__( # type: ignore + self, *args: t.Any, **kwargs: t.Any, ) -> None: ... @@ -229,33 +233,34 @@ class Tunnel( def _decode(self, data: bytes, context: Context, path: PathType) -> bytes: ... def _encode(self, data: bytes, context: Context, path: PathType) -> bytes: ... -# TODO: Compiled +class Compiled(Construct[t.Any, t.Any]): + source: t.Optional[str] + defersubcon: t.Optional[Construct[t.Any, t.Any]] + parsefunc: t.Callable[[StreamType, Context], t.Any] + buildfunc: t.Callable[[t.Any, StreamType, Context], t.Any] + def __init__( + self, + parsefunc: t.Callable[[StreamType, Context], t.Any], + buildfunc: t.Callable[[t.Any, StreamType, Context], t.Any], + ) -> None: ... # =============================================================================== # bytes and bits # =============================================================================== -class Bytes(Construct[ParsedType, BuildTypes]): +class Bytes(Construct[bytes, t.Union[bytes, bytearray, int]]): length: ConstantOrContextLambda[int] - def __new__( - cls, - length: ConstantOrContextLambda[int], - ) -> Bytes[bytes, t.Union[bytes, int]]: ... def __init__( - self: Bytes[bytes, t.Union[bytes, int]], + self, length: ConstantOrContextLambda[int], ) -> None: ... -GreedyBytes: Construct[bytes, bytes] +GreedyBytes: Construct[bytes, t.Union[bytes, bytearray]] -def Bitwise( - subcon: Construct[SubconParsedType, SubconBuildTypes] -) -> t.Union[ +def Bitwise(subcon: Construct[SubconParsedType, SubconBuildTypes]) -> t.Union[ Transformed[SubconParsedType, SubconBuildTypes], Restreamed[SubconParsedType, SubconBuildTypes], ]: ... -def Bytewise( - subcon: Construct[SubconParsedType, SubconBuildTypes] -) -> t.Union[ +def Bytewise(subcon: Construct[SubconParsedType, SubconBuildTypes]) -> t.Union[ Transformed[SubconParsedType, SubconBuildTypes], Restreamed[SubconParsedType, SubconBuildTypes], ]: ... @@ -273,101 +278,61 @@ class FormatField(Construct[ParsedType, BuildTypes]): FORMAT_BOOL = t.Literal["?"] @t.overload def __new__( - cls, + cls: "type[FormatField[int, int]]", endianity: str, format: FORMAT_INT, ) -> FormatField[int, int]: ... @t.overload def __new__( - cls, + cls: "type[FormatField[float, float]]", endianity: str, format: FORMAT_FLOAT, ) -> FormatField[float, float]: ... @t.overload def __new__( - cls, + cls: "type[FormatField[bool, bool]]", endianity: str, format: FORMAT_BOOL, ) -> FormatField[bool, bool]: ... @t.overload def __new__( - cls, + cls: "type[FormatField[t.Any, t.Any]]", endianity: str, format: str, ) -> FormatField[t.Any, t.Any]: ... - @t.overload - def __init__( - self: FormatField[int, int], - endianity: str, - format: FORMAT_INT, - ) -> None: ... - @t.overload - def __init__( - self: FormatField[float, float], - endianity: str, - format: FORMAT_FLOAT, - ) -> None: ... - @t.overload - def __init__( - self: FormatField[bool, bool], - endianity: str, - format: FORMAT_BOOL, - ) -> None: ... - @t.overload - def __init__( - self: FormatField[t.Any, t.Any], - endianity: str, - format: str, - ) -> None: ... + else: def __new__( - cls, + cls: "type[FormatField[t.Any, t.Any]]", endianity: str, format: str, ) -> FormatField[t.Any, t.Any]: ... - def __init__( - self: FormatField[t.Any, t.Any], - endianity: str, - format: str, - ) -> None: ... -class BytesInteger(Construct[ParsedType, BuildTypes]): +class BytesInteger(Construct[int, int]): length: ConstantOrContextLambda[int] signed: bool swapped: ConstantOrContextLambda[bool] - def __new__( - cls, - length: ConstantOrContextLambda[int], - signed: bool = ..., - swapped: ConstantOrContextLambda[bool] = ..., - ) -> BytesInteger[int, int]: ... def __init__( - self: BytesInteger[int, int], + self, length: ConstantOrContextLambda[int], signed: bool = ..., swapped: ConstantOrContextLambda[bool] = ..., ) -> None: ... -class BitsInteger(Construct[ParsedType, BuildTypes]): +class BitsInteger(Construct[int, int]): length: ConstantOrContextLambda[int] signed: bool swapped: ConstantOrContextLambda[bool] - def __new__( - cls, - length: ConstantOrContextLambda[int], - signed: bool = ..., - swapped: ConstantOrContextLambda[bool] = ..., - ) -> BitsInteger[int, int]: ... def __init__( - self: BitsInteger[int, int], + self, length: ConstantOrContextLambda[int], signed: bool = ..., swapped: ConstantOrContextLambda[bool] = ..., ) -> None: ... -Bit: BitsInteger[int, int] -Nibble: BitsInteger[int, int] -Octet: BitsInteger[int, int] +Bit: BitsInteger +Nibble: BitsInteger +Octet: BitsInteger Int8ub: FormatField[int, int] Int16ub: FormatField[int, int] @@ -413,12 +378,12 @@ Half: FormatField[float, float] Single: FormatField[float, float] Double: FormatField[float, float] -Int24ub: BytesInteger[int, int] -Int24ul: BytesInteger[int, int] -Int24un: BytesInteger[int, int] -Int24sb: BytesInteger[int, int] -Int24sl: BytesInteger[int, int] -Int24sn: BytesInteger[int, int] +Int24ub: BytesInteger +Int24ul: BytesInteger +Int24un: BytesInteger +Int24sb: BytesInteger +Int24sl: BytesInteger +Int24sn: BytesInteger VarInt: Construct[int, int] ZigZag: Construct[int, int] @@ -426,7 +391,9 @@ ZigZag: Construct[int, int] # =============================================================================== # strings # =============================================================================== -class StringEncoded(Construct[ParsedType, BuildTypes]): +possiblestringencodings: t.Dict[str, int] + +class StringEncoded(Construct[str, str]): if sys.version_info >= (3, 8): ENCODING_1 = t.Literal["ascii", "utf8", "utf_8", "u8"] ENCODING_2 = t.Literal["utf16", "utf_16", "u16", "utf_16_be", "utf_16_le"] @@ -435,25 +402,20 @@ class StringEncoded(Construct[ParsedType, BuildTypes]): else: ENCODING = str encoding: ENCODING - def __new__( - cls, - subcon: Construct[ParsedType, BuildTypes], - encoding: ENCODING, - ) -> StringEncoded[str, str]: ... def __init__( - self: StringEncoded[str, str], - subcon: Construct[ParsedType, BuildTypes], + self, + subcon: Construct[bytes, bytes], encoding: ENCODING, ) -> None: ... def PaddedString( length: ConstantOrContextLambda[int], encoding: StringEncoded.ENCODING -) -> StringEncoded[str, str]: ... +) -> StringEncoded: ... def PascalString( lengthfield: Construct[int, int], encoding: StringEncoded.ENCODING -) -> StringEncoded[str, str]: ... -def CString(encoding: StringEncoded.ENCODING) -> StringEncoded[str, str]: ... -def GreedyString(encoding: StringEncoded.ENCODING) -> StringEncoded[str, str]: ... +) -> StringEncoded: ... +def CString(encoding: StringEncoded.ENCODING) -> StringEncoded: ... +def GreedyString(encoding: StringEncoded.ENCODING) -> StringEncoded: ... # =============================================================================== # mappings @@ -466,18 +428,14 @@ class EnumIntegerString(str): @staticmethod def new(intvalue: int, stringvalue: str) -> EnumIntegerString: ... -class Enum(Adapter[int, int, ParsedType, BuildTypes]): +class Enum( + Adapter[int, int, t.Union[EnumInteger, EnumIntegerString], t.Union[int, str]] +): encmapping: t.Dict[str, int] decmapping: t.Dict[int, EnumIntegerString] ksymapping: t.Dict[int, str] - def __new__( - cls, - subcon: Construct[int, int], - *merge: t.Union[t.Type[enum.IntEnum], t.Type[enum.IntFlag]], - **mapping: int, - ) -> Enum[t.Union[EnumInteger, EnumIntegerString], t.Union[int, str]]: ... def __init__( - self: Enum[t.Union[EnumInteger, EnumIntegerString], t.Union[int, str]], + self, subcon: Construct[int, int], *merge: t.Union[t.Type[enum.IntEnum], t.Type[enum.IntFlag]], **mapping: int, @@ -487,17 +445,13 @@ class Enum(Adapter[int, int, ParsedType, BuildTypes]): class BitwisableString(str): def __or__(self, other: BitwisableString) -> BitwisableString: ... -class FlagsEnum(Adapter[int, int, ParsedType, BuildTypes]): +class FlagsEnum( + Adapter[int, int, Container[bool], t.Union[int, str, t.Dict[str, bool]]] +): flags: t.Dict[str, int] reverseflags: t.Dict[int, str] - def __new__( - cls, - subcon: Construct[int, int], - *merge: t.Union[t.Type[enum.IntEnum], t.Type[enum.IntFlag]], - **flags: int, - ) -> FlagsEnum[Container[bool], t.Union[int, str, t.Dict[str, bool]]]: ... def __init__( - self: FlagsEnum[Container[bool], t.Union[int, str, t.Dict[str, bool]]], + self, subcon: Construct[int, int], *merge: t.Union[t.Type[enum.IntEnum], t.Type[enum.IntFlag]], **flags: int, @@ -507,13 +461,8 @@ class FlagsEnum(Adapter[int, int, ParsedType, BuildTypes]): class Mapping(Adapter[SubconParsedType, SubconBuildTypes, t.Any, t.Any]): decmapping: t.Dict[int, str] encmapping: t.Dict[str, int] - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - mapping: t.Dict[t.Any, t.Any], - ) -> Mapping[t.Any, t.Any]: ... def __init__( - self: Mapping[t.Any, t.Any], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], mapping: t.Dict[t.Any, t.Any], ) -> None: ... @@ -522,32 +471,22 @@ class Mapping(Adapter[SubconParsedType, SubconBuildTypes, t.Any, t.Any]): # structures and sequences # =============================================================================== # this can maybe made better when variadic generics are available -class Struct(Construct[ParsedType, BuildTypes]): +class Struct(Construct[Container[t.Any], t.Optional[t.Dict[str, t.Any]]]): subcons: t.List[Construct[t.Any, t.Any]] _subcons: t.Dict[str, Construct[t.Any, t.Any]] - def __new__( - cls, - *subcons: Construct[t.Any, t.Any], - **subconskw: Construct[t.Any, t.Any], - ) -> Struct[Container[t.Any], t.Optional[t.Dict[str, t.Any]]]: ... def __init__( - self: Struct[Container[t.Any], t.Optional[t.Dict[str, t.Any]]], + self, *subcons: Construct[t.Any, t.Any], **subconskw: Construct[t.Any, t.Any], ) -> None: ... def __getattr__(self, name: str) -> t.Any: ... # this can maybe made better when variadic generics are available -class Sequence(Construct[ParsedType, BuildTypes]): +class Sequence(Construct[ListContainer[t.Any], t.Optional[t.List[t.Any]]]): subcons: t.List[Construct[t.Any, t.Any]] _subcons: t.Dict[str, Construct[t.Any, t.Any]] - def __new__( - cls, - *subcons: Construct[t.Any, t.Any], - **subconskw: Construct[t.Any, t.Any], - ) -> Sequence[ListContainer[t.Any], t.Optional[t.List[t.Any]]]: ... def __init__( - self: Sequence[ListContainer[t.Any], t.Optional[t.List[t.Any]]], + self, *subcons: Construct[t.Any, t.Any], **subconskw: Construct[t.Any, t.Any], ) -> None: ... @@ -560,30 +499,14 @@ class Array( Subconstruct[ SubconParsedType, SubconBuildTypes, - ParsedType, - BuildTypes, + ListContainer[SubconParsedType], # type: ignore + t.List[SubconBuildTypes], # type: ignore ] ): count: ConstantOrContextLambda[int] discard: bool - def __new__( - cls, - count: ConstantOrContextLambda[int], - subcon: Construct[SubconParsedType, SubconBuildTypes], - discard: bool = ..., - ) -> Array[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ]: ... def __init__( - self: Array[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ], + self, count: ConstantOrContextLambda[int], subcon: Construct[SubconParsedType, SubconBuildTypes], discard: bool = ..., @@ -593,28 +516,13 @@ class GreedyRange( Subconstruct[ SubconParsedType, SubconBuildTypes, - ParsedType, - BuildTypes, + ListContainer[SubconParsedType], # type: ignore + t.List[SubconBuildTypes], # type: ignore ] ): discard: bool - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - discard: bool = ..., - ) -> GreedyRange[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ]: ... def __init__( - self: GreedyRange[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], discard: bool = ..., ) -> None: ... @@ -623,8 +531,8 @@ class RepeatUntil( Subconstruct[ SubconParsedType, SubconBuildTypes, - ParsedType, - BuildTypes, + ListContainer[SubconParsedType], # type: ignore + t.List[SubconBuildTypes], # type: ignore ] ): predicate: t.Union[ @@ -632,29 +540,8 @@ class RepeatUntil( t.Callable[[SubconParsedType, ListContainer[SubconParsedType], Context], bool], ] discard: bool - def __new__( - cls, - predicate: t.Union[ - bool, - t.Callable[ - [SubconParsedType, ListContainer[SubconParsedType], Context], bool - ], - ], - subcon: Construct[SubconParsedType, SubconBuildTypes], - discard: bool = ..., - ) -> RepeatUntil[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ]: ... def __init__( - self: RepeatUntil[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ], + self, predicate: t.Union[ bool, t.Callable[ @@ -682,93 +569,60 @@ class Renamed( # =============================================================================== # miscellaneous # =============================================================================== -class Const(Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): - value: SubconBuildTypes +class Const(Subconstruct[t.Any, t.Any, ParsedType, BuildTypes]): + value: BuildTypes @t.overload def __new__( - cls, + cls: "type[Const[bytes, t.Optional[bytes]]]", value: bytes, - ) -> Const[None, None, bytes, t.Optional[bytes]]: ... + ) -> Const[bytes, t.Optional[bytes]]: ... @t.overload def __new__( - cls, + cls: "type[Const[SubconParsedType, t.Optional[SubconBuildTypes]]]", value: SubconBuildTypes, subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> Const[None, None, SubconParsedType, t.Optional[SubconBuildTypes]]: ... + ) -> Const[SubconParsedType, t.Optional[SubconBuildTypes]]: ... -class Computed(Construct[ParsedType, BuildTypes]): +class Computed(Construct[ParsedType, None]): func: ConstantOrContextLambda2[ParsedType] - @t.overload - def __new__( - cls, - func: ConstantOrContextLambda2[ParsedType], - ) -> Computed[ParsedType, None]: ... - @t.overload - def __new__( - cls, - func: ConstantOrContextLambda2[t.Any], - ) -> Computed[t.Any, None]: ... - @t.overload def __init__( - self: Computed[ParsedType, None], + self, func: ConstantOrContextLambda2[ParsedType], ) -> None: ... - @t.overload - def __init__( - self: Computed[t.Any, None], - func: ConstantOrContextLambda2[t.Any], - ) -> None: ... Index: Construct[int, t.Any] -class Rebuild(Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): +class Rebuild(Subconstruct[SubconParsedType, SubconBuildTypes, SubconParsedType, None]): func: ConstantOrContextLambda[SubconBuildTypes] - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - func: ConstantOrContextLambda[SubconBuildTypes], - ) -> Rebuild[SubconParsedType, SubconBuildTypes, SubconParsedType, None]: ... def __init__( - self: Rebuild[SubconParsedType, SubconBuildTypes, SubconParsedType, None], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], func: ConstantOrContextLambda[SubconBuildTypes], ) -> None: ... -class Default(Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): - value: ConstantOrContextLambda[SubconBuildTypes] - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - value: ConstantOrContextLambda[SubconBuildTypes], - ) -> Default[ +class Default( + Subconstruct[ SubconParsedType, SubconBuildTypes, SubconParsedType, t.Optional[SubconBuildTypes], - ]: ... + ] +): + value: ConstantOrContextLambda[SubconBuildTypes] def __init__( - self: Default[ - SubconParsedType, - SubconBuildTypes, - SubconParsedType, - t.Optional[SubconBuildTypes], - ], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], value: ConstantOrContextLambda[SubconBuildTypes], ) -> None: ... -class Check(Construct[ParsedType, BuildTypes]): +class Check(Construct[None, None]): func: ConstantOrContextLambda[bool] - def __new__( - cls, - func: ConstantOrContextLambda[bool], - ) -> Check[None, None]: ... def __init__( - self: Check[None, None], + self, func: ConstantOrContextLambda[bool], ) -> None: ... -Error: Construct[None, None] +Error: Construct[t.NoReturn, t.NoReturn] class FocusedSeq(Construct[t.Any, t.Any]): subcons: t.List[Construct[t.Any, t.Any]] @@ -789,31 +643,15 @@ class NamedTuple( Adapter[ SubconParsedType, SubconBuildTypes, - ParsedType, - BuildTypes, + t.Tuple[t.Any, ...], + t.Union[t.Tuple[t.Any, ...], t.List[t.Any], t.Dict[str, t.Any]], ] ): tuplename: str tuplefields: str factory: Construct[SubconParsedType, SubconBuildTypes] - def __new__( - cls, - tuplename: str, - tuplefields: str, - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> NamedTuple[ - SubconParsedType, - SubconBuildTypes, - t.Tuple[t.Any, ...], - t.Union[t.Tuple[t.Any, ...], t.List[t.Any], t.Dict[str, t.Any]], - ]: ... def __init__( - self: NamedTuple[ - SubconParsedType, - SubconBuildTypes, - t.Tuple[t.Any, ...], - t.Union[t.Tuple[t.Any, ...], t.List[t.Any], t.Dict[str, t.Any]], - ], + self, tuplename: str, tuplefields: str, subcon: Construct[SubconParsedType, SubconBuildTypes], @@ -848,126 +686,60 @@ def Timestamp( K = t.TypeVar("K") V = t.TypeVar("V") -class Hex(Adapter[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): +class Hex(Adapter[t.Any, t.Any, ParsedType, BuildTypes]): @t.overload def __new__( - cls, subcon: Construct[int, BuildTypes] - ) -> Hex[int, BuildTypes, HexDisplayedInteger, BuildTypes]: ... + cls: "type[Hex[HexDisplayedInteger, BuildTypes]]", + subcon: Construct[int, BuildTypes], + ) -> Hex[HexDisplayedInteger, BuildTypes]: ... @t.overload def __new__( - cls, subcon: Construct[bytes, BuildTypes] - ) -> Hex[bytes, BuildTypes, HexDisplayedBytes, BuildTypes]: ... + cls: "type[Hex[HexDisplayedBytes, BuildTypes]]", + subcon: Construct[bytes, BuildTypes], + ) -> Hex[HexDisplayedBytes, BuildTypes]: ... @t.overload def __new__( - cls, subcon: Construct[RawCopyObj[SubconParsedType], BuildTypes] + cls: "type[Hex[HexDisplayedDict[str, t.Union[int, bytes, SubconParsedType]], BuildTypes,]]", + subcon: Construct[RawCopyObj[SubconParsedType], BuildTypes], ) -> Hex[ - RawCopyObj[SubconParsedType], - BuildTypes, HexDisplayedDict[str, t.Union[int, bytes, SubconParsedType]], BuildTypes, ]: ... @t.overload def __new__( - cls, subcon: Construct[Container[t.Any], BuildTypes] - ) -> Hex[ - Container[t.Any], BuildTypes, HexDisplayedDict[str, t.Any], BuildTypes - ]: ... - @t.overload - def __new__( - cls, subcon: Construct[SubconParsedType, SubconBuildTypes] - ) -> Hex[ - SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes - ]: ... - @t.overload - def __init__( - self: Hex[int, BuildTypes, HexDisplayedInteger, BuildTypes], - subcon: Construct[int, BuildTypes], - ) -> None: ... - @t.overload - def __init__( - self: Hex[bytes, BuildTypes, HexDisplayedBytes, BuildTypes], - subcon: Construct[bytes, BuildTypes], - ) -> None: ... - @t.overload - def __init__( - self: Hex[ - RawCopyObj[SubconParsedType], - BuildTypes, - HexDisplayedDict[str, t.Union[int, bytes, SubconParsedType]], - BuildTypes, - ], - subcon: Construct[RawCopyObj[SubconParsedType], BuildTypes], - ) -> None: ... - @t.overload - def __init__( - self: Hex[ - Container[t.Any], BuildTypes, HexDisplayedDict[str, t.Any], BuildTypes - ], + cls: "type[Hex[HexDisplayedDict[str, t.Any], BuildTypes]]", subcon: Construct[Container[t.Any], BuildTypes], - ) -> None: ... + ) -> Hex[HexDisplayedDict[str, t.Any], BuildTypes]: ... @t.overload - def __init__( - self: Hex[ - SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes - ], + def __new__( + cls: "type[Hex[SubconParsedType, SubconBuildTypes]]", subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> None: ... + ) -> Hex[SubconParsedType, SubconBuildTypes]: ... -class HexDump(Adapter[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): +class HexDump(Adapter[t.Any, t.Any, ParsedType, BuildTypes]): @t.overload def __new__( - cls, subcon: Construct[bytes, BuildTypes] - ) -> HexDump[bytes, BuildTypes, HexDumpDisplayedBytes, BuildTypes]: ... + cls: "type[HexDump[HexDumpDisplayedBytes, BuildTypes]]", + subcon: Construct[bytes, BuildTypes], + ) -> HexDump[HexDumpDisplayedBytes, BuildTypes]: ... @t.overload def __new__( - cls, subcon: Construct[RawCopyObj[SubconParsedType], BuildTypes] + cls: "type[HexDump[HexDumpDisplayedDict[str, t.Union[int, bytes, SubconParsedType]],BuildTypes,]]", + subcon: Construct[RawCopyObj[SubconParsedType], BuildTypes], ) -> HexDump[ - RawCopyObj[SubconParsedType], - BuildTypes, HexDumpDisplayedDict[str, t.Union[int, bytes, SubconParsedType]], BuildTypes, ]: ... @t.overload def __new__( - cls, subcon: Construct[Container[t.Any], BuildTypes] - ) -> HexDump[ - Container[t.Any], BuildTypes, HexDumpDisplayedDict[str, t.Any], BuildTypes - ]: ... + cls: "type[HexDump[HexDumpDisplayedDict[str, t.Any], BuildTypes]]", + subcon: Construct[Container[t.Any], BuildTypes], + ) -> HexDump[HexDumpDisplayedDict[str, t.Any], BuildTypes]: ... @t.overload def __new__( - cls, subcon: Construct[SubconParsedType, SubconBuildTypes] - ) -> HexDump[ - SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes - ]: ... - @t.overload - def __init__( - self: HexDump[bytes, BuildTypes, HexDumpDisplayedBytes, BuildTypes], - subcon: Construct[bytes, BuildTypes], - ) -> None: ... - @t.overload - def __init__( - self: HexDump[ - RawCopyObj[SubconParsedType], - BuildTypes, - HexDumpDisplayedDict[str, t.Union[int, bytes, SubconParsedType]], - BuildTypes, - ], - subcon: Construct[RawCopyObj[SubconParsedType], BuildTypes], - ) -> None: ... - @t.overload - def __init__( - self: HexDump[ - Container[t.Any], BuildTypes, HexDumpDisplayedDict[str, t.Any], BuildTypes - ], - subcon: Construct[Container[t.Any], BuildTypes], - ) -> None: ... - @t.overload - def __init__( - self: HexDump[ - SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes - ], + cls: "type[HexDump[SubconParsedType, SubconBuildTypes]]", subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> None: ... + ) -> HexDump[SubconParsedType, SubconBuildTypes]: ... # =============================================================================== # conditional @@ -986,22 +758,17 @@ class Union(Construct[Container[t.Any], t.Dict[str, t.Any]]): def __getattr__(self, name: str) -> t.Any: ... # this can maybe made better when variadic generics are available -class Select(Construct[ParsedType, BuildTypes]): +class Select(Construct[t.Any, t.Any]): subcons: t.List[Construct[t.Any, t.Any]] - def __new__( - cls, - *subcons: Construct[t.Any, t.Any], - **subconskw: Construct[t.Any, t.Any], - ) -> Select[t.Any, t.Any]: ... def __init__( - self: Select[t.Any, t.Any], + self, *subcons: Construct[t.Any, t.Any], **subconskw: Construct[t.Any, t.Any], ) -> None: ... def Optional( subcon: Construct[SubconParsedType, SubconBuildTypes] -) -> Select[t.Union[SubconParsedType, None], t.Union[SubconBuildTypes, None]]: ... +) -> Construct[t.Union[SubconParsedType, None], t.Union[SubconBuildTypes, None]]: ... ThenParsedType = t.TypeVar("ThenParsedType") ThenBuildTypes = t.TypeVar("ThenBuildTypes") @@ -1010,23 +777,33 @@ ElseBuildTypes = t.TypeVar("ElseBuildTypes") class IfThenElse(Construct[ParsedType, BuildTypes]): condfunc: ConstantOrContextLambda[bool] - thensubcon: Construct[ParsedType, BuildTypes] - elsesubcon: Construct[ParsedType, BuildTypes] + thensubcon: Construct[t.Any, t.Any] + elsesubcon: Construct[t.Any, t.Any] + @t.overload def __new__( - cls, + cls: "type[IfThenElse[t.Union[ThenParsedType, ElseParsedType], t.Union[ThenBuildTypes, ElseBuildTypes]]]", condfunc: ConstantOrContextLambda[bool], thensubcon: Construct[ThenParsedType, ThenBuildTypes], elsesubcon: Construct[ElseParsedType, ElseBuildTypes], - ) -> IfThenElse[ - t.Union[ThenParsedType, ElseParsedType], t.Union[ThenBuildTypes, ElseBuildTypes] - ]: ... + ) -> "IfThenElse[t.Union[ThenParsedType, ElseParsedType], t.Union[ThenBuildTypes, ElseBuildTypes]]": ... + @t.overload + def __new__( + cls: "type[IfThenElse[t.Any, t.Any]]", + condfunc: ConstantOrContextLambda[bool], + thensubcon: Construct[t.Any, t.Any], + elsesubcon: Construct[t.Any, t.Any], + ) -> "IfThenElse[t.Any, t.Any]": ... def If( condfunc: ConstantOrContextLambda[bool], subcon: Construct[ThenParsedType, ThenBuildTypes], -) -> IfThenElse[t.Union[ThenParsedType, None], t.Union[ThenBuildTypes, None]]: ... +) -> IfThenElse[t.Optional[ThenParsedType], t.Optional[ThenBuildTypes]]: ... SwitchType = t.TypeVar("SwitchType") +SwitchParsedType = t.TypeVar("SwitchParsedType") +SwitchBuildTypes = t.TypeVar("SwitchBuildTypes") +SwitchDefaultParsedType = t.TypeVar("SwitchDefaultParsedType") +SwitchDefaultBuildTypes = t.TypeVar("SwitchDefaultBuildTypes") class Switch(Construct[ParsedType, BuildTypes]): keyfunc: ConstantOrContextLambda[t.Any] @@ -1034,41 +811,37 @@ class Switch(Construct[ParsedType, BuildTypes]): default: Construct[t.Any, t.Any] @t.overload def __new__( - cls, + cls: "type[Switch[SwitchParsedType | None, SwitchBuildTypes | None]]", keyfunc: ConstantOrContextLambda[SwitchType], - cases: t.Dict[SwitchType, Construct[int, int]], - default: t.Optional[Construct[int, int]] = ..., - ) -> Switch[int, t.Optional[int]]: ... + cases: dict[t.Any, Construct[SwitchParsedType, SwitchBuildTypes]], + default: None = ..., + ) -> Switch[SwitchParsedType | None, SwitchBuildTypes | None]: ... @t.overload def __new__( - cls, - keyfunc: ConstantOrContextLambda[t.Any], - cases: t.Dict[t.Any, Construct[t.Any, t.Any]], - default: t.Optional[Construct[t.Any, t.Any]] = ..., + cls: "type[Switch[SwitchParsedType, SwitchBuildTypes]]", + keyfunc: ConstantOrContextLambda[SwitchType], + cases: dict[t.Any, Construct[SwitchParsedType, SwitchBuildTypes]], + default: Construct[t.NoReturn, t.NoReturn], + ) -> Switch[SwitchParsedType, SwitchBuildTypes]: ... + @t.overload + def __new__( + cls: "type[Switch[SwitchParsedType | SwitchDefaultParsedType, SwitchBuildTypes | SwitchDefaultBuildTypes]]", + keyfunc: ConstantOrContextLambda[SwitchType], + cases: dict[t.Any, Construct[SwitchParsedType, SwitchBuildTypes]], + default: Construct[SwitchDefaultParsedType, SwitchDefaultBuildTypes], + ) -> Switch[SwitchParsedType | SwitchDefaultParsedType, SwitchBuildTypes | SwitchDefaultBuildTypes]: ... + @t.overload + def __new__( + cls: "type[Switch[t.Any, t.Any]]", + keyfunc: ConstantOrContextLambda[SwitchType], + cases: dict[t.Any, Construct[t.Any, t.Any]], + default: Construct[t.Any, t.Any] | None = ..., ) -> Switch[t.Any, t.Any]: ... - @t.overload - def __init__( - self: Switch[int, t.Optional[int]], - keyfunc: ConstantOrContextLambda[SwitchType], - cases: t.Dict[SwitchType, Construct[int, int]], - default: t.Optional[Construct[int, int]] = ..., - ) -> None: ... - @t.overload - def __init__( - self: Switch[t.Any, t.Any], - keyfunc: ConstantOrContextLambda[t.Any], - cases: t.Dict[t.Any, Construct[t.Any, t.Any]], - default: t.Optional[Construct[t.Any, t.Any]] = ..., - ) -> None: ... -class StopIf(Construct[ParsedType, BuildTypes]): +class StopIf(Construct[None, None]): condfunc: ConstantOrContextLambda[bool] - def __new__( - cls, - condfunc: ConstantOrContextLambda[bool], - ) -> StopIf[None, None]: ... def __init__( - self: StopIf[None, None], + self, condfunc: ConstantOrContextLambda[bool], ) -> None: ... @@ -1107,7 +880,7 @@ def AlignedStruct( modulus: ConstantOrContextLambda[int], *subcons: Construct[t.Any, t.Any], **subconskw: Construct[t.Any, t.Any], -) -> Struct[Container[t.Any], t.Optional[t.Dict[str, t.Any]]]: ... +) -> Struct: ... def BitStruct( *subcons: Construct[t.Any, t.Any], **subconskw: Construct[t.Any, t.Any] ) -> t.Union[ @@ -1130,23 +903,26 @@ class Pointer( stream: t.Optional[t.Callable[[Context], StreamType]] = ..., ) -> None: ... -class Peek(Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> Peek[ +class Peek( + Subconstruct[ SubconParsedType, SubconBuildTypes, SubconParsedType, t.Union[SubconBuildTypes, None], - ]: ... + ] +): def __init__( - self: Peek[ - SubconParsedType, - SubconBuildTypes, - SubconParsedType, - t.Union[SubconBuildTypes, None], - ], + self, + subcon: Construct[SubconParsedType, SubconBuildTypes], + ) -> None: ... + +class OffsettedEnd( + Subconstruct[SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes] +): + endoffset: ConstantOrContextLambda[int] + def __init__( + self, + endoffset: ConstantOrContextLambda[int], subcon: Construct[SubconParsedType, SubconBuildTypes], ) -> None: ... @@ -1178,32 +954,23 @@ class RawCopyObj(t.Generic[ParsedType], Container[t.Any]): offset2: int length: int -class RawCopy(Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> RawCopy[ +class RawCopy( + Subconstruct[ SubconParsedType, SubconBuildTypes, RawCopyObj[SubconParsedType], t.Optional[t.Dict[str, t.Union[SubconBuildTypes, bytes]]], - ]: ... + ] +): def __init__( - self: RawCopy[ - SubconParsedType, - SubconBuildTypes, - RawCopyObj[SubconParsedType], - t.Optional[t.Dict[str, t.Union[SubconBuildTypes, bytes]]], - ], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], ) -> None: ... def ByteSwapped( subcon: Construct[SubconParsedType, SubconBuildTypes] ) -> Transformed[SubconParsedType, SubconBuildTypes]: ... -def BitsSwapped( - subcon: Construct[SubconParsedType, SubconBuildTypes] -) -> t.Union[ +def BitsSwapped(subcon: Construct[SubconParsedType, SubconBuildTypes]) -> t.Union[ Transformed[SubconParsedType, SubconBuildTypes], Restreamed[SubconParsedType, SubconBuildTypes], ]: ... @@ -1226,8 +993,6 @@ def PrefixedArray( ) -> Array[ SubconParsedType, SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], ]: ... class FixedSized( @@ -1261,7 +1026,9 @@ class NullStripped( ): pad: bytes def __init__( - self, subcon: Construct[SubconParsedType, SubconBuildTypes], pad: bytes = ... + self, + subcon: Construct[SubconParsedType, SubconBuildTypes], + pad: bytes = ..., ) -> None: ... class RestreamData( @@ -1316,13 +1083,8 @@ class ProcessXor( Subconstruct[SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes] ): padfunc: ConstantOrContextLambda2[t.Union[int, bytes]] - def __new__( - cls, - padfunc: ConstantOrContextLambda2[t.Union[int, bytes]], - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> ProcessXor[SubconParsedType, SubconBuildTypes]: ... def __init__( - self: ProcessXor[SubconParsedType, SubconBuildTypes], + self, padfunc: ConstantOrContextLambda2[t.Union[int, bytes]], subcon: Construct[SubconParsedType, SubconBuildTypes], ) -> None: ... @@ -1332,14 +1094,8 @@ class ProcessRotateLeft( ): amount: ConstantOrContextLambda2[int] group: ConstantOrContextLambda2[int] - def __new__( - cls, - amount: ConstantOrContextLambda2[int], - group: ConstantOrContextLambda2[int], - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> ProcessRotateLeft[SubconParsedType, SubconBuildTypes]: ... def __init__( - self: ProcessRotateLeft[SubconParsedType, SubconBuildTypes], + self, amount: ConstantOrContextLambda2[int], group: ConstantOrContextLambda2[int], subcon: Construct[SubconParsedType, SubconBuildTypes], @@ -1386,47 +1142,55 @@ class Rebuffered( tailcutoff: t.Optional[int] = ..., ) -> None: ... +class EncryptedSym(Tunnel[SubconParsedType, SubconBuildTypes]): + cipher: ConstantOrContextLambda2[Cipher[Mode]] + def __init__( + self, + subcon: Construct[SubconParsedType, SubconBuildTypes], + cipher: ConstantOrContextLambda2[Cipher[Mode]], + ) -> None: ... + +class EncryptedSymAead(Tunnel[SubconParsedType, SubconBuildTypes]): + cipher: ConstantOrContextLambda2[t.Union[AESGCM, AESCCM, ChaCha20Poly1305]] + nonce: ConstantOrContextLambda2[bytes] + associated_data: ConstantOrContextLambda2[bytes] + def __init__( + self, + subcon: Construct[SubconParsedType, SubconBuildTypes], + cipher: ConstantOrContextLambda2[t.Union[AESGCM, AESCCM, ChaCha20Poly1305]], + nonce: ConstantOrContextLambda2[bytes], + associated_data: ConstantOrContextLambda2[bytes] = ..., + ) -> None: ... + # =============================================================================== # lazy equivalents # =============================================================================== -class Lazy(Subconstruct[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes]): - def __new__( - cls, - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> Lazy[ +class Lazy( + Subconstruct[ SubconParsedType, SubconBuildTypes, t.Callable[[], SubconParsedType], t.Union[t.Callable[[], SubconParsedType], SubconParsedType], - ]: ... + ] +): def __init__( - self: Lazy[ - SubconParsedType, - SubconBuildTypes, - t.Callable[[], SubconParsedType], - t.Union[t.Callable[[], SubconParsedType], SubconParsedType], - ], + self, subcon: Construct[SubconParsedType, SubconBuildTypes], ) -> None: ... class LazyContainer(t.Generic[ContainerType], t.Dict[str, ContainerType]): def __getattr__(self, name: str) -> ContainerType: ... def __getitem__(self, index: t.Union[str, int]) -> ContainerType: ... - def keys(self) -> t.Iterator[str]: ... - def values(self) -> t.List[ContainerType]: ... - def items(self) -> t.List[t.Tuple[str, ContainerType]]: ... + def keys(self) -> t.Iterator[str]: ... # type: ignore + def values(self) -> t.List[ContainerType]: ... # type: ignore + def items(self) -> t.List[t.Tuple[str, ContainerType]]: ... # type: ignore -class LazyStruct(Construct[ParsedType, BuildTypes]): +class LazyStruct(Construct[LazyContainer[t.Any], t.Optional[t.Dict[str, t.Any]]]): subcons: t.List[Construct[t.Any, t.Any]] _subcons: t.Dict[str, Construct[t.Any, t.Any]] _subconsindexes: t.Dict[str, int] - def __new__( - cls, - *subcons: Construct[t.Any, t.Any], - **subconskw: Construct[t.Any, t.Any], - ) -> LazyStruct[LazyContainer[t.Any], t.Optional[t.Dict[str, t.Any]]]: ... def __init__( - self: LazyStruct[LazyContainer[t.Any], t.Optional[t.Dict[str, t.Any]]], + self, *subcons: Construct[t.Any, t.Any], **subconskw: Construct[t.Any, t.Any], ) -> None: ... @@ -1438,40 +1202,21 @@ class LazyArray( Subconstruct[ SubconParsedType, SubconBuildTypes, - ParsedType, - BuildTypes, + ListContainer[SubconParsedType], # type: ignore + t.List[SubconBuildTypes], # type: ignore ] ): count: ConstantOrContextLambda[int] - def __new__( - cls, - count: ConstantOrContextLambda[int], - subcon: Construct[SubconParsedType, SubconBuildTypes], - ) -> LazyArray[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ]: ... def __init__( - self: LazyArray[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ], + self, count: ConstantOrContextLambda[int], subcon: Construct[SubconParsedType, SubconBuildTypes], ) -> None: ... class LazyBound(Construct[ParsedType, BuildTypes]): subconfunc: t.Callable[[], Construct[ParsedType, BuildTypes]] - def __new__( - cls, - subconfunc: t.Callable[[], Construct[ParsedType, BuildTypes]], - ) -> LazyBound[ParsedType, BuildTypes]: ... def __init__( - self: LazyBound[ParsedType, BuildTypes], + self, subconfunc: t.Callable[[], Construct[ParsedType, BuildTypes]], ) -> None: ... @@ -1518,44 +1263,23 @@ def Filter( ]: ... class Slicing( - Adapter[SubconParsedType, SubconBuildTypes, SubconParsedType, SubconBuildTypes] + Adapter[ + SubconParsedType, + SubconBuildTypes, + ListContainer[SubconParsedType], # type: ignore + t.List[SubconBuildTypes], # type: ignore + ] ): - def __new__( - cls, - subcon: t.Union[ - Array[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ], - GreedyRange[ - SubconParsedType, - SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], - ], - ], - count: int, - start: t.Optional[int], - stop: t.Optional[int], - step: int = ..., - empty: t.Optional[SubconParsedType] = ..., - ) -> Slicing[ListContainer[SubconParsedType], t.List[SubconBuildTypes]]: ... def __init__( - self: Slicing[ListContainer[SubconParsedType], t.List[SubconBuildTypes]], + self, subcon: t.Union[ Array[ SubconParsedType, SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], ], GreedyRange[ SubconParsedType, SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], ], ], count: int, @@ -1574,14 +1298,10 @@ class Indexing( Array[ SubconParsedType, SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], ], GreedyRange[ SubconParsedType, SubconBuildTypes, - ListContainer[SubconParsedType], - t.List[SubconBuildTypes], ], ], count: int, diff --git a/construct-stubs/expr.pyi b/construct-stubs/expr.pyi index 56fae52..a7c1a1a 100644 --- a/construct-stubs/expr.pyi +++ b/construct-stubs/expr.pyi @@ -1,4 +1,3 @@ -import operator import typing as t from construct.core import * @@ -470,7 +469,7 @@ class ExprMixin(t.Generic[ReturnType], object): @t.overload def __eq__(self: ExprMixin[float], other: ConstOrCallable[float]) -> BinExpr[bool]: ... @t.overload - def __eq__(self, other: t.Any) -> BinExpr[t.Any]: ... + def __eq__(self, other: ConstOrCallable[t.Any]) -> BinExpr[t.Any]: ... # type: ignore # __ne__ ########################################################################################################### @t.overload @@ -488,7 +487,7 @@ class ExprMixin(t.Generic[ReturnType], object): @t.overload def __ne__(self: ExprMixin[float], other: ConstOrCallable[float]) -> BinExpr[bool]: ... @t.overload - def __ne__(self, other: t.Any) -> BinExpr[t.Any]: ... + def __ne__(self, other: t.Any) -> BinExpr[t.Any]: ... # type: ignore # __neg__ ########################################################################################################## @t.overload @@ -498,7 +497,7 @@ class ExprMixin(t.Generic[ReturnType], object): @t.overload def __neg__(self: ExprMixin[float]) -> BinExpr[float]: ... @t.overload - def __neg__(self) -> UniExpr[t.Any]: ... + def __neg__(self) -> BinExpr[t.Any]: ... # __pos__ ########################################################################################################## @t.overload @@ -508,7 +507,7 @@ class ExprMixin(t.Generic[ReturnType], object): @t.overload def __pos__(self: ExprMixin[float]) -> BinExpr[float]: ... @t.overload - def __pos__(self) -> UniExpr[t.Any]: ... + def __pos__(self) -> BinExpr[t.Any]: ... # __invert__ ####################################################################################################### @t.overload @@ -516,7 +515,7 @@ class ExprMixin(t.Generic[ReturnType], object): @t.overload def __invert__(self: ExprMixin[bool]) -> BinExpr[int]: ... @t.overload - def __invert__(self) -> UniExpr[t.Any]: ... + def __invert__(self) -> BinExpr[t.Any]: ... # __inv__ ########################################################################################################## def __inv__(self) -> UniExpr[t.Any]: ... diff --git a/construct-stubs/lib/containers.pyi b/construct-stubs/lib/containers.pyi index a50033a..37efc75 100644 --- a/construct-stubs/lib/containers.pyi +++ b/construct-stubs/lib/containers.pyi @@ -19,7 +19,7 @@ def recursion_lock( class Container(t.Generic[ContainerType], t.Dict[str, ContainerType]): def __getattr__(self, name: str) -> ContainerType: ... - def update( + def update( # type: ignore self, seqordict: t.Union[t.Dict[str, ContainerType], t.Tuple[str, ContainerType]], ) -> None: ... diff --git a/construct-stubs/lib/hex.pyi b/construct-stubs/lib/hex.pyi index afa985f..a39d918 100644 --- a/construct-stubs/lib/hex.pyi +++ b/construct-stubs/lib/hex.pyi @@ -1,6 +1,5 @@ import typing as t - class HexDisplayedInteger(int): ... class HexDisplayedBytes(bytes): ... @@ -10,3 +9,6 @@ V = t.TypeVar("V") class HexDisplayedDict(t.Dict[K, V]): ... class HexDumpDisplayedBytes(bytes): ... class HexDumpDisplayedDict(t.Dict[K, V]): ... + +def hexdump(data: bytes, linesize: int) -> str: ... +def hexundump(data: str, linesize: int) -> bytes: ... diff --git a/construct-stubs/lib/py3compat.pyi b/construct-stubs/lib/py3compat.pyi index f105096..c86f2f5 100644 --- a/construct-stubs/lib/py3compat.pyi +++ b/construct-stubs/lib/py3compat.pyi @@ -1,5 +1,6 @@ import typing as t +PY: t.Tuple[int, int] PY2: bool PY3: bool PYPY: bool diff --git a/construct_typed/__init__.py b/construct_typed/__init__.py index 9ea0ccf..f594f2b 100644 --- a/construct_typed/__init__.py +++ b/construct_typed/__init__.py @@ -9,15 +9,19 @@ from .dataclass_struct import ( TStructField, csfield, sfield, + EnhancedDataclassMixin ) from .generic_wrapper import ( Adapter, ConstantOrContextLambda, + ConstantOrContextLambda2, Construct, Context, ListContainer, PathType, - Array + Array, + Subconstruct, + Computed, ) from .tenum import EnumBase, EnumValue, FlagsEnumBase, TEnum, TFlagsEnum @@ -32,6 +36,7 @@ __all__ = [ "TStructField", "csfield", "sfield", + "EnhancedDataclassMixin", "EnumBase", "EnumValue", "FlagsEnumBase", @@ -39,9 +44,12 @@ __all__ = [ "TFlagsEnum", "Adapter", "ConstantOrContextLambda", + "ConstantOrContextLambda2", "Construct", "Context", "ListContainer", "PathType", - "Array" + "Array", + "Subconstruct", + "Computed" ] diff --git a/construct_typed/dataclass_struct.py b/construct_typed/dataclass_struct.py index 6ecae05..e626085 100644 --- a/construct_typed/dataclass_struct.py +++ b/construct_typed/dataclass_struct.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # pyright: strict +# pyright: reportIncompatibleVariableOverride=false, reportAny=false import dataclasses import textwrap import typing as t @@ -11,6 +12,7 @@ from construct.lib.containers import ( recursion_lock, ) from construct.lib.py3compat import bytestringtype, reprstring, unicodestringtype +from typing_extensions import override from .generic_wrapper import Adapter, Construct, Context, ParsedType, PathType @@ -27,7 +29,7 @@ class DataclassMixin: methods exists and every name can be used. """ - __dataclass_fields__: "t.ClassVar[t.Dict[str, dataclasses.Field[t.Any]]]" + __dataclass_fields__: "t.ClassVar[dict[str, dataclasses.Field[t.Any]]]" def __getitem__(self, key: str) -> t.Any: return getattr(self, key) @@ -77,8 +79,8 @@ class DataclassMixin: def csfield( subcon: Construct[ParsedType, t.Any], - doc: t.Optional[str] = None, - parsed: t.Optional[t.Callable[[t.Any, Context], None]] = None, + doc: str | None = None, + parsed: t.Callable[[t.Any, Context], None] | None = None, ) -> ParsedType: """ Helper method for "DataclassStruct" and "DataclassBitStruct" to create the dataclass fields. @@ -102,10 +104,10 @@ def csfield( # Set default values in case of special sucons if isinstance(orig_subcon, cs.Const): - const_subcon: "cs.Const[t.Any, t.Any, t.Any, t.Any]" = orig_subcon + const_subcon: "cs.Const[t.Any, t.Any]" = orig_subcon default = const_subcon.value elif isinstance(orig_subcon, cs.Default): - default_subcon: "cs.Default[t.Any, t.Any, t.Any, t.Any]" = orig_subcon + default_subcon: "cs.Default[t.Any, t.Any]" = orig_subcon if callable(default_subcon.value): default = None # context lambda is only defined at parsing/building else: @@ -152,27 +154,14 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]): Image(width=1, height=2, pixels=b'12') """ - subcon: "cs.Struct[t.Any, t.Any]" - if t.TYPE_CHECKING: - - def __new__( - cls, - dc_type: t.Type[DataclassType], - reverse: bool = False, - ) -> "DataclassStruct[DataclassType]": - ... - + subcon: "cs.Struct" # type: ignore def __init__( self, - dc_type: t.Type[DataclassType], + dc_type: type[DataclassType], reverse: bool = False, ) -> None: - if not issubclass(dc_type, DataclassMixin): - raise TypeError(f"'{repr(dc_type)}' has to be a '{repr(DataclassMixin)}'") - if not dataclasses.is_dataclass(dc_type): - raise TypeError(f"'{repr(dc_type)}' has to be a 'dataclasses.dataclass'") - self.dc_type = dc_type - self.reverse = reverse + self.dc_type: type[DataclassType] = dc_type + self.reverse: bool = reverse # get all fields from the dataclass fields = dataclasses.fields(self.dc_type) @@ -180,7 +169,7 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]): fields = tuple(reversed(fields)) # extract the construct formats from the struct_type - subcon_fields = {} + subcon_fields: dict[str, t.Any] = {} for field in fields: subcon_fields[field.name] = field.metadata["subcon"] @@ -190,6 +179,7 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]): def __getattr__(self, name: str) -> t.Any: return getattr(self.subcon, name) + @override def _decode( self, obj: "cs.Container[t.Any]", context: Context, path: PathType ) -> DataclassType: @@ -214,9 +204,10 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]): return dc # type: ignore + @override def _encode( self, obj: DataclassType, context: Context, path: PathType - ) -> t.Dict[str, t.Any]: + ) -> dict[str, t.Any]: if not isinstance(obj, self.dc_type): raise TypeError(f"'{repr(obj)}' has to be of type {repr(self.dc_type)}") @@ -224,20 +215,16 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]): fields = dataclasses.fields(self.dc_type) # extract all fields from the container, that are used for create the dataclass object - ret_dict: t.Dict[str, t.Any] = {} + ret_dict: dict[str, t.Any] = {} for field in fields: value = getattr(obj, field.name) ret_dict[field.name] = value return ret_dict - def DataclassBitStruct( - dc_type: t.Type[DataclassType], reverse: bool = False -) -> t.Union[ - "cs.Transformed[DataclassType, DataclassType]", - "cs.Restreamed[DataclassType, DataclassType]", -]: + dc_type: type[DataclassType], reverse: bool = False +) -> "cs.Transformed[DataclassType, DataclassType] | cs.Restreamed[DataclassType, DataclassType]": r""" Makes a DataclassStruct inside a Bitwise. @@ -264,6 +251,29 @@ def DataclassBitStruct( """ return cs.Bitwise(DataclassStruct(dc_type, reverse)) +class EnhancedDataclassMixin(DataclassMixin): + @classmethod + def format(cls): + return DataclassStruct(cls) + + @classmethod + def build(cls, obj: t.Self, **kw: dict[str, t.Any]): + return cls.format().build(obj, **kw) + + @classmethod + def parse(cls, data: bytes | bytearray, **kw: dict[str, t.Any]): + return cls.format().parse(data, **kw) + + @classmethod + def parse_file(cls, file: str, **kw: dict[str, t.Any]): + return cls.format().parse_file(file, **kw) + + @classmethod + def parse_stream(cls, stream: t.IO[bytes], **kw: dict[str, t.Any]): + return cls.format().parse_stream(stream, **kw) + + def build_self(self) -> bytes: + return self.build(self) # support legacy names TStruct = DataclassStruct diff --git a/construct_typed/generic_wrapper.py b/construct_typed/generic_wrapper.py index f570f70..cd4788b 100644 --- a/construct_typed/generic_wrapper.py +++ b/construct_typed/generic_wrapper.py @@ -12,12 +12,14 @@ if t.TYPE_CHECKING: # while type checking, the original classes are already generics, because they are defined like this in the stubs. from construct import Adapter as Adapter from construct import ConstantOrContextLambda as ConstantOrContextLambda + from construct import ConstantOrContextLambda2 as ConstantOrContextLambda2 from construct import Construct as Construct from construct import Context as Context from construct import ListContainer as ListContainer from construct import PathType as PathType from construct import Array as Array - + from construct import Subconstruct as Subconstruct + from construct import Computed as Computed else: import construct as cs @@ -39,10 +41,17 @@ else: pass class Array( - t.Generic[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes], + t.Generic[SubconParsedType, SubconBuildTypes], cs.Array, ): pass + class Subconstruct(t.Generic[SubconParsedType, SubconBuildTypes, ParsedType, BuildTypes], cs.Subconstruct): + pass + + class Computed(t.Generic[ParsedType], cs.Computed): + pass + ConstantOrContextLambda = t.Union[ValueType, t.Callable[[Context], t.Any]] + ConstantOrContextLambda2 = t.Union[ValueType, t.Callable[[Context], ValueType]] PathType = str diff --git a/construct_typed/tenum.py b/construct_typed/tenum.py index a71fb7f..4417c6b 100644 --- a/construct_typed/tenum.py +++ b/construct_typed/tenum.py @@ -1,7 +1,10 @@ +# pyright: reportAny=false import enum import typing as t -from .generic_wrapper import * +from typing_extensions import Self, override + +from .generic_wrapper import Construct, Adapter, Context, PathType # ## TEnum ############################################################################################################ @@ -10,8 +13,8 @@ class EnumValue: This is a helper class for adding documentation to an enum value. """ - def __init__(self, value: int, doc: t.Optional[str] = None) -> None: - self.value = value + def __init__(self, value: int, doc: str | None = None) -> None: + self.value: int = value self.__doc__ = doc if doc else "" @@ -45,7 +48,7 @@ class EnumBase(enum.IntEnum): 'This is the running state.' """ - def __new__(cls, val: t.Union[EnumValue, int]) -> "EnumBase": + def __new__(cls, val: EnumValue | int) -> "Self": if isinstance(val, EnumValue): obj = int.__new__(cls, val.value) obj._value_ = val.value @@ -60,7 +63,8 @@ class EnumBase(enum.IntEnum): # not found in the enum, a new pseudo member is created. # The idea is taken from: https://stackoverflow.com/a/57179436 @classmethod - def _missing_(cls, value: t.Any) -> t.Optional[enum.Enum]: + @override + def _missing_(cls, value: t.Any) -> enum.Enum | None: if isinstance(value, int): pseudo_member = cls._value2member_map_.get(value, None) if pseudo_member is None: @@ -74,7 +78,8 @@ class EnumBase(enum.IntEnum): return pseudo_member return None # will raise the ValueError in Enum.__new__ - def __reduce_ex__(self, proto: t.Any) -> t.Tuple[t.Any, ...]: + @override + def __reduce_ex__(self, proto: t.Any) -> tuple[t.Any, ...]: """ Pickle enums by value instead of name (restores pre-3.11 behavior). See https://github.com/python/cpython/pull/26658 for why this exists. @@ -89,29 +94,18 @@ class TEnum(Adapter[int, int, EnumType, EnumType]): """ Typed enum. """ - - if t.TYPE_CHECKING: - - def __new__( - cls, subcon: Construct[int, int], enum_type: t.Type[EnumType] - ) -> "TEnum[EnumType]": - ... - - def __init__(self, subcon: Construct[int, int], enum_type: t.Type[EnumType]): - if not issubclass(enum_type, EnumBase): - raise TypeError( - "'{}' has to be a '{}'".format(repr(enum_type), repr(EnumBase)) - ) - + def __init__(self, subcon: Construct[int, int], enum_type: type[EnumType]): # save enum type - self.enum_type = t.cast(t.Type[EnumType], enum_type) # type: ignore + self.enum_type: type[EnumType] = enum_type # init adatper super(TEnum, self).__init__(subcon) # type: ignore + @override def _decode(self, obj: int, context: Context, path: PathType) -> EnumType: return self.enum_type(obj) + @override def _encode( self, obj: EnumType, @@ -158,7 +152,7 @@ class FlagsEnumBase(enum.IntFlag): 'This is option two.' """ - def __new__(cls, val: t.Union[EnumValue, int]) -> "FlagsEnumBase": + def __new__(cls, val: EnumValue | int) -> "Self": if isinstance(val, EnumValue): obj = int.__new__(cls, val.value) obj._value_ = val.value @@ -170,6 +164,7 @@ class FlagsEnumBase(enum.IntFlag): return obj @classmethod + @override def _missing_(cls, value: t.Any) -> t.Any: """ Returns member (possibly creating it) if one can be found for value. @@ -178,7 +173,8 @@ class FlagsEnumBase(enum.IntFlag): new_member.__doc__ = "missing value" return new_member - def __reduce_ex__(self, proto: t.Any) -> t.Tuple[t.Any, ...]: + @override + def __reduce_ex__(self, proto: t.Any) -> tuple[t.Any, ...]: """ Pickle enums by value instead of name (restores pre-3.11 behavior). See https://github.com/python/cpython/pull/26658 for why this exists. @@ -193,29 +189,18 @@ class TFlagsEnum(Adapter[int, int, FlagsEnumType, FlagsEnumType]): """ Typed enum. """ - - if t.TYPE_CHECKING: - - def __new__( - cls, subcon: Construct[int, int], enum_type: t.Type[FlagsEnumType] - ) -> "TFlagsEnum[FlagsEnumType]": - ... - - def __init__(self, subcon: Construct[int, int], enum_type: t.Type[FlagsEnumType]): - if not issubclass(enum_type, FlagsEnumBase): - raise TypeError( - "'{}' has to be a '{}'".format(repr(enum_type), repr(FlagsEnumBase)) - ) - + def __init__(self, subcon: Construct[int, int], enum_type: type[FlagsEnumType]): # save enum type - self.enum_type = t.cast(t.Type[FlagsEnumType], enum_type) # type: ignore + self.enum_type: type[FlagsEnumType] = enum_type # init adatper super(TFlagsEnum, self).__init__(subcon) # type: ignore + @override def _decode(self, obj: int, context: Context, path: PathType) -> FlagsEnumType: return self.enum_type(obj) + @override def _encode( self, obj: FlagsEnumType, diff --git a/construct_typed/version.py b/construct_typed/version.py index 740da2f..38a2845 100644 --- a/construct_typed/version.py +++ b/construct_typed/version.py @@ -1,2 +1,2 @@ -version = (0, 5, 6) -version_string = "0.5.6" +version = (0, 7, 0) +version_string = "0.7.0+wrapper" diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 3412486..0000000 --- a/mypy.ini +++ /dev/null @@ -1,3 +0,0 @@ -[mypy] -strict = True -warn_unused_ignores = False \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8a2689b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,76 @@ + +[build-system] +requires = ["setuptools >= 75.8.0"] +build-backend = "setuptools.build_meta" + +[project] +name="construct-typing" +dynamic = ["version"] +license = { file = "LICENSE" } +description="Extension for the python package 'construct' that adds typing features" +readme = "README.md" +authors=[{ name = "Tim Riddermann" }] +requires-python = ">=3.9" +dependencies = [ + "construct==2.10.70", + "typing_extensions>=4.6.0" +] +keywords = [ + "construct", + "kaitai", + "declarative", + "data structure", + "struct", + "binary", + "symmetric", + "parser", + "builder", + "parsing", + "building", + "pack", + "unpack", + "packer", + "unpacker", + "bitstring", + "bytestring", + "annotation", + "type hint", + "typing", + "typed", + "bitstruct", + "PEP 561", +] +classifiers = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Code Generators", + "Programming Language :: Python :: 3", + "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 :: Implementation :: CPython", + "Typing :: Typed", +] + +[project.urls] +"Homepage" = "https://github.com/timrid/construct-typing" +"Bug Reports" = "https://github.com/timrid/construct-typing/issues" + +[tool.setuptools] +packages=[ + "construct-stubs", + "construct-stubs.lib", + "construct_typed" +] + +[tool.setuptools.dynamic] +version = {attr = "construct_typed.version.version_string"} + +[tool.mypy] +strict = true +warn_unused_ignores = false \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 87d1d1b..2514c06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -construct==2.10.67 +construct==2.10.70 pytest>=6.2.0 numpy arrow @@ -7,4 +7,8 @@ cloudpickle lz4 black isort -mypy \ No newline at end of file +mypy +cryptography +build +setuptools +wheel diff --git a/setup.py b/setup.py deleted file mode 100644 index 54dd52d..0000000 --- a/setup.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - -version_string = "?.?.?" -exec(open("./construct_typed/version.py").read()) - -setup( - name="construct-typing", - version=version_string, - packages=["construct-stubs", "construct_typed"], - package_data={ - "construct-stubs": ["*.pyi", "lib/*.pyi"], - "construct_typed": ["py.typed"], - }, - license="MIT", - license_files=("LICENSE",), - description="Extension for the python package 'construct' that adds typing features", - long_description=open("README.md").read(), - long_description_content_type="text/markdown", - platforms=["POSIX", "Windows"], - url="https://github.com/timrid/construct-typing", - author="Tim Riddermann", - python_requires=">=3.7", - install_requires=["construct==2.10.68"], - keywords=[ - "construct", - "kaitai", - "declarative", - "data structure", - "struct", - "binary", - "symmetric", - "parser", - "builder", - "parsing", - "building", - "pack", - "unpack", - "packer", - "unpacker", - "bitstring", - "bytestring", - "annotation", - "type hint", - "typing", - "typed", - "bitstruct", - "PEP 561", - ], - classifiers=[ - "Development Status :: 3 - Alpha", - "License :: OSI Approved :: MIT License", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Software Development :: Build Tools", - "Topic :: Software Development :: Code Generators", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: Implementation :: CPython", - "Typing :: Typed", - ], -) diff --git a/tests/declarativeunittest.py b/tests/declarativeunittest.py index 1d1be0c..ed7fb59 100644 --- a/tests/declarativeunittest.py +++ b/tests/declarativeunittest.py @@ -1,38 +1,170 @@ +import binascii +import io +import typing as t + import pytest +from construct import * +from construct.lib import * + +import construct_typed as cst xfail = pytest.mark.xfail skip = pytest.mark.skip skipif = pytest.mark.skipif -import os, math, random, collections, itertools, io, hashlib, binascii +Buffer = t.Union[bytes, memoryview, bytearray] +ParsedType = t.TypeVar("ParsedType") +BuildTypes = t.TypeVar("BuildTypes") +ContainerType = t.TypeVar("ContainerType", bound=cst.TContainerMixin) +T = t.TypeVar("T") -from construct import * -from construct.lib import * +IdentType = t.TypeVar("IdentType") class ZeroIO(io.BufferedIOBase): - def read(self, __size=None): + def read(self, __size: t.Optional[int] = None) -> bytes: if __size is not None: return bytes(__size) else: return bytes(0) - def read1(self, __size=0): + def read1(self, __size: int = 0) -> bytes: return bytes(__size) -ident = lambda x: x -devzero = ZeroIO() +def ident(x: IdentType) -> IdentType: + return x -def raises(func, *args, **kw): +devzero: t.BinaryIO = ZeroIO() # type: ignore + + +def raises( + func: t.Callable[..., t.Any], *args: t.Any, **kw: t.Any +) -> t.Union[t.Any, Exception]: try: return func(*args, **kw) except Exception as e: return e.__class__ -def common(format, datasample, objsample, sizesample=SizeofError, **kw): +@t.overload +def common( + format: cst.TStruct[ContainerType], + datasample: Buffer, + objsample: t.Union[ContainerType, t.Dict[str, t.Any]], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[ListContainer[ParsedType], t.Any]", + datasample: Buffer, + objsample: t.List[ParsedType], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[Container[t.Any], t.Any]", + datasample: Buffer, + objsample: t.Dict[str, t.Any], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[t.Union[EnumInteger, EnumIntegerString], t.Any]", + datasample: Buffer, + objsample: t.Union[int, str], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[HexDisplayedInteger, t.Any]", + datasample: Buffer, + objsample: t.Union[HexDisplayedInteger, int], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[HexDisplayedBytes, t.Any]", + datasample: Buffer, + objsample: t.Union[HexDisplayedBytes, bytes], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[HexDisplayedDict[str, t.Any], t.Any]", + datasample: Buffer, + objsample: t.Dict[str, t.Any], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[HexDumpDisplayedBytes, t.Any]", + datasample: Buffer, + objsample: t.Union[HexDumpDisplayedBytes, bytes], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[HexDumpDisplayedDict[str, t.Any], t.Any]", + datasample: Buffer, + objsample: t.Dict[str, t.Any], + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +@t.overload +def common( + format: "Construct[ParsedType, t.Any]", + datasample: Buffer, + objsample: ParsedType, + sizesample: t.Union[int, t.Type[Exception]] = ..., + **kw: t.Any +) -> None: + ... + + +def common( + format: "Construct[t.Any, t.Any]", + datasample: Buffer, + objsample: t.Any, + sizesample: t.Union[int, t.Type[Exception]] = SizeofError, + **kw: t.Any +) -> None: obj = format.parse(datasample, **kw) assert obj == objsample data = format.build(objsample, **kw) @@ -44,35 +176,35 @@ def common(format, datasample, objsample, sizesample=SizeofError, **kw): size = format.sizeof(**kw) assert size == sizesample else: - size = raises(format.sizeof, **kw) - assert size == sizesample + size_ex = raises(format.sizeof, **kw) + assert size_ex == sizesample -def setattrs(obj, **kwargs): - """ Set multiple named values of an object """ +def setattrs(obj: T, **kwargs: t.Any) -> T: + """Set multiple named values of an object""" for name, value in kwargs.items(): setattr(obj, name, value) return obj -def commonhex(format, hexdata): +def commonhex(format: "Construct[t.Any, t.Any]", hexdata: str) -> None: commonbytes(format, binascii.unhexlify(hexdata)) -def commondumpdeprecated(format, filename): +def commondumpdeprecated(format: "Construct[t.Any, t.Any]", filename: str) -> None: filename = "tests/deprecated_gallery/blobs/" + filename with open(filename, "rb") as f: data = f.read() commonbytes(format, data) -def commondump(format, filename): +def commondump(format: "Construct[t.Any, t.Any]", filename: str) -> None: filename = "tests/gallery/blobs/" + filename with open(filename, "rb") as f: data = f.read() commonbytes(format, data) -def commonbytes(format, data): +def commonbytes(format: "Construct[t.Any, t.Any]", data: bytes) -> None: obj = format.parse(data) - data2 = format.build(obj) + format.build(obj) diff --git a/tests/declarativeunittest.pyi b/tests/declarativeunittest.pyi deleted file mode 100644 index e2f8cab..0000000 --- a/tests/declarativeunittest.pyi +++ /dev/null @@ -1,109 +0,0 @@ -import typing as t -from construct import * -from construct.lib import * -import construct_typed as cst - -Buffer = t.Union[bytes, memoryview, bytearray] -ParsedType = t.TypeVar("ParsedType") -BuildTypes = t.TypeVar("BuildTypes") -ContainerType = t.TypeVar("ContainerType", bound=cst.TContainerMixin) -T = t.TypeVar("T") - -IdentType = t.TypeVar("IdentType") - -def ident(p1: IdentType) -> IdentType: ... - -devzero: t.BinaryIO - -def raises( - func: t.Callable[..., t.Any], *args: t.Any, **kw: t.Any -) -> t.Union[t.Any, Exception]: ... -@t.overload -def common( - format: cst.TStruct[ContainerType], - datasample: Buffer, - objsample: t.Union[ContainerType, t.Dict[str, t.Any]], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[ListContainer[ParsedType], t.Any], - datasample: Buffer, - objsample: t.List[ParsedType], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[Container[t.Any], t.Any], - datasample: Buffer, - objsample: t.Dict[str, t.Any], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[t.Union[EnumInteger, EnumIntegerString], t.Any], - datasample: Buffer, - objsample: t.Union[int, str], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[HexDisplayedInteger, t.Any], - datasample: Buffer, - objsample: t.Union[HexDisplayedInteger, int], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[HexDisplayedBytes, t.Any], - datasample: Buffer, - objsample: t.Union[HexDisplayedBytes, bytes], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[HexDisplayedDict[str, t.Any], t.Any], - datasample: Buffer, - objsample: t.Dict[str, t.Any], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[HexDumpDisplayedBytes, t.Any], - datasample: Buffer, - objsample: t.Union[HexDumpDisplayedBytes, bytes], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[HexDumpDisplayedDict[str, t.Any], t.Any], - datasample: Buffer, - objsample: t.Dict[str, t.Any], - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -@t.overload -def common( - format: Construct[ParsedType, t.Any], - datasample: Buffer, - objsample: ParsedType, - sizesample: t.Union[int, t.Type[Exception]] = ..., - **kw: t.Any -) -> None: ... -def setattrs(obj: T, **kwargs: t.Any) -> T: ... -def commonhex(format: Construct[t.Any, t.Any], hexdata: str) -> None: ... -def commondumpdeprecated( - format: Construct[t.Any, t.Any], filename: str -) -> None: ... -def commondump(format: Construct[t.Any, t.Any], filename: str) -> None: ... -def commonbytes( - format: Construct[ParsedType, t.Any], data: ParsedType -) -> None: ... diff --git a/tests/test_core.py b/tests/test_core.py index 4b1e4d0..602a899 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # mypy: no-warn-unused-ignores -from .declarativeunittest import raises, common, commonhex, commondumpdeprecated, commondump, commonbytes, ident, devzero +from .declarativeunittest import raises, common, ident, devzero from construct.core import * from construct import * from construct.lib import * @@ -151,17 +151,29 @@ def test_formatfield_bool_issue_901() -> None: assert d.sizeof() == 1 def test_bytesinteger() -> None: + d = BytesInteger(0) + assert raises(d.parse, b"") == IntegerError + assert raises(d.build, 0) == IntegerError d = BytesInteger(4, signed=True, swapped=False) common(d, b"\x01\x02\x03\x04", 0x01020304, 4) common(d, b"\xff\xff\xff\xff", -1, 4) d = BytesInteger(4, signed=False, swapped=this.swapped) common(d, b"\x01\x02\x03\x04", 0x01020304, 4, swapped=False) common(d, b"\x04\x03\x02\x01", 0x01020304, 4, swapped=True) + assert raises(BytesInteger(-1).parse, b"") == IntegerError + assert raises(BytesInteger(-1).build, 0) == IntegerError + assert raises(BytesInteger(8).build, None) == IntegerError + assert raises(BytesInteger(8, signed=False).build, -1) == IntegerError + assert raises(BytesInteger(8, True).build, -2**64) == IntegerError + assert raises(BytesInteger(8, True).build, 2**64) == IntegerError + assert raises(BytesInteger(8, False).build, -2**64) == IntegerError + assert raises(BytesInteger(8, False).build, 2**64) == IntegerError assert raises(BytesInteger(this.missing).sizeof) == SizeofError - assert raises(BytesInteger(4, signed=False).build, -1) == IntegerError - common(BytesInteger(0), b"", 0, 0) def test_bitsinteger() -> None: + d = BitsInteger(0) + assert raises(d.parse, b"") == IntegerError + assert raises(d.build, 0) == IntegerError d = BitsInteger(8) common(d, b"\x01\x01\x01\x01\x01\x01\x01\x01", 255, 8) d = BitsInteger(8, signed=True) @@ -171,9 +183,17 @@ def test_bitsinteger() -> None: d = BitsInteger(16, swapped=this.swapped) common(d, b"\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00", 0xff00, 16, swapped=False) common(d, b"\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01", 0xff00, 16, swapped=True) - assert raises(BitsInteger(this.missing).sizeof) == SizeofError + assert raises(BitsInteger(-1).parse, b"") == IntegerError + assert raises(BitsInteger(-1).build, 0) == IntegerError + assert raises(BitsInteger(5, swapped=True).parse, bytes(5)) == IntegerError + assert raises(BitsInteger(5, swapped=True).build, 0) == IntegerError + assert raises(BitsInteger(8).build, None) == IntegerError assert raises(BitsInteger(8, signed=False).build, -1) == IntegerError - common(BitsInteger(0), b"", 0, 0) + assert raises(BitsInteger(8, True).build, -2**64) == IntegerError + assert raises(BitsInteger(8, True).build, 2**64) == IntegerError + assert raises(BitsInteger(8, False).build, -2**64) == IntegerError + assert raises(BitsInteger(8, False).build, 2**64) == IntegerError + assert raises(BitsInteger(this.missing).sizeof) == SizeofError def test_varint() -> None: d = VarInt @@ -420,11 +440,11 @@ def test_struct_proper_context() -> None: "x"/Byte, "inner"/Struct( "y"/Byte, - "a"/Computed(this._.x+1), - "b"/Computed(this.y+2), + "a"/Computed(this._.x+1), # type: ignore + "b"/Computed(this.y+2), # type: ignore ), - "c"/Computed(this.x+3), - "d"/Computed(this.inner.y+4), + "c"/Computed(this.x+3), # type: ignore + "d"/Computed(this.inner.y+4), # type: ignore ) assert d.parse(b"\x01\x0f") == Container(x=1, inner=Container(y=15, a=2, b=17), c=4, d=19) @@ -511,7 +531,7 @@ def test_const() -> None: def test_computed() -> None: common(Computed(255), b"", 255, 0) - common(Computed(lambda ctx: 255), b"", 255, 0) + common(Computed(lambda ctx: 255), b"", 255, 0) # type: ignore assert Computed(255).build(None) == b"" assert Struct(Computed(255)).build({}) == b"" assert raises(Computed(this.missing).parse, b"") == KeyError @@ -637,8 +657,7 @@ def test_numpy_error() -> None: numpy.load(io.BytesIO(b"")) # type: ignore def test_namedtuple() -> None: - import collections - coord = collections.namedtuple("coord", "x y z") + coord = t.NamedTuple("coord", [("x", int), ("y", int), ("z", int)]) d1 = NamedTuple("coord", "x y z", Array(3, Byte)) common(d1, b"123", coord(49,50,51), 3) d2 = NamedTuple("coord", "x y z", GreedyRange(Byte)) @@ -708,10 +727,13 @@ def test_hexdump() -> None: def test_hexdump_regression_issue_188() -> None: # Hex HexDump were not inheriting subcon flags - d = Struct(Hex(Const(b"MZ"))) + a = Hex(Const(b"MZ")) + d = Struct(a) assert d.parse(b"MZ") == Container() assert d.build(dict()) == b"MZ" - d = Struct(HexDump(Const(b"MZ"))) + + b = HexDump(Const(b"MZ")) + d = Struct(b) assert d.parse(b"MZ") == Container() assert d.build(dict()) == b"MZ" @@ -808,8 +830,10 @@ def test_select_buildfromnone_issue_747() -> None: assert d.build(dict()) == b"" def test_if() -> None: - common(If(True, Byte), b"\x01", 1, 1) - common(If(False, Byte), b"", None, 0) + d = If(True, Byte) + common(d, b"\x01", 1, 1) + d = If(False, Byte) + common(d, b"", None, 0) def test_ifthenelse() -> None: common(IfThenElse(True, Int8ub, Int16ub), b"\x01", 1, 1) @@ -922,6 +946,17 @@ def test_peek() -> None: assert d4.build(Container(a=0x01, b=0x0102)) == b"" assert d4.sizeof() == 0 +def test_offsettedend() -> None: + d1 = Struct( + "header" / Bytes(2), + "data" / OffsettedEnd(-2, GreedyBytes), + "footer" / Bytes(2), + ) + common(d1, b"\x01\x02\x03\x04\x05\x06\x07", Container(header=b'\x01\x02', data=b'\x03\x04\x05', footer=b'\x06\x07')) + + d2 = OffsettedEnd(0, Byte) + assert raises(d2.sizeof) == SizeofError + def test_seek() -> None: d = Seek(5) assert d.parse(b"") == 5 @@ -1330,6 +1365,105 @@ def test_compressed_prefixed() -> None: assert st.parse(st.build(Container(one=zeros,two=zeros))) == Container(one=zeros,two=zeros) assert raises(d.sizeof) == SizeofError +@pytest.mark.xfail(ONWINDOWS and PYPY, reason="no wheel for 'cryptography' is currently available for pypy on windows") +def test_encryptedsym() -> None: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + key128 = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + key256 = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + iv = b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + nonce = iv + + # AES 128/256 bit - ECB + d = EncryptedSym(GreedyBytes, lambda ctx: Cipher(algorithms.AES(ctx.key), modes.ECB())) + common(d, b"\xf4\x0f\x54\xb7\x6a\x7a\xf1\xdb\x92\x73\x14\xde\x2f\xa0\x3e\x2d", b'Secret Message..', key=key128, iv=iv) + common(d, b"\x82\x6b\x01\x82\x90\x02\xa1\x9e\x35\x0a\xe2\xc3\xee\x1a\x42\xf5", b'Secret Message..', key=key256, iv=iv) + + # AES 128/256 bit - CBC + d = EncryptedSym(GreedyBytes, lambda ctx: Cipher(algorithms.AES(ctx.key), modes.CBC(ctx.iv))) + common(d, b"\xba\x79\xc2\x62\x22\x08\x29\xb9\xfb\xd3\x90\xc4\x04\xb7\x55\x87", b'Secret Message..', key=key128, iv=iv) + common(d, b"\x60\xc2\x45\x0d\x7e\x41\xd4\xf8\x85\xd4\x8a\x64\xd1\x45\x49\xe3", b'Secret Message..', key=key256, iv=iv) + + # AES 128/256 bit - CTR + d = EncryptedSym(GreedyBytes, lambda ctx: Cipher(algorithms.AES(ctx.key), modes.CTR(ctx.nonce))) + common(d, b"\x80\x78\xb6\x0c\x07\xf5\x0c\x90\xce\xa2\xbf\xcb\x5b\x22\xb9\xb5", b'Secret Message..', key=key128, nonce=nonce) + common(d, b"\x6a\xae\x7b\x86\x1a\xa6\xe0\x6a\x49\x02\x02\x1b\xf2\x3c\xd8\x0d", b'Secret Message..', key=key256, nonce=nonce) + + assert raises(EncryptedSym(GreedyBytes, "AES").build, b"") == CipherError # type: ignore + assert raises(EncryptedSym(GreedyBytes, "AES").parse, b"") == CipherError # type: ignore + +@pytest.mark.xfail(ONWINDOWS and PYPY, reason="no wheel for 'cryptography' is currently available for pypy on windows") +def test_encryptedsym_cbc_example() -> None: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + d = Struct( + "iv" / Default(Bytes(16), os.urandom(16)), + "enc_data" / EncryptedSym( + Aligned(16, + Struct( + "width" / Int16ul, + "height" / Int16ul + ) + ), + lambda ctx: Cipher(algorithms.AES(ctx._.key), modes.CBC(ctx.iv)) + ) + ) + key128 = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + byts = d.build({"enc_data": {"width": 5, "height": 4}}, key=key128) + obj = d.parse(byts, key=key128) + assert obj.enc_data == Container(width=5, height=4) + +@pytest.mark.xfail(ONWINDOWS and PYPY, reason="no wheel for 'cryptography' is currently available for pypy on windows") +def test_encryptedsymaead() -> None: + from cryptography.hazmat.primitives.ciphers import aead + key128 = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + key256 = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + nonce = b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + + # AES 128/256 bit - GCM + d = Struct( + "associated_data" / Bytes(21), + "data" / EncryptedSymAead( + GreedyBytes, + lambda ctx: aead.AESGCM(ctx._.key), + this._.nonce, + this.associated_data + ) + ) + common( + d, + b"This is authenticated\xb6\xd3\x64\x0c\x7a\x31\xaa\x16\xa3\x58\xec\x17\x39\x99\x2e\xf8\x4e\x41\x17\x76\x3f\xd1\x06\x47\x04\x9f\x42\x1c\xf4\xa9\xfd\x99\x9c\xe9", + Container(associated_data=b"This is authenticated", data=b"The secret message"), + key=key128, + nonce=nonce + ) + common( + d, + b"This is authenticated\xde\xb4\x41\x79\xc8\x7f\xea\x8d\x0e\x41\xf6\x44\x2f\x93\x21\xe6\x37\xd1\xd3\x29\xa4\x97\xc3\xb5\xf4\x81\x72\xa1\x7f\x3b\x9b\x53\x24\xe4", + Container(associated_data=b"This is authenticated", data=b"The secret message"), + key=key256, + nonce=nonce + ) + assert raises(EncryptedSymAead(GreedyBytes, "AESGCM", bytes(16)).build, b"") == CipherError # type: ignore + assert raises(EncryptedSymAead(GreedyBytes, "AESGCM", bytes(16)).parse, b"") == CipherError # type: ignore + +@pytest.mark.xfail(ONWINDOWS and PYPY, reason="no wheel for 'cryptography' is currently available for pypy on windows") +def test_encryptedsymaead_gcm_example() -> None: + from cryptography.hazmat.primitives.ciphers import aead + d = Struct( + "nonce" / Default(Bytes(16), os.urandom(16)), + "associated_data" / Bytes(21), + "enc_data" / EncryptedSymAead( + GreedyBytes, + lambda ctx: aead.AESGCM(ctx._.key), + this.nonce, + this.associated_data + ) + ) + key128 = b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + byts = d.build({"associated_data": b"This is authenticated", "enc_data": b"The secret message"}, key=key128) + obj = d.parse(byts, key=key128) + assert obj.enc_data == b"The secret message" + assert obj.associated_data == b"This is authenticated" + def test_rebuffered() -> None: data = b"0" * 1000 assert Rebuffered(Array(1000,Byte)).parse_stream(io.BytesIO(data)) == [48]*1000 @@ -1545,7 +1679,7 @@ def test_operators() -> None: assert d.docs == "description" d = "description" * Byte assert d.docs == "description" - """ + _ = """ description """ * \ Byte @@ -1687,9 +1821,11 @@ def test_from_issue_244() -> None: assert d.parse(b"abcd") == [Container(num=97, index=0),Container(num=98, index=1),Container(num=99, index=2),Container(num=100, index=3),] def test_from_issue_269() -> None: - d = Struct("enabled" / Byte, If(this.enabled, Padding(2))) + a = If(this.enabled, Padding(2)) + d = Struct("enabled" / Byte, a) assert d.build(dict(enabled=1)) == b"\x01\x00\x00" assert d.build(dict(enabled=0)) == b"\x00" + d = Struct("enabled" / Byte, "pad" / If(this.enabled, Padding(2))) assert d.build(dict(enabled=1)) == b"\x01\x00\x00" assert d.build(dict(enabled=0)) == b"\x00" @@ -1796,11 +1932,11 @@ def test_pickling_constructs() -> None: ) data = bytes(100) - du = cloudpickle.loads(cloudpickle.dumps(d, protocol=-1)) + du = cloudpickle.loads(cloudpickle.dumps(d, protocol=-1)) # type: ignore assert du.parse(data) == d.parse(data) def test_pickling_constructs_issue_894() -> None: - import cloudpickle + import cloudpickle # type: ignore fundus_header = Struct( 'width' / Int32un, @@ -1812,7 +1948,7 @@ def test_pickling_constructs_issue_894() -> None: 'img' / Int8un, ) - cloudpickle.dumps(fundus_header) + cloudpickle.dumps(fundus_header) # type: ignore def test_exposing_members_attributes() -> None: d1 = Struct( @@ -2023,7 +2159,7 @@ def test_struct_root_topmost() -> None: assert d.parse(b"", z=2) == Container(x=1, inner=Container(inner2=Container(x=1,z=2,zz=2))) def test_parsedhook_repeatersdiscard() -> None: - outputs = [] + outputs: t.List[int] = [] def printobj1(obj: int, ctx: "Context") -> None: outputs.append(obj) d1 = GreedyRange(Byte * printobj1, discard=True) diff --git a/tests/test_typed.py b/tests/test_typed.py index 7d726a3..df8cc84 100644 --- a/tests/test_typed.py +++ b/tests/test_typed.py @@ -2,9 +2,11 @@ # pyright: strict import dataclasses import enum +import textwrap import typing as t import construct as cs + import construct_typed as cst from construct_typed import DataclassBitStruct, DataclassMixin, DataclassStruct, csfield @@ -73,6 +75,19 @@ def test_dataclass_str_repr() -> None: ) +def test_dataclass_ifthenelse() -> None: + @dataclasses.dataclass + class IfThenElseTest(DataclassMixin): + test_if: t.Optional[int] = csfield(cs.If(False, cs.Int8ub)) + test_ifthenelse: t.Optional[int] = csfield( + cs.IfThenElse(True, cs.Int8ub, cs.Pass) + ) + + a = IfThenElseTest(test_if=None, test_ifthenelse=None) + assert a.test_if == None + assert a.test_ifthenelse == None + + def test_dataclass_struct() -> None: @dataclasses.dataclass class Image(DataclassMixin): @@ -386,9 +401,10 @@ def test_tenum_no_enumbase() -> None: def test_tenum_asdict() -> None: # see: https://github.com/timrid/construct-typing/issues/21 - import construct_typed as cst import dataclasses + import construct_typed as cst + class TestEnum(cst.EnumBase): one = 1 two = 2 @@ -427,9 +443,9 @@ def test_tenum_docstring() -> None: Value_NoDoc = cst.EnumValue(2) Value_NoDoc2 = 3 - assert ( - TestEnum.__doc__ - == """ + assert TestEnum.__doc__ is not None + assert textwrap.dedent(TestEnum.__doc__) == textwrap.dedent( + """ This is an test enum. """ ) @@ -499,9 +515,10 @@ def test_tenum_flags() -> None: def test_tenum_flags_asdict() -> None: - import construct_typed as cst import dataclasses + import construct_typed as cst + class TestEnum(cst.FlagsEnumBase): one = 1 two = 2 @@ -540,9 +557,9 @@ def test_tenum_flags_docstring() -> None: Value_NoDoc = cst.EnumValue(2) Value_NoDoc2 = 4 - assert ( - TestEnum.__doc__ - == """ + assert TestEnum.__doc__ is not None + assert textwrap.dedent(TestEnum.__doc__) == textwrap.dedent( + """ This is an test flags enum. """ )