Compare commits

...

93 commits
v0.5.2 ... main

Author SHA1 Message Date
wrapper
486c553d65 fallback switch
Some checks failed
CI / OS ubuntu-latest, Python 3.10 (push) Has been cancelled
CI / OS ubuntu-latest, Python 3.11 (push) Has been cancelled
CI / OS ubuntu-latest, Python 3.12 (push) Has been cancelled
CI / OS ubuntu-latest, Python 3.13 (push) Has been cancelled
CI / OS ubuntu-latest, Python 3.9 (push) Has been cancelled
CI / OS windows-latest, Python 3.10 (push) Has been cancelled
CI / OS windows-latest, Python 3.11 (push) Has been cancelled
CI / OS windows-latest, Python 3.12 (push) Has been cancelled
CI / OS windows-latest, Python 3.13 (push) Has been cancelled
CI / OS windows-latest, Python 3.9 (push) Has been cancelled
CI / create_wheel_and_sdist (push) Has been cancelled
2026-04-07 23:28:33 +07:00
wrapper
0a4628935b add
Some checks are pending
CI / OS ubuntu-latest, Python 3.10 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.11 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.12 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.13 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.9 (push) Waiting to run
CI / OS windows-latest, Python 3.10 (push) Waiting to run
CI / OS windows-latest, Python 3.11 (push) Waiting to run
CI / OS windows-latest, Python 3.12 (push) Waiting to run
CI / OS windows-latest, Python 3.13 (push) Waiting to run
CI / OS windows-latest, Python 3.9 (push) Waiting to run
CI / create_wheel_and_sdist (push) Waiting to run
2026-04-07 20:19:38 +07:00
wrapper
c1896ab8dc Construct Error class does not return
Some checks are pending
CI / OS ubuntu-latest, Python 3.10 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.11 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.12 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.13 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.9 (push) Waiting to run
CI / OS windows-latest, Python 3.10 (push) Waiting to run
CI / OS windows-latest, Python 3.11 (push) Waiting to run
CI / OS windows-latest, Python 3.12 (push) Waiting to run
CI / OS windows-latest, Python 3.13 (push) Waiting to run
CI / OS windows-latest, Python 3.9 (push) Waiting to run
CI / create_wheel_and_sdist (push) Waiting to run
2026-04-07 19:25:45 +07:00
wrapper
2f078b340b more switch fixes
Some checks are pending
CI / OS ubuntu-latest, Python 3.10 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.11 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.12 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.13 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.9 (push) Waiting to run
CI / OS windows-latest, Python 3.10 (push) Waiting to run
CI / OS windows-latest, Python 3.11 (push) Waiting to run
CI / OS windows-latest, Python 3.12 (push) Waiting to run
CI / OS windows-latest, Python 3.13 (push) Waiting to run
CI / OS windows-latest, Python 3.9 (push) Waiting to run
CI / create_wheel_and_sdist (push) Waiting to run
2026-04-07 19:02:24 +07:00
wrapper
0c93e4d551 mod
Some checks are pending
CI / OS ubuntu-latest, Python 3.10 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.11 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.12 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.13 (push) Waiting to run
CI / OS ubuntu-latest, Python 3.9 (push) Waiting to run
CI / OS windows-latest, Python 3.10 (push) Waiting to run
CI / OS windows-latest, Python 3.11 (push) Waiting to run
CI / OS windows-latest, Python 3.12 (push) Waiting to run
CI / OS windows-latest, Python 3.13 (push) Waiting to run
CI / OS windows-latest, Python 3.9 (push) Waiting to run
CI / create_wheel_and_sdist (push) Waiting to run
2026-04-07 18:48:27 +07:00
timrid
f3b7bc342e incremented version to 0.7.0 2025-10-27 20:25:48 +01:00
timrid
fddd438ac8 fix publish workflow 2025-10-27 20:25:27 +01:00
timrid
fb3f0c926f
Merge pull request #34 from timrid/feature/pyproject-toml
Switch to `pyproject.toml` and trusted publishing
2025-10-27 19:58:58 +01:00
timrid
071038405c added trusted publishing infos 2025-10-27 19:57:41 +01:00
Tim Rid
a328767689 replaced setup.py with pyproject.toml 2025-10-27 19:57:41 +01:00
timrid
fe19de7b48
Merge pull request #36 from Oli4/main
fix(core.pyi): replaces typing.TypeAlias by typing_extensions.TypeAlias for compatibility with python3.9
2025-10-26 22:29:24 +01:00
Olivier Morelle
c5ffc142be fix(core.pyi): replaces typing.TypeAlias by typing_extensions.TypeAlias for compatibility with python3.9 2025-10-26 20:40:54 +01:00
timrid
5c0ea2e827
Merge pull request #33 from timrid/feature/update-to-construct-v2.10.70
Update to construct==2.10.70
2025-01-12 15:43:56 +01:00
Tim Rid
a9f4b44801 fixed mypy errors 2025-01-12 15:39:57 +01:00
Tim Rid
04e1bc0c61 updated to construct==2.10.70 2025-01-12 15:35:54 +01:00
timrid
ef933cc06d
Merge pull request #30 from adamrimon/feature/inner_construct_methods
Internal Construct methods
2025-01-12 13:39:49 +01:00
timrid
7107945a31
Merge pull request #32 from timrid/feature/python_3.12_3.13
Added Python 3.12 & 3.13 and removed EOL Python 3.7 & 3.8
2025-01-12 13:09:03 +01:00
Tim Rid
a222fe7695 print pyright version in CI 2025-01-12 13:01:00 +01:00
Tim Rid
f01246ae82 Since Python 3.13 the compiler now strips common leading whitespace from every line in a docstring. So this have to be fixed in the pytests. 2025-01-12 12:46:05 +01:00
Tim Rid
a2a6be536f satisfy pyright 1.1.391 2025-01-12 12:33:51 +01:00
Tim Rid
457857389a According to PEP688 (https://peps.python.org/pep-0688/#removal-of-the-bytes-special-case) bytes has not any special meaning any more. So bytearray is not included in bytes any more and we have to declare it explicitly. collections.abc.Buffer cant be used, because memoryview is not supported by construct 2025-01-12 12:22:45 +01:00
Tim Rid
bbeb5b185f removed Python Versions with EOL (3.7, 3.8) 2025-01-12 11:58:33 +01:00
Tim Rid
647c273bff Added Python 3.12 and 3.13 to the CI 2025-01-12 11:53:03 +01:00
Prilkop
9957b0e6f1
added internal Construct methods, used when inheriting the class 2024-06-17 15:46:04 +03:00
Tim Riddermann
2ffa785ce6 incremented version to 0.6.2 2023-08-03 09:27:59 +02:00
Tim Riddermann
c7e3fc7057 removed unnessasary __new__ methods in DataclassStruct, TEnum and TFlagsEnum 2023-08-03 09:22:33 +02:00
Tim Riddermann
c405d09d5f fixed error message from EnumBase and FlagsEnumBase that occures since pyright v1.1.320 2023-08-03 09:21:30 +02:00
Tim Riddermann
af98d2004d incremented version to 0.6.1 2023-07-24 11:56:08 +02:00
Tim Riddermann
74593404b1 fixed missmatch between stub and runtime type for Array 2023-07-24 11:55:44 +02:00
Tim Riddermann
af1a93c630 incremented version to 0.6.0 2023-07-24 11:02:39 +02:00
Tim Riddermann
c04a90575d use PEP688 buffer protocol for "parse" Method (fixes #24) 2023-07-24 10:57:50 +02:00
Tim Riddermann
c6bded59f4 fixes #25 2023-07-24 10:56:37 +02:00
Tim Riddermann
86fddbe2ac simplified "IfThenElse" 2023-07-24 10:36:28 +02:00
Tim Riddermann
26f7fd82dc removed unnessesary comment 2023-07-24 10:14:33 +02:00
Tim Riddermann
86f27dc1fb use method-scoped TypeVars for __new__ (see here https://github.com/microsoft/pyright/issues/5404#issuecomment-1645764913) 2023-07-24 09:40:50 +02:00
Tim Riddermann
ede1a72c70 added ignores, because 'Computed' is not subscriptable at runtime 2023-07-18 17:42:57 +02:00
Tim Riddermann
d9026772a0 fixed further mypy issues 2023-07-18 17:39:10 +02:00
Tim Riddermann
825783db57 fixed mypy issues 2023-07-18 17:37:29 +02:00
Tim Riddermann
e39fc5b19d corrected version requirements.txt 2023-07-18 17:36:54 +02:00
Tim Riddermann
3c81d99c12 Added __new__ only where it is an absolute must have. 2023-07-18 17:36:27 +02:00
Tim Riddermann
f75029247f added missing definitions 2023-07-18 17:26:03 +02:00
Tim Riddermann
7f08ab2f28 added intermediate variable, so that pyright v1.1.316 passes 2023-07-18 17:25:21 +02:00
Tim Riddermann
dd1691528f fixed pyright errors 2023-07-18 15:46:17 +02:00
Tim Riddermann
6b4a52e73a merged .py and .pyi 2023-07-18 15:44:17 +02:00
Tim Riddermann
1e7ced992d removed all self type annotations of __init__ methods 2023-07-18 15:38:01 +02:00
Tim Riddermann
dcdeeca39f removed all __new__ methods. mypy v1.4.1 is working. 2023-06-30 14:13:01 +02:00
Tim Riddermann
6550e59dd3 added missing __new__ methods 2023-06-30 13:06:29 +02:00
Tim Riddermann
3b51654b1e incremented version to 0.5.6 2023-05-09 13:03:26 +02:00
timrid
88f3109722
Merge pull request #23 from timrid/bugfix/stream-type
Bugfix/stream type
2023-05-09 13:00:06 +02:00
Tim Riddermann
2200a0a8d0 fixed mypy errors 2023-05-09 12:31:38 +02:00
Tim Riddermann
bb3935e7eb fixes #19 2023-05-09 12:27:46 +02:00
timrid
4d07ed4cc6
Merge pull request #22 from timrid/bugfix/pickel-enum-by-value
Pickle enums by value instead of name (restores pre-3.11 behavior) to support `dataclasses.asdict`
2023-05-09 12:13:30 +02:00
Tim Riddermann
a44ea84299 fixed mypy error 2023-05-09 11:47:10 +02:00
Tim Riddermann
6185a95e74 ignored typing error 2023-05-09 11:44:09 +02:00
Tim Riddermann
d969fab1d3 fixed python 3.7 "TypeError: 'type' object is not subscriptable" 2023-05-09 11:31:18 +02:00
Tim Riddermann
3a676478ee fixed mypy issues 2023-05-09 11:27:19 +02:00
Tim Riddermann
50a2f34fc6 fixed mypy issue 2023-05-09 11:12:42 +02:00
Tim Riddermann
ab33490dec Pickle enums by value instead of name (restores pre-3.11 behavior) to support dataclasses.asdict 2023-05-09 10:52:18 +02:00
Tim Riddermann
acc3fa3443 incremented version to 0.5.5 2023-01-09 13:23:37 +01:00
Tim Riddermann
bea99456d7 using __new__ instead of __init__ for EnumBase to create enum member objects (fixes #18) 2023-01-09 09:02:33 +01:00
Tim Riddermann
80b11b2ef9 added missing parts for cst.Array 2023-01-09 08:18:16 +01:00
Tim Riddermann
238ca5d2df removed not working __init__ 2023-01-06 12:05:14 +01:00
Tim Riddermann
232fcbd725 inserted missing asserts 2023-01-06 12:00:56 +01:00
Tim Riddermann
7f6577af47 added __init__ methods for all that currently only defined an __new__ method 2023-01-06 12:00:42 +01:00
Tim Riddermann
b5add648ed fixed pyright 1.1.287 error 2023-01-06 10:45:55 +01:00
Tim Riddermann
f98532a382 fixed _subconsindexes 2023-01-06 10:35:07 +01:00
Tim Riddermann
747bfaebd1 added generic wrapper for cs.Array 2023-01-06 10:32:44 +01:00
Tim Riddermann
31a10dcc88 added type hints for Struct._subcons, Sequence._subcons, FocusedSeq._subcons, Union._subcons, LazyStruct._subcons, LazyStruct._subconsindexes 2023-01-06 10:32:19 +01:00
Tim Rid
ed5d0fe826 incremented version to 0.5.4 2022-12-24 12:20:03 +01:00
Tim Rid
916349f876 enhanced EnumBase and FlagsEnumBase to support induvidual documentation for each enum value via EnumValue 2022-12-24 12:04:45 +01:00
Tim Rid
b7e92c3c7d removed mypy error 'Unused "type: ignore" comment' 2022-12-23 20:48:05 +01:00
Tim Rid
349c8e5dd2 Updated IfThenElse so that it represents the real implementation 2022-12-23 20:37:05 +01:00
Tim Rid
ceb4b67ff8 incremented version to 0.5.3 2022-10-31 10:06:15 +01:00
Tim Rid
f3051a1116 upgrade requirement to construct==2.10.68 2022-10-31 09:54:50 +01:00
Tim Rid
25883cb7c8 Updated settings.json 2022-10-31 09:54:15 +01:00
Tim Rid
ce871936d9 Added Python 3.10 and 3.11 to the Test-Matrix and removed github warning "Node.js 12 actions are deprecated." 2022-10-25 20:49:07 +02:00
Tim Rid
92b3555c50 removed _create_pseudo_member completely and integrated it into _missing_ 2022-10-25 19:26:37 +02:00
Tim Rid
e10938f308 Added type hint to self type, for mypy support. 2022-10-24 00:07:57 +02:00
Tim Rid
2b780ae7b9 changed Adapter __new__ to __init__ to fix #13. But this change on Adapter also reqires to change other Adapter subclasses. 2022-10-23 23:49:58 +02:00
Tim Rid
aeff7c6b7c remove github actions deprecation warnings. 2022-10-23 23:03:40 +02:00
Tim Rid
a03190d816 fixed some pyright/pylance issues 2022-10-23 22:48:42 +02:00
Tim Rid
94896208b9 fixed mypy error: error: Returning Any from function declared to return "int" 2022-10-23 22:24:08 +02:00
Tim Rid
18597a05db changed requirements to use the latest numpy, because numpy 1.23.* is not supporting Python 3.7. 2022-10-23 22:19:07 +02:00
Tim Rid
19fd0bf7f3 Fixed Error in Python 3.11: "_sunder_ names, such as '_create_pseudo_member_', are reserved for future Enum use" 2022-10-23 22:12:55 +02:00
Tim Rid
47fd2838f7 changed requirements for Python 3.11 2022-10-23 22:12:49 +02:00
Tim Rid
834d244eda ignore mypy error "Call to untyped function "load" in typed context" 2022-02-13 01:14:01 +01:00
Tim Rid
5533a7923e fixed "potential security vulnerabilities" from github 2022-02-13 01:10:20 +01:00
Tim Rid
29d2608c37 fixed BuildTypes of cs.Const 2022-02-13 01:06:45 +01:00
timrid
e91bd27589
use settings from mypy.ini and also test the construct_typed folder 2022-01-09 12:51:34 +01:00
timrid
ba31334a33
Merge pull request #8 from Parnassius/pathlike
Use `os.PathLike` for file names
2022-01-09 12:48:27 +01:00
Tim Rid
375bab3faa Merge branch 'bugfix/mypy-unused-ignores' into main 2022-01-09 12:39:36 +01:00
Tim Rid
930fd9928c added mypy.ini: no error on unused ignores. mypy and pyright are slightly different, so pyright needs some ignores that mypy does not nee. 2022-01-09 12:30:57 +01:00
Parnassius
0979257dd7 Use os.PathLike for file names 2022-01-07 12:27:46 +01:00
23 changed files with 1278 additions and 672 deletions

View file

@ -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' ]
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
runs-on: ${{ matrix.os }}
name: OS ${{ matrix.os }}, Python ${{ matrix.python-version }}
@ -16,25 +20,26 @@ jobs:
steps:
# Checks out a copy of your repository on the machine
- name: Checkout code
uses: actions/checkout@v1
uses: actions/checkout@v3
# Setup python
- name: Setup python
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: x64
# Setup node.js (for pyright)
- name: Setup node.js (for pyright)
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: '14'
node-version: 16
# Install pyright
- name: Install pyright
run: |
npm install -g pyright
pyright --version
# Install this package
- name: Install this package
@ -55,9 +60,36 @@ jobs:
# Run mypy
- name: Run mypy
run: |
mypy --strict tests/
mypy tests construct_typed
# Run pyright
- 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/

View file

@ -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

3
.gitignore vendored
View file

@ -129,3 +129,6 @@ dmypy.json
example_737
example_888
example_ksy.ksy
# Test stuff
devtest/

32
.vscode/settings.json vendored
View file

@ -1,24 +1,26 @@
{
"python.pythonPath": "python",
// static analysis
"python.languageServer": "Pylance",
// "python.testing.unittestEnabled": false,
// "python.testing.nosetestsEnabled": false,
// "python.testing.pytestEnabled": true,
"pythonTestExplorer.testFramework": "pytest",
"python.formatting.provider": "black",
"python.sortImports.path": "isort",
"python.sortImports.args": [
"--profile=black",
],
// "[python]": {
// "editor.codeActionsOnSave": {
// "source.organizeImports": true
// }
// }
"python.analysis.typeCheckingMode": "strict",
"python.analysis.autoImportCompletions": false,
"python.analysis.diagnosticSeverityOverrides": {
"reportPrivateUsage": "information",
"reportUntypedNamedTuple": "information",
},
// formating
"python.formatting.provider": "black",
// sorting
"python.sortImports.path": "isort",
"python.sortImports.args": [
"--profile=black",
],
// tests
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
}

View file

@ -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)

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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]: ...
@ -543,7 +542,7 @@ class Path2(ExprMixin[ReturnType]):
class FuncPath(ExprMixin[ReturnType]):
def __init__(self, func: t.Callable[[t.Any], t.Any], operand: t.Optional[t.Any] = ...) -> None: ...
def __init__(self, func: t.Callable[[t.Any], ReturnType], operand: t.Optional[t.Any] = ...) -> None: ...
def __call__(self, operand: t.Any, *args: t.Any) -> ReturnType: ...

View file

@ -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: ...

View file

@ -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: ...

View file

@ -1,5 +1,6 @@
import typing as t
PY: t.Tuple[int, int]
PY2: bool
PY3: bool
PYPY: bool

View file

@ -9,16 +9,21 @@ from .dataclass_struct import (
TStructField,
csfield,
sfield,
EnhancedDataclassMixin
)
from .generic_wrapper import (
Adapter,
ConstantOrContextLambda,
ConstantOrContextLambda2,
Construct,
Context,
ListContainer,
PathType,
Array,
Subconstruct,
Computed,
)
from .tenum import EnumBase, FlagsEnumBase, TEnum, TFlagsEnum
from .tenum import EnumBase, EnumValue, FlagsEnumBase, TEnum, TFlagsEnum
__all__ = [
"DataclassBitStruct",
@ -31,14 +36,20 @@ __all__ = [
"TStructField",
"csfield",
"sfield",
"EnhancedDataclassMixin",
"EnumBase",
"EnumValue",
"FlagsEnumBase",
"TEnum",
"TFlagsEnum",
"Adapter",
"ConstantOrContextLambda",
"ConstantOrContextLambda2",
"Construct",
"Context",
"ListContainer",
"PathType",
"Array",
"Subconstruct",
"Computed"
]

View file

@ -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,6 +29,8 @@ class DataclassMixin:
methods exists and every name can be used.
"""
__dataclass_fields__: "t.ClassVar[dict[str, dataclasses.Field[t.Any]]]"
def __getitem__(self, key: str) -> t.Any:
return getattr(self, key)
@ -75,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.
@ -100,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:
@ -150,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)
@ -178,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"]
@ -188,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:
@ -210,11 +202,12 @@ class DataclassStruct(Adapter[t.Any, t.Any, DataclassType, DataclassType]):
value = obj[field.name]
setattr(dc, field.name, value)
return dc
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)}")
@ -222,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.
@ -262,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
@ -269,4 +281,4 @@ TBitStruct = DataclassBitStruct
TContainerMixin = DataclassMixin
TContainerBase = DataclassMixin
TStructField = csfield
sfield = csfield
sfield = csfield

View file

@ -12,11 +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
@ -37,5 +40,18 @@ else:
class Context:
pass
class Array(
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

View file

@ -1,37 +1,90 @@
# 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 ############################################################################################################
class EnumValue:
"""
This is a helper class for adding documentation to an enum value.
"""
def __init__(self, value: int, doc: str | None = None) -> None:
self.value: int = value
self.__doc__ = doc if doc else ""
class EnumBase(enum.IntEnum):
"""
Base class for an Enum used in `construct_typed.TEnum`.
This class extends the standard `enum.IntEnum`, so that missing values are automatically generated.
This class extends the standard `enum.IntEnum` by.
- missing values are automatically generated
- possibility to add documentation for each enum value (see `EnumValue`)
Example::
>>> class State(EnumBase):
... Idle = 1
... Running = EnumValue(2, "This is the running state.")
>>> State(1)
<State.Idle: 1>
>>> State["Idle"]
<State.Idle: 1>
>>> State.Idle
<State.Idle: 1>
>>> State(3) # missing value
<State.3: 3>
>>> State.Running.__doc__ # documentation
'This is the running state.'
"""
# Extend the enum type with __missing__ method. So if a enum value
def __new__(cls, val: EnumValue | int) -> "Self":
if isinstance(val, EnumValue):
obj = int.__new__(cls, val.value)
obj._value_ = val.value
obj.__doc__ = val.__doc__
else:
obj = int.__new__(cls, val)
obj._value_ = val
obj.__doc__ = ""
return obj
# Extend the enum type with _missing_ method. So if a enum value
# 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["EnumBase"]:
@override
def _missing_(cls, value: t.Any) -> enum.Enum | None:
if isinstance(value, int):
return cls._create_pseudo_member_(value)
pseudo_member = cls._value2member_map_.get(value, None)
if pseudo_member is None:
new_member = int.__new__(cls, value)
# I expect a name attribute to hold a string, hence str(value)
# However, new_member._name_ = value works, too
new_member._name_ = str(value)
new_member._value_ = value
new_member.__doc__ = "missing value"
pseudo_member = cls._value2member_map_.setdefault(value, new_member)
return pseudo_member
return None # will raise the ValueError in Enum.__new__
@classmethod
def _create_pseudo_member_(cls, value: int) -> "EnumBase":
pseudo_member = cls._value2member_map_.get(value, None) # type: ignore
if pseudo_member is None:
new_member = int.__new__(cls, value)
# I expect a name attribute to hold a string, hence str(value)
# However, new_member._name_ = value works, too
new_member._name_ = str(value)
new_member._value_ = value
pseudo_member = cls._value2member_map_.setdefault(value, new_member) # type: ignore
return pseudo_member # type: ignore
@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.
"""
return self.__class__, (self._value_,)
EnumType = t.TypeVar("EnumType", bound=EnumBase)
@ -41,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,
@ -79,7 +121,65 @@ class TEnum(Adapter[int, int, EnumType, EnumType]):
# ## TFlagsEnum #######################################################################################################
class FlagsEnumBase(enum.IntFlag):
pass
"""
Base class for an Enum used in `construct_typed.TFlagsEnum`.
This class extends the standard `enum.IntFlag` by.
- possibility to add documentation for each enum value (see `EnumValue`)
Example::
>>> class Option(FlagsEnumBase):
... OptOne = 1
... OptTwo = EnumValue(2, "This is option two.")
>>> Option(1)
<Option.OptOne: 1>
>>> Option["OptOne"]
<Option.OptOne: 1>
>>> Option.OptOne
<Option.OptOne: 1>
>>> Option(3)
<Option.OptTwo|OptOne: 3>
>>> Option(4)
<Option.4: 4>
>>> Option.OptTwo.__doc__ # documentation
'This is option two.'
"""
def __new__(cls, val: EnumValue | int) -> "Self":
if isinstance(val, EnumValue):
obj = int.__new__(cls, val.value)
obj._value_ = val.value
obj.__doc__ = val.__doc__
else:
obj = int.__new__(cls, val)
obj._value_ = val
obj.__doc__ = ""
return obj
@classmethod
@override
def _missing_(cls, value: t.Any) -> t.Any:
"""
Returns member (possibly creating it) if one can be found for value.
"""
new_member = super()._missing_(value)
new_member.__doc__ = "missing value"
return new_member
@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.
"""
return self.__class__, (self._value_,)
FlagsEnumType = t.TypeVar("FlagsEnumType", bound=FlagsEnumBase)
@ -89,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,

View file

@ -1,2 +1,2 @@
version = (0, 5, 2)
version_string = "0.5.2"
version = (0, 7, 0)
version_string = "0.7.0+wrapper"

76
pyproject.toml Normal file
View file

@ -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

View file

@ -1,10 +1,14 @@
construct==2.10.67
construct==2.10.70
pytest>=6.2.0
numpy==1.20.*
numpy
arrow
ruamel.yaml
cloudpickle
lz4
black
isort
mypy
mypy
cryptography
build
setuptools
wheel

View file

@ -1,64 +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.67"],
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 :: Implementation :: CPython",
"Typing :: Typed",
],
)

View file

@ -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)

View file

@ -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: ...

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from .declarativeunittest import raises, common, commonhex, commondumpdeprecated, commondump, commonbytes, ident, devzero
# mypy: no-warn-unused-ignores
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
@ -224,8 +244,8 @@ def test_paddedstring() -> None:
common(PaddedString(100, e), data, s, 100)
for e in ["ascii","utf8","utf16","utf-16-le","utf32","utf-32-le"]:
PaddedString(10, e).sizeof() == 10
PaddedString(this.n, e).sizeof(n=10) == 10
assert PaddedString(10, e).sizeof() == 10
assert PaddedString(this.n, e).sizeof(n=10) == 10
def test_pascalstring() -> None:
for e,_ in [("utf8",1),("utf16",2),("utf_16_le",2),("utf32",4),("utf_32_le",4)]:
@ -236,8 +256,8 @@ def test_pascalstring() -> None:
common(PascalString(sc, e), sc.build(0), u"")
for e in ["utf8","utf16","utf-16-le","utf32","utf-32-le","ascii"]:
raises(PascalString(Byte, e).sizeof) == SizeofError
raises(PascalString(VarInt, e).sizeof) == SizeofError
assert raises(PascalString(Byte, e).sizeof) == SizeofError
assert raises(PascalString(VarInt, e).sizeof) == SizeofError
def test_cstring() -> None:
s = u""
@ -246,12 +266,12 @@ def test_cstring() -> None:
common(CString(e), s.encode(e)+bytes(us), s)
common(CString(e), bytes(us), u"")
CString("utf8").build(s) == b'\xd0\x90\xd1\x84\xd0\xbe\xd0\xbd'+b"\x00"
CString("utf16").build(s) == b'\xff\xfe\x10\x04D\x04>\x04=\x04'+b"\x00\x00"
CString("utf32").build(s) == b'\xff\xfe\x00\x00\x10\x04\x00\x00D\x04\x00\x00>\x04\x00\x00=\x04\x00\x00'+b"\x00\x00\x00\x00"
assert CString("utf8").build(s) == b'\xd0\x90\xd1\x84\xd0\xbe\xd0\xbd'+b"\x00"
assert CString("utf16").build(s) == b'\xff\xfe\x10\x04D\x04>\x04=\x04'+b"\x00\x00"
assert CString("utf32").build(s) == b'\xff\xfe\x00\x00\x10\x04\x00\x00D\x04\x00\x00>\x04\x00\x00=\x04\x00\x00'+b"\x00\x00\x00\x00"
for e in ["utf8","utf16","utf-16-le","utf32","utf-32-le","ascii"]:
raises(CString(e).sizeof) == SizeofError
assert raises(CString(e).sizeof) == SizeofError
def test_greedystring() -> None:
for e,_ in [("utf8",1),("utf16",2),("utf_16_le",2),("utf32",4),("utf_32_le",4)]:
@ -260,7 +280,7 @@ def test_greedystring() -> None:
common(GreedyString(e), b"", u"")
for e in ["utf8","utf16","utf-16-le","utf32","utf-32-le","ascii"]:
raises(GreedyString(e).sizeof) == SizeofError
assert raises(GreedyString(e).sizeof) == SizeofError
def test_string_encodings() -> None:
# checks that "-" is replaced with "_"
@ -271,7 +291,7 @@ def test_flag() -> None:
d = Flag
common(d, b"\x00", False, 1)
common(d, b"\x01", True, 1)
d.parse(b"\xff") == True
assert d.parse(b"\xff") == True
def test_enum() -> None:
d = Enum(Byte, one=1, two=2, four=4, eight=8)
@ -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
@ -591,7 +611,7 @@ def test_rebuild_issue_664() -> None:
def test_default() -> None:
d = Default(Byte, 0)
common(d, b"\xff", 255, 1)
d.build(None) == b"\x00"
assert d.build(None) == b"\x00"
def test_check() -> None:
common(Check(True), b"", None, 0)
@ -634,11 +654,10 @@ def test_numpy() -> None:
@pytest.mark.xfail(reason="docs stated that it throws StreamError, not true at all")
def test_numpy_error() -> None:
import numpy, io
numpy.load(io.BytesIO(b""))
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
@ -1033,13 +1068,14 @@ def test_prefixed() -> None:
common(d5, b"\x0a"+bytes(10), u"\x00"*10, SizeofError)
def test_prefixedarray() -> None:
common(PrefixedArray(Byte,Byte), b"\x02\x0a\x0b", [10,11], SizeofError)
assert PrefixedArray(Byte, Byte).parse(b"\x03\x01\x02\x03") == [1,2,3]
assert PrefixedArray(Byte, Byte).parse(b"\x00") == []
assert PrefixedArray(Byte, Byte).build([1,2,3]) == b"\x03\x01\x02\x03"
assert raises(PrefixedArray(Byte, Byte).parse, b"") == StreamError
assert raises(PrefixedArray(Byte, Byte).parse, b"\x03\x01") == StreamError
assert raises(PrefixedArray(Byte, Byte).sizeof) == SizeofError
d = PrefixedArray(Byte, Byte)
common(d, b"\x02\x0a\x0b", [10,11], SizeofError)
assert d.parse(b"\x03\x01\x02\x03") == [1,2,3]
assert d.parse(b"\x00") == []
assert d.build([1,2,3]) == b"\x03\x01\x02\x03"
assert raises(d.parse, b"") == StreamError
assert raises(d.parse, b"\x03\x01") == StreamError
assert raises(d.sizeof) == SizeofError
def test_fixedsized() -> None:
d1 = FixedSized(10, Byte)
@ -1213,7 +1249,7 @@ def test_checksum() -> None:
def test_checksum_nonbytes_issue_323() -> None:
d = Struct(
"vals" / Byte[2],
"checksum" / Checksum(Byte, lambda vals: sum(vals) & 0xFF, this.vals),
"checksum" / Checksum(Byte, lambda vals: int(sum(vals)) & 0xFF, this.vals),
)
assert d.parse(b"\x00\x00\x00") == Container(vals=[0, 0], checksum=0)
assert raises(d.parse, b"\x00\x00\x01") == ChecksumError
@ -1329,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
@ -1544,7 +1679,7 @@ def test_operators() -> None:
assert d.docs == "description"
d = "description" * Byte
assert d.docs == "description"
"""
_ = """
description
""" * \
Byte
@ -1686,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"
@ -1704,7 +1841,7 @@ def test_from_issue_324() -> None:
)),
"checksum" / Checksum(
Byte,
lambda data: sum(data) & 0xFF,
lambda data: int(sum(data)) & 0xFF,
this.vals.data
),
)
@ -1795,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,
@ -1811,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(
@ -2022,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)

View file

@ -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):
@ -384,6 +399,69 @@ def test_tenum_no_enumbase() -> None:
assert raises(lambda: cst.TEnum(cs.Byte, cls)) == TypeError
def test_tenum_asdict() -> None:
# see: https://github.com/timrid/construct-typing/issues/21
import dataclasses
import construct_typed as cst
class TestEnum(cst.EnumBase):
one = 1
two = 2
four = 4
eight = 8
@dataclasses.dataclass
class SomeDataclass:
a: TestEnum
dc = SomeDataclass(TestEnum.one)
dc_dict = dataclasses.asdict(dc)
assert dc_dict["a"] == dc.a
assert dc_dict["a"] is dc.a
dc = SomeDataclass(TestEnum(5))
dc_dict = dataclasses.asdict(dc)
assert dc_dict["a"] == dc.a
assert dc_dict["a"] is dc.a
def test_tenum_docstring() -> None:
class TestEnum(cst.EnumBase):
"""
This is an test enum.
"""
Value_WithDoc = cst.EnumValue(0, doc="an enum with a documentation")
Value_WithMultilineDoc = cst.EnumValue(
1,
"""
An enum with a multiline documentation...
...next line...
""",
)
Value_NoDoc = cst.EnumValue(2)
Value_NoDoc2 = 3
assert TestEnum.__doc__ is not None
assert textwrap.dedent(TestEnum.__doc__) == textwrap.dedent(
"""
This is an test enum.
"""
)
assert TestEnum.Value_WithDoc.__doc__ == "an enum with a documentation"
assert (
TestEnum.Value_WithMultilineDoc.__doc__
== """
An enum with a multiline documentation...
...next line...
"""
)
assert TestEnum.Value_NoDoc.__doc__ == ""
assert TestEnum.Value_NoDoc2.__doc__ == ""
assert TestEnum(5).__doc__ == "missing value"
def test_dataclass_struct_wrong_enumbase() -> None:
class E1(cst.EnumBase):
a = 1
@ -434,3 +512,65 @@ def test_tenum_flags() -> None:
assert d.build(TestEnum(255)) == b"\xff"
assert d.build(TestEnum.eight) == b"\x08"
assert raises(d.build, 2) == TypeError
def test_tenum_flags_asdict() -> None:
import dataclasses
import construct_typed as cst
class TestEnum(cst.FlagsEnumBase):
one = 1
two = 2
four = 4
eight = 8
@dataclasses.dataclass
class SomeDataclass:
a: TestEnum
dc = SomeDataclass(TestEnum.one)
dc_dict = dataclasses.asdict(dc)
assert dc_dict["a"] == dc.a
assert dc_dict["a"] is dc.a
dc = SomeDataclass(TestEnum(5))
dc_dict = dataclasses.asdict(dc)
assert dc_dict["a"] == dc.a
assert dc_dict["a"] is dc.a
def test_tenum_flags_docstring() -> None:
class TestEnum(cst.FlagsEnumBase):
"""
This is an test flags enum.
"""
Value_WithDoc = cst.EnumValue(0, doc="an enum with a documentation")
Value_WithMultilineDoc = cst.EnumValue(
1,
"""
An enum with a multiline documentation...
...next line...
""",
)
Value_NoDoc = cst.EnumValue(2)
Value_NoDoc2 = 4
assert TestEnum.__doc__ is not None
assert textwrap.dedent(TestEnum.__doc__) == textwrap.dedent(
"""
This is an test flags enum.
"""
)
assert TestEnum.Value_WithDoc.__doc__ == "an enum with a documentation"
assert (
TestEnum.Value_WithMultilineDoc.__doc__
== """
An enum with a multiline documentation...
...next line...
"""
)
assert TestEnum.Value_NoDoc.__doc__ == ""
assert TestEnum.Value_NoDoc2.__doc__ == ""
assert TestEnum(8).__doc__ == "missing value"