![Build Status](https://github.com/donadigo/bytefield/workflows/Build/badge.svg)
# ByteField
A Python library for parsing/manipulating binary data with easily accessible Python properties inspired by Django. The library is still in development. ByteField supports:
* Variable length fields
* Nested structures
* Parsing only accessed fields
## Quick example
ByteField allows to define binary data layout declaratively which then maps to underlying bytes:
```py
from bytefield import *
class Header(ByteStruct):
magic = StringField(length=5)
length = IntegerField()
array = ArrayField(shape=None, elem_field_type=IntegerField)
floating = FloatField()
header = Header(magic='bytes', floating=3.14)
header.length = 3
header.array = list(range(1, header.length + 1))
print(header.data)
```
### Output:
```py
bytearray(b'bytes\x03\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\xc3\xf5H@')`
```
## Example: parse a JPEG header
You can embed other structure declarations inside structures:
```py
from bytefield import *
class RGB(ByteStruct):
r = IntegerField(signed=False, size=1)
g = IntegerField(signed=False, size=1)
b = IntegerField(signed=False, size=1)
class Marker(ByteStruct):
marker = IntegerField(size=2, signed=False)
length = IntegerField(size=2, signed=False)
identifier = StringField(length=5, encoding='ascii')
version = IntegerField(size=2, signed=False)
density = IntegerField(size=1, signed=False)
x_density = IntegerField(size=2, signed=False)
y_density = IntegerField(size=2, signed=False)
x_thumbnail = IntegerField(size=2, signed=False)
y_thumbnail = IntegerField(size=2, signed=False)
thumb_data = ArrayField(shape=None, elem_field_type=RGB)
class JPEGHeader(ByteStruct):
soi = IntegerField(size=2, signed=False)
marker = StructField(Marker)
with open('image.jpg', 'rb') as f:
# Parse the JPEG header
header = JPEGHeader(f.read())
# Resize the thumbnail data
header.marker.resize(
Marker.thumb_data_field, header.marker.x_thumbnail * header.marker.y_thumbnail
)
# Display the thumbnail
display_thumbnail(header.marker.thumb_data)
```
## Writing custom struct logic
You can create high-level structures which define their own behavior depending on the data contained within the struct:
```py
from bytefield import *
class DynamicFloatArray(ByteStruct):
length = IntegerField(signed=False)
array_data = ArrayField(None, FloatField)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# When instantiated, resize the array according to its length
self.resize(DynamicFloatArray.array_data_field, self.length)
data = bytearray(b'\x03\x00\x00\x00\x00\x00\x80?\x00\x00\x00@\x00\x00@@')
print(DynamicFloatArray(data))
```
### Output:
```py
[DynamicFloatArray object at 0x1c88e709e50]
length (int): 3
array_data (ndarray): [1.0 2.0 3.0]
```
## Variable fields
Bytefield supports fields with unknown type/size:
```py
from bytefiel import *
TYPE_INTEGER = 0
TYPE_FLOAT = 1
TYPE_STRING = 2
class DynamicString(ByteStruct):
length = IntegerField(signed=False)
str = StringField(None)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.resize(DynamicString.str_field, self.length)
class Content(ByteStruct):
content_type = IntegerField(signed=False, size=2)
content_data = VariableField() # a variable field that will be resized when parsing the struct
def __init__(self, data: bytearray = None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
resize_bytes = not bool(data)
if self.content_type == TYPE_INTEGER:
self.resize(Content.content_data_field, IntegerField(), resize_bytes=resize_bytes)
elif self.content_type == TYPE_FLOAT:
self.resize(Content.content_data_field, FloatField(), resize_bytes=resize_bytes)
elif self.content_type == TYPE_STRING:
self.resize(Content.content_data_field, StructField(DynamicString), resize_bytes=resize_bytes)
write = Content()
write.content_type = TYPE_STRING
write.resize(Content.content_data_field, StructField(DynamicString), resize_bytes=True)
write.content_data.str = 'content'
write.content_data.length = len(write.content_data.str)
read = Content(write.data)
print(f'{write.data} is parsed to:\n{read}')
```
### Output
```
bytearray(b'\x02\x00\x07\x00\x00\x00content') is parsed to:
[Content object at 0x1c1846888b0]
content_type (int): 2
content_data (DynamicString):
length (int): 7
str (str): content
```
Raw data
{
"_id": null,
"home_page": "https://github.com/donadigo/bytefield",
"name": "bytefield",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "struct,bytefield,bytearray",
"author": "donadigo",
"author_email": "donadigo@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/11/26/0df7b59f208d76f610589b634c2255555862d37649bb788af856360954c1/bytefield-1.0.2.tar.gz",
"platform": null,
"description": "![Build Status](https://github.com/donadigo/bytefield/workflows/Build/badge.svg)\r\n# ByteField\r\nA Python library for parsing/manipulating binary data with easily accessible Python properties inspired by Django. The library is still in development. ByteField supports:\r\n* Variable length fields\r\n* Nested structures\r\n* Parsing only accessed fields\r\n\r\n## Quick example\r\nByteField allows to define binary data layout declaratively which then maps to underlying bytes:\r\n```py\r\nfrom bytefield import *\r\n\r\nclass Header(ByteStruct):\r\n magic = StringField(length=5)\r\n length = IntegerField()\r\n array = ArrayField(shape=None, elem_field_type=IntegerField)\r\n floating = FloatField()\r\n\r\nheader = Header(magic='bytes', floating=3.14)\r\nheader.length = 3\r\nheader.array = list(range(1, header.length + 1))\r\nprint(header.data)\r\n```\r\n\r\n### Output:\r\n```py\r\nbytearray(b'bytes\\x03\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\xc3\\xf5H@')`\r\n```\r\n\r\n## Example: parse a JPEG header\r\nYou can embed other structure declarations inside structures:\r\n```py\r\nfrom bytefield import *\r\n\r\nclass RGB(ByteStruct):\r\n r = IntegerField(signed=False, size=1)\r\n g = IntegerField(signed=False, size=1)\r\n b = IntegerField(signed=False, size=1)\r\n\r\nclass Marker(ByteStruct):\r\n marker = IntegerField(size=2, signed=False)\r\n length = IntegerField(size=2, signed=False)\r\n identifier = StringField(length=5, encoding='ascii')\r\n version = IntegerField(size=2, signed=False)\r\n density = IntegerField(size=1, signed=False)\r\n x_density = IntegerField(size=2, signed=False)\r\n y_density = IntegerField(size=2, signed=False)\r\n x_thumbnail = IntegerField(size=2, signed=False)\r\n y_thumbnail = IntegerField(size=2, signed=False)\r\n thumb_data = ArrayField(shape=None, elem_field_type=RGB)\r\n\r\nclass JPEGHeader(ByteStruct):\r\n soi = IntegerField(size=2, signed=False)\r\n marker = StructField(Marker)\r\n\r\nwith open('image.jpg', 'rb') as f:\r\n # Parse the JPEG header\r\n header = JPEGHeader(f.read())\r\n\r\n # Resize the thumbnail data\r\n header.marker.resize(\r\n Marker.thumb_data_field, header.marker.x_thumbnail * header.marker.y_thumbnail\r\n )\r\n\r\n # Display the thumbnail\r\n display_thumbnail(header.marker.thumb_data)\r\n```\r\n\r\n## Writing custom struct logic\r\nYou can create high-level structures which define their own behavior depending on the data contained within the struct:\r\n```py\r\nfrom bytefield import *\r\n\r\nclass DynamicFloatArray(ByteStruct):\r\n length = IntegerField(signed=False)\r\n array_data = ArrayField(None, FloatField)\r\n\r\n def __init__(self, *args, **kwargs):\r\n super().__init__(*args, **kwargs)\r\n # When instantiated, resize the array according to its length\r\n self.resize(DynamicFloatArray.array_data_field, self.length)\r\n\r\ndata = bytearray(b'\\x03\\x00\\x00\\x00\\x00\\x00\\x80?\\x00\\x00\\x00@\\x00\\x00@@')\r\nprint(DynamicFloatArray(data))\r\n```\r\n\r\n### Output:\r\n```py\r\n[DynamicFloatArray object at 0x1c88e709e50]\r\nlength (int): 3\r\narray_data (ndarray): [1.0 2.0 3.0]\r\n```\r\n\r\n## Variable fields\r\nBytefield supports fields with unknown type/size:\r\n```py\r\nfrom bytefiel import *\r\n\r\nTYPE_INTEGER = 0\r\nTYPE_FLOAT = 1\r\nTYPE_STRING = 2\r\n\r\nclass DynamicString(ByteStruct):\r\n length = IntegerField(signed=False)\r\n str = StringField(None)\r\n\r\n def __init__(self, *args, **kwargs):\r\n super().__init__(*args, **kwargs)\r\n self.resize(DynamicString.str_field, self.length)\r\n\r\nclass Content(ByteStruct):\r\n content_type = IntegerField(signed=False, size=2)\r\n content_data = VariableField() # a variable field that will be resized when parsing the struct\r\n\r\n def __init__(self, data: bytearray = None, *args, **kwargs):\r\n super().__init__(data, *args, **kwargs)\r\n resize_bytes = not bool(data)\r\n if self.content_type == TYPE_INTEGER:\r\n self.resize(Content.content_data_field, IntegerField(), resize_bytes=resize_bytes)\r\n elif self.content_type == TYPE_FLOAT:\r\n self.resize(Content.content_data_field, FloatField(), resize_bytes=resize_bytes)\r\n elif self.content_type == TYPE_STRING:\r\n self.resize(Content.content_data_field, StructField(DynamicString), resize_bytes=resize_bytes)\r\n\r\nwrite = Content()\r\nwrite.content_type = TYPE_STRING\r\nwrite.resize(Content.content_data_field, StructField(DynamicString), resize_bytes=True)\r\nwrite.content_data.str = 'content'\r\nwrite.content_data.length = len(write.content_data.str)\r\n\r\nread = Content(write.data)\r\nprint(f'{write.data} is parsed to:\\n{read}')\r\n```\r\n\r\n### Output\r\n```\r\nbytearray(b'\\x02\\x00\\x07\\x00\\x00\\x00content') is parsed to:\r\n[Content object at 0x1c1846888b0]\r\ncontent_type (int): 2\r\ncontent_data (DynamicString):\r\n length (int): 7\r\n str (str): content\r\n```\r\n\r\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Parse binary data using declarative field layout and native Python properties",
"version": "1.0.2",
"split_keywords": [
"struct",
"bytefield",
"bytearray"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "11260df7b59f208d76f610589b634c2255555862d37649bb788af856360954c1",
"md5": "ee9c00dd76834f374e5e68b48f52e536",
"sha256": "eb5f8c8d51de54d8764041531521e072b4e6208acd3504f1f2260c0d6377599e"
},
"downloads": -1,
"filename": "bytefield-1.0.2.tar.gz",
"has_sig": false,
"md5_digest": "ee9c00dd76834f374e5e68b48f52e536",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 17067,
"upload_time": "2023-01-29T20:31:01",
"upload_time_iso_8601": "2023-01-29T20:31:01.353739Z",
"url": "https://files.pythonhosted.org/packages/11/26/0df7b59f208d76f610589b634c2255555862d37649bb788af856360954c1/bytefield-1.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-01-29 20:31:01",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "donadigo",
"github_project": "bytefield",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "bytefield"
}