# 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"
}