construct-dataclasses


Nameconstruct-dataclasses JSON
Version 1.1.10 PyPI version JSON
download
home_pagehttps://github.com/MatrixEditor/construct-dataclasses
Summaryenhancement for the python package 'construct' that adds support for dataclasses.
upload_time2023-10-12 10:52:08
maintainer
docs_urlNone
authorMatrixEditor
requires_python>=3.8
licenseGNU GPLv3
keywords construct kaitai declarative data structure struct binary symmetric parser builder parsing building pack unpack packer unpacker bitstring bytestring dataclasses
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # construct-dataclasses

[![python](https://img.shields.io/badge/python-3.7+-blue.svg?logo=python&labelColor=grey)](https://www.python.org/downloads/)
![Codestyle](https://img.shields.io:/static/v1?label=Codestyle&message=black&color=black)
![License](https://img.shields.io:/static/v1?label=License&message=GNU+GPLv3&color=blue)
[![PyPI](https://img.shields.io/pypi/v/construct-dataclasses)](https://pypi.org/project/construct-dataclasses/)

This small repository is an enhancement of the python package [*construct*](https://pypi.org/project/construct/), which is a powerful tool to declare symmetrical parsers and builders for binary data. This project combines construct with python's dataclasses with support for nested structs.

## Installation

You can install the package via pip or just copy the python file (*\_\_init\_\_.py*) as it is only one.

```bash
pip install construct-dataclasses
```

## Usage

More usage examples are placed in the [*examples/*](/examples/) directory.

### Define dataclasses

Before we can start declaring fields on a dataclass, the class itself has to be created. Currently, there are two ways on how to create
a dataclass usable by this package.

1. Use the standard `@dataclass` decorator and create the parser instance afterwards (**recommended for type checking**):

    ```python
    from construct_dataclasses import DataclassStruct

    @dataclasses.dataclass
    class Foo: ...

    # Create the parser manually
    parser = DataclassStruct(Foo)
    instance = parser.parse(...)
    ```

2. Use the `@dataclass_struct` decorator to define a new dataclass and automatically create a parser instance that will be assigned as a class attribute:

    ```python
    from construct_dataclasses import dataclass_struct

    @dataclass_struct
    class Foo: ...

    # Use the class-parser to parse
    instance = Foo.parser.parse(...)
    # or to build
    data = Foo.parser.build(instance)
    ```

> Hint: Use `@container` to mimic a construct container instance if needed. That may be the case if you have to access
> an already parsed object of a custom type:
> ```python
> @container
> @dataclasses.dataclass
> class ImageHeader:
>   length: int = csfield(Int32ub)
>
> @dataclasses.dataclass
> class Image:
>   header: ImageHeader = csfield(ImageHeader)
>   data: bytes = csfield(Bytes(this.header.length))
> ```
>  The access to `header.length` would throw an exception without the container annotation.

### Define fields

This module defines a new way how to declare fields of a dataclass. In order to combine the python package construct with python's dataclasses module, this project introduces the following four methods:

- `csfield`: Default definition of a field using a subcon or other dataclass

    ```python
    @dataclass_struct
    class ImageHeader:
        signature: bytes = csfield(cs.Const(b"BMP"))
        num_entries: int = csfield(cs.Int32ul)
    ```

- `subcsfield`: Definition of nested constructs that are contained in list-like structures.

    ```python
    @dataclass_struct
    class Image:
        header: ImageHeader = csfield(ImageHeader) # dataclass reference
        width: int          = csfield(cs.Int8ub)
        height: int         = csfield(cs.Int8ub)
        # Note that we have to convert our dataclass into a struct using
        # the method "to_struct(...)"
        pixels: list[Pixel] = subcsfield(Pixel, cs.Array(this.width * this.height, to_struct(Pixel)))
    ```

- `tfield`: a simple typed field that tries to return an instance of the given model class. **Use `subcsfield` for dataclass models, `csenum`for simple enum fields and `tfield` for enum types in list fields**.

    ```python
    @dataclass_struct
    class ImageHeader:
        orientations: list[Orientation] = tfield(Orientation, cs.Enum(cs.Int8ul, Orientation))
    ```

- `csenum`: shortcut for simple enum fields

    ```python
    @dataclass_struct
    class ImageHeader:
        orientations: Orientation = csenum(Orientation, cs.Int8ul)
    ```

### Convert dataclasses

By default, all conversion is done automatically if you don't use instances of `SubContruct` classes in your field definitions. If you have to define a subcon that needs a nested subcon, like `Array` or `RepeatUntil` and you would like to parse a dataclass struct, it is required to convert the defined dataclass into a struct.

- `to_struct`: This method converts all fields defined in a dataclass into a single `Struct` or `AlignedStruct` instance.

    ```python
    @dataclass_struct
    class Pixel:
        data: int = csfield(cs.Int8ub)

    pixel_struct: construct.Struct = to_struct(Pixel)
    ```
- `to_object`: In order to use data returned by `Struct.parse`, this method can be used to apply this data and create a dataclass object from it.

    ```python
    data = pixel_struct.parse(b"...")
    pixel = to_object(data, Pixel)
    ```

The complete example is shown below:

```python
# Example modifed from here: https://github.com/timrid/construct-typing/
import dataclasses
import enum
import construct as cs

from construct_dataclasses import dataclass_struct, csfield, to_struct, subcsfield, csenum

class Orientation(enum.IntEnum):
    NONE = 0
    HORIZONTAL = 1
    VERTICAL = 2

@dataclass_struct
class ImageHeader:
    signature: bytes         = csfield(cs.Const(b"BMP"))
    orientation: Orientation = csenum(Orientation, cs.Int8ub)

@dataclass_struct
class Pixel:
    data: int = csfield(cs.Int8ub)

@dataclass_struct
class Image:
    header: ImageHeader = csfield(ImageHeader)
    width: int          = csfield(cs.Int8ub)
    height: int         = csfield(cs.Int8ub)
    pixels: list[Pixel] = subcsfield(Pixel, cs.Array(this.width * this.height, to_struct(Pixel)))

obj = Image(
    header=ImageHeader(
        orientation=Orientation.VERTICAL
    ),
    width=3,
    height=2,
    pixels=[Pixel(1), Pixel(2), Pixel(3), Pixel(4), Pixel(5), Pixel(6)]
)

print(Image.parser.build(obj))
print(Image.parser.parse(b"BMP\x02\x03\x02\x01\x02\x03\x04\x05\06"))
```

The expected output would be:

    b'BMP\x02\x03\x02\x01\x02\x03\x04\x05\x06'
    Image(
        header=ImageHeader(signature=b'BMP', orientation=<Orientation.VERTICAL: 2>),
        width=3, height=2,
        pixels=[Pixel(data=1), Pixel(data=2), Pixel(data=3), Pixel(data=4), Pixel(data=5), Pixel(data=6)]
    )

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/MatrixEditor/construct-dataclasses",
    "name": "construct-dataclasses",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "construct,kaitai,declarative,data structure,struct,binary,symmetric,parser,builder,parsing,building,pack,unpack,packer,unpacker,bitstring,bytestring,dataclasses",
    "author": "MatrixEditor",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/95/77/c92e415abb80daf7ddf53fb16638cfdcc68b71446a30db844e8f6783ccaf/construct-dataclasses-1.1.10.tar.gz",
    "platform": "POSIX",
    "description": "# construct-dataclasses\r\n\r\n[![python](https://img.shields.io/badge/python-3.7+-blue.svg?logo=python&labelColor=grey)](https://www.python.org/downloads/)\r\n![Codestyle](https://img.shields.io:/static/v1?label=Codestyle&message=black&color=black)\r\n![License](https://img.shields.io:/static/v1?label=License&message=GNU+GPLv3&color=blue)\r\n[![PyPI](https://img.shields.io/pypi/v/construct-dataclasses)](https://pypi.org/project/construct-dataclasses/)\r\n\r\nThis small repository is an enhancement of the python package [*construct*](https://pypi.org/project/construct/), which is a powerful tool to declare symmetrical parsers and builders for binary data. This project combines construct with python's dataclasses with support for nested structs.\r\n\r\n## Installation\r\n\r\nYou can install the package via pip or just copy the python file (*\\_\\_init\\_\\_.py*) as it is only one.\r\n\r\n```bash\r\npip install construct-dataclasses\r\n```\r\n\r\n## Usage\r\n\r\nMore usage examples are placed in the [*examples/*](/examples/) directory.\r\n\r\n### Define dataclasses\r\n\r\nBefore we can start declaring fields on a dataclass, the class itself has to be created. Currently, there are two ways on how to create\r\na dataclass usable by this package.\r\n\r\n1. Use the standard `@dataclass` decorator and create the parser instance afterwards (**recommended for type checking**):\r\n\r\n    ```python\r\n    from construct_dataclasses import DataclassStruct\r\n\r\n    @dataclasses.dataclass\r\n    class Foo: ...\r\n\r\n    # Create the parser manually\r\n    parser = DataclassStruct(Foo)\r\n    instance = parser.parse(...)\r\n    ```\r\n\r\n2. Use the `@dataclass_struct` decorator to define a new dataclass and automatically create a parser instance that will be assigned as a class attribute:\r\n\r\n    ```python\r\n    from construct_dataclasses import dataclass_struct\r\n\r\n    @dataclass_struct\r\n    class Foo: ...\r\n\r\n    # Use the class-parser to parse\r\n    instance = Foo.parser.parse(...)\r\n    # or to build\r\n    data = Foo.parser.build(instance)\r\n    ```\r\n\r\n> Hint: Use `@container` to mimic a construct container instance if needed. That may be the case if you have to access\r\n> an already parsed object of a custom type:\r\n> ```python\r\n> @container\r\n> @dataclasses.dataclass\r\n> class ImageHeader:\r\n>   length: int = csfield(Int32ub)\r\n>\r\n> @dataclasses.dataclass\r\n> class Image:\r\n>   header: ImageHeader = csfield(ImageHeader)\r\n>   data: bytes = csfield(Bytes(this.header.length))\r\n> ```\r\n>  The access to `header.length` would throw an exception without the container annotation.\r\n\r\n### Define fields\r\n\r\nThis module defines a new way how to declare fields of a dataclass. In order to combine the python package construct with python's dataclasses module, this project introduces the following four methods:\r\n\r\n- `csfield`: Default definition of a field using a subcon or other dataclass\r\n\r\n    ```python\r\n    @dataclass_struct\r\n    class ImageHeader:\r\n        signature: bytes = csfield(cs.Const(b\"BMP\"))\r\n        num_entries: int = csfield(cs.Int32ul)\r\n    ```\r\n\r\n- `subcsfield`: Definition of nested constructs that are contained in list-like structures.\r\n\r\n    ```python\r\n    @dataclass_struct\r\n    class Image:\r\n        header: ImageHeader = csfield(ImageHeader) # dataclass reference\r\n        width: int          = csfield(cs.Int8ub)\r\n        height: int         = csfield(cs.Int8ub)\r\n        # Note that we have to convert our dataclass into a struct using\r\n        # the method \"to_struct(...)\"\r\n        pixels: list[Pixel] = subcsfield(Pixel, cs.Array(this.width * this.height, to_struct(Pixel)))\r\n    ```\r\n\r\n- `tfield`: a simple typed field that tries to return an instance of the given model class. **Use `subcsfield` for dataclass models, `csenum`for simple enum fields and `tfield` for enum types in list fields**.\r\n\r\n    ```python\r\n    @dataclass_struct\r\n    class ImageHeader:\r\n        orientations: list[Orientation] = tfield(Orientation, cs.Enum(cs.Int8ul, Orientation))\r\n    ```\r\n\r\n- `csenum`: shortcut for simple enum fields\r\n\r\n    ```python\r\n    @dataclass_struct\r\n    class ImageHeader:\r\n        orientations: Orientation = csenum(Orientation, cs.Int8ul)\r\n    ```\r\n\r\n### Convert dataclasses\r\n\r\nBy default, all conversion is done automatically if you don't use instances of `SubContruct` classes in your field definitions. If you have to define a subcon that needs a nested subcon, like `Array` or `RepeatUntil` and you would like to parse a dataclass struct, it is required to convert the defined dataclass into a struct.\r\n\r\n- `to_struct`: This method converts all fields defined in a dataclass into a single `Struct` or `AlignedStruct` instance.\r\n\r\n    ```python\r\n    @dataclass_struct\r\n    class Pixel:\r\n        data: int = csfield(cs.Int8ub)\r\n\r\n    pixel_struct: construct.Struct = to_struct(Pixel)\r\n    ```\r\n- `to_object`: In order to use data returned by `Struct.parse`, this method can be used to apply this data and create a dataclass object from it.\r\n\r\n    ```python\r\n    data = pixel_struct.parse(b\"...\")\r\n    pixel = to_object(data, Pixel)\r\n    ```\r\n\r\nThe complete example is shown below:\r\n\r\n```python\r\n# Example modifed from here: https://github.com/timrid/construct-typing/\r\nimport dataclasses\r\nimport enum\r\nimport construct as cs\r\n\r\nfrom construct_dataclasses import dataclass_struct, csfield, to_struct, subcsfield, csenum\r\n\r\nclass Orientation(enum.IntEnum):\r\n    NONE = 0\r\n    HORIZONTAL = 1\r\n    VERTICAL = 2\r\n\r\n@dataclass_struct\r\nclass ImageHeader:\r\n    signature: bytes         = csfield(cs.Const(b\"BMP\"))\r\n    orientation: Orientation = csenum(Orientation, cs.Int8ub)\r\n\r\n@dataclass_struct\r\nclass Pixel:\r\n    data: int = csfield(cs.Int8ub)\r\n\r\n@dataclass_struct\r\nclass Image:\r\n    header: ImageHeader = csfield(ImageHeader)\r\n    width: int          = csfield(cs.Int8ub)\r\n    height: int         = csfield(cs.Int8ub)\r\n    pixels: list[Pixel] = subcsfield(Pixel, cs.Array(this.width * this.height, to_struct(Pixel)))\r\n\r\nobj = Image(\r\n    header=ImageHeader(\r\n        orientation=Orientation.VERTICAL\r\n    ),\r\n    width=3,\r\n    height=2,\r\n    pixels=[Pixel(1), Pixel(2), Pixel(3), Pixel(4), Pixel(5), Pixel(6)]\r\n)\r\n\r\nprint(Image.parser.build(obj))\r\nprint(Image.parser.parse(b\"BMP\\x02\\x03\\x02\\x01\\x02\\x03\\x04\\x05\\06\"))\r\n```\r\n\r\nThe expected output would be:\r\n\r\n    b'BMP\\x02\\x03\\x02\\x01\\x02\\x03\\x04\\x05\\x06'\r\n    Image(\r\n        header=ImageHeader(signature=b'BMP', orientation=<Orientation.VERTICAL: 2>),\r\n        width=3, height=2,\r\n        pixels=[Pixel(data=1), Pixel(data=2), Pixel(data=3), Pixel(data=4), Pixel(data=5), Pixel(data=6)]\r\n    )\r\n",
    "bugtrack_url": null,
    "license": "GNU GPLv3",
    "summary": "enhancement for the python package 'construct' that adds support for dataclasses.",
    "version": "1.1.10",
    "project_urls": {
        "Homepage": "https://github.com/MatrixEditor/construct-dataclasses"
    },
    "split_keywords": [
        "construct",
        "kaitai",
        "declarative",
        "data structure",
        "struct",
        "binary",
        "symmetric",
        "parser",
        "builder",
        "parsing",
        "building",
        "pack",
        "unpack",
        "packer",
        "unpacker",
        "bitstring",
        "bytestring",
        "dataclasses"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cca25be00f122a0f00a116a59dc757a19eab099ec7f777ecca5651f5386c4267",
                "md5": "52ceb4f27805824b32f4ec752027d53f",
                "sha256": "cf00d1edc015342a299690d71107a4837133e9a66b1dad0082c18b54b86b1d52"
            },
            "downloads": -1,
            "filename": "construct_dataclasses-1.1.10-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "52ceb4f27805824b32f4ec752027d53f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 22311,
            "upload_time": "2023-10-12T10:52:07",
            "upload_time_iso_8601": "2023-10-12T10:52:07.267687Z",
            "url": "https://files.pythonhosted.org/packages/cc/a2/5be00f122a0f00a116a59dc757a19eab099ec7f777ecca5651f5386c4267/construct_dataclasses-1.1.10-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9577c92e415abb80daf7ddf53fb16638cfdcc68b71446a30db844e8f6783ccaf",
                "md5": "d218407d60df5ac7a29dfba85056f5f0",
                "sha256": "31070c3bcb25c7fb128b3a7f13592bbc699082b5977518eacf38066aeb352b9f"
            },
            "downloads": -1,
            "filename": "construct-dataclasses-1.1.10.tar.gz",
            "has_sig": false,
            "md5_digest": "d218407d60df5ac7a29dfba85056f5f0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 23013,
            "upload_time": "2023-10-12T10:52:08",
            "upload_time_iso_8601": "2023-10-12T10:52:08.714777Z",
            "url": "https://files.pythonhosted.org/packages/95/77/c92e415abb80daf7ddf53fb16638cfdcc68b71446a30db844e8f6783ccaf/construct-dataclasses-1.1.10.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-10-12 10:52:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "MatrixEditor",
    "github_project": "construct-dataclasses",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "construct-dataclasses"
}
        
Elapsed time: 2.02643s