Name | databytes JSON |
Version |
1.2.1
JSON |
| download |
home_page | None |
Summary | A class based binary structure to serialize/deserialize binary data (using python struct under the hood). |
upload_time | 2024-11-30 01:13:01 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.10 |
license | Copyright (c) 2024-present StΓ©phane "Twidi" Angel. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
keywords |
binary
struct
data-structures
shared-memory
buffer
serialization
deserialization
memory-mapping
bytes
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# DataBytes
[](https://pypi.org/project/databytes/)
[](https://github.com/twidi/databytes/actions/workflows/ci.yml)
A Python library providing a class-based approach to serialize and deserialize binary data, built on top of Python's `struct` module. DataBytes makes it easy to work with binary structures, shared memory, and memory-mapped files in a type-safe and object-oriented way.
## Features
- Class-based binary structure definitions
- Type-safe field declarations
- Support for nested structures and arrays
- Use standard python binary packing and unpacking via the `struct` package
- Support for Python 3.10+
## Installation
You can install `databytes` from [PyPI](https://pypi.org/project/databytes/):
```bash
pip install databytes
```
## Quick Start
```python
from databytes import BinaryStruct
from databytes import types as t
class Point(BinaryStruct):
name: t.string[12] # fixed-length string of 12 chars
x: t.uint16
y: t.uint16
class Rectangle(BinaryStruct):
top_left: Point
bottom_right: Point
area: t.uint32
selected_points: Point[2] # array of 2 points
# Create a new structure with a buffer
assert Rectangle._nb_bytes == 68 # (12+4) for each point + 4 for area
# can be a bytes (read-only), bytearray, memoryview (like the `.buf` of a shared memory), mmap.mmap, array.array, ctypes.Array, numpy bytes array
buffer = bytearray(Rectangle._nb_bytes)
rect = Rectangle(buffer)
# Set values
rect.top_left.name = "Top Left"
rect.top_left.x = 0
rect.top_left.y = 0
rect.bottom_right.name = "Bottom Right"
rect.bottom_right.x = 10
rect.bottom_right.y = 10
rect.area = 100
rect.selected_points[0].name = "Selected 1"
rect.selected_points[0].x = 1
rect.selected_points[0].y = 2
rect.selected_points[1].name = "Selected 2"
rect.selected_points[1].x = 3
rect.selected_points[1].y = 4
# buffer is updated
print(buffer)
# bytearray(b'Top Left\x00\x00\x00\x00\x00\x00\x00\x00Bottom Right\n\x00\n\x00d\x00\x00\x00Selected 1\x00\x00\x01\x00\x02\x00Selected 2\x00\x00\x03\x00\x04\x00')
# Load this buffer into another Rectangle instance
rect2 = Rectangle(buffer)
assert rect2.top_left.name == "Top Left"
assert rect2.top_left.x == 0
assert rect2.top_left.y == 0
assert rect2.bottom_right.name == "Bottom Right"
assert rect2.bottom_right.x == 10
assert rect2.bottom_right.y == 10
assert rect2.area == 100
assert rect2.selected_points[0].name == "Selected 1"
assert rect2.selected_points[0].x == 1
assert rect2.selected_points[0].y == 2
assert rect2.selected_points[1].name == "Selected 2"
assert rect2.selected_points[1].x == 3
assert rect2.selected_points[1].y == 4
```
## Key Concepts
- **Binary Structures**: Define your data structures using Python classes that inherit from `BinaryStruct`
- **Typing**: Field are declared using type hints
- **Memory Efficiency**: Direct memory access without unnecessary copies
- **Shared Memory**: Easy integration with multiprocessing using shared memory buffers
- **Extensibility**: Create custom field types by extending the base types
## Dependencies
- Python >= 3.10
- typing-extensions
- numpy (only used to create the `Buffer` type alias π )
Optional dependencies:
- rich (for enhanced display capabilities)
## Available types
### Basic Types
As some types have the same name as builtins, the best way to import them is to use `from databytes import types as t` and use the `t.` prefix:
```python
from databytes import BinaryStruct
from databytes import types as t
class Data(BinaryStruct):
field: t.uint16
```
- `uint8` / `ubyte` (1 byte, coalesce to python int)
- `int8` / `byte` (1 byte, coalesce to python int)
- `uint16` / `ushort` (2 bytes, coalesce to python int)
- `int16` / `short` (2 bytes, coalesce to python int)
- `uint32` (4 bytes, coalesce to python int)
- `int32` (4 bytes, coalesce to python int)
- `uint64` (8 bytes, coalesce to python int)
- `int64` (8 bytes, coalesce to python int)
- `float32` / `float` (4 bytes, coalesce to python float)
- `float64` / `double` (8 bytes, coalesce to python float)
- `char` (1 byte, coalesce to python bytes)
- `string` (fixed-length string, automatically padded with nulls, coalesce to python str)
### Custom Types
You can use as a field another struct:
```python
from databytes import BinaryStruct
from databytes import types as t
class Point(BinaryStruct):
x: t.uint16
y: t.uint16
class Rectangle(BinaryStruct):
top_left: Point
bottom_right: Point
buffer = bytearray(Rectangle._nb_bytes)
rect = Rectangle(buffer)
rect.top_left.x = 0
rect.top_left.y = 0
rect.bottom_right.x = 10
rect.bottom_right.y = 10
```
*WARNING*: It's actually not possible to set the values of a sub-struct directly without using the attributes. It's due to the fact that the sub-structs instances are tied to their parent buffer, with the correct offset.
```python
point_buffer = bytearray(Point._nb_bytes)
point = Point(point_buffer)
point.x = 5
point.y = 5
rect.bottom_right = point
# when reading the buffer on another instance, we'll have the original values
rect2 = Rectangle(buffer)
assert rect2.bottom_right.x == 10
assert rect2.bottom_right.y == 10
```
### Arrays
Basic and custom types can be used in arrays of any dimension:
```python
from databytes import BinaryStruct
from databytes import types as t
class Point(BinaryStruct):
x: t.uint16
y: t.uint16
class Rectangle(BinaryStruct):
corners: Point[2] # array of 2 points
matrix_2dim: Point[2, 3] # matrix of 3x2 points
matrix_3dim: Point[2, 3, 4] # matrix of 4x3x2 points
```
*WARNING*: The dimensions are defined in reverse order: `Point[2, 3, 4]` is a list of 4 lists of 3 lists of 2 points
*WARNING*: It's actually not possible to set the arrays instances directly without using concrete slicing and attributes. It's due to the fact that the sub-structs instances are tied to their parent buffer, with the correct offset.
```python
class Point(BinaryStruct):
x: t.uint16
y: t.uint16
class Rectangle(BinaryStruct):
corners: Point[2] # array of 2 points
point1_buffer = bytearray(Point._nb_bytes)
point2_buffer = bytearray(Point._nb_bytes)
point1 = Point(point1_buffer)
point1.x = 5
point1.y = 5
point2 = Point(point2_buffer)
point2.x = 10
point2.y = 10
buffer = bytearray(Rectangle._nb_bytes)
rect = Rectangle(buffer)
# Won't work
rect.corners[0] = point1
rect.corners[1] = point2
# Won't work either
rect.corners = [point1, point2]
# Will work
rect.corners[0].x = 5 # you can use `= point1.x`
rect.corners[0].y = 5
rect.corners[1].x = 10
rect.corners[1].y = 10
```
#### Special case for strings.
By default a string is a string of 1 character. To defined the max length, define the length as the first dimension:
```python
from databytes import BinaryStruct
from databytes import types as t
class Person(BinaryStruct):
name: t.string[10] # fixed-length string of 10 chars
```
When retrieving the value, you'll have a real string, not a list of characters. If the string is shorter than the allocated space, it will be null-padded in the buffer but retrieved as a real string without the nulls.
To define an array of strings, add more dimensions:
```python
from databytes import BinaryStruct
from databytes import types as t
class Person(BinaryStruct):
name: t.string[10] # fixed-length string of 10 chars
aliases: t.string[10, 2] # array of 2 strings of 10 chars
```
## API
### Key points
- `BinaryStruct` is the base class for all structures (`from databytes import BinaryStruct`)
- `types` module contains all available types (`from databytes import types as t`)
- a `BinaryStruct` instance is created taking a buffer as argument (`buffer = bytearray(Rectangle._nb_bytes); rect = Rectangle(buffer)`) and an optional offset for the start of the buffer to use
```python
from databytes import BinaryStruct
from databytes import types as t
class Data(BinaryStruct):
field: t.uint16
buffer = bytearray(1000)
# here the data will be read/written at offset 100 of the buffer (and will only use the bytes size defined by the fields, here 2 bytes)
data = Data(buffer, 100)
```
### Supported buffers
We defined the `Buffer` type alias like this:
```python
Buffer: TypeAlias = bytes | bytearray | memoryview | mmap.mmap | array.array | ctypes.Array | np.ndarray
```
Not that all buffers are not writeable (like `bytes`, immutable `memoryview`s, immutable numpy arrays).
If you have another buffer type that we should support, please tell us.
You can use any buffer as long as:
- it works with `pack_info` and `unpack_from` from the python `struct` module
- it has a `__len__` method (we use it to ensure at init time that the buffer is big enough)
(of course, if it's not in our `Buffer` type alias, mypy won't be happy and you'll have to handle it)
### BinaryStruct extra fields
To not pollute the namespace, extra fields are prefixed with `_`:
- `_nb_bytes` (class var): number of bytes used by the structure (including sub-structs)
- `_struct_format` (class var): the format, using the python `struct` module format, of the whole structure (just for information)
- `_endianness` (class var): the endianness of the structure (see below for more details)
- `_buffer` (instance var): the buffer used by the structure (the buffer passed to the constructor, will be the same for the main struct and all sub-structs)
- `_offset` (instance var): the offset of this specific structure in the buffer
You can define your own fields for your own purpose, as only the fields defined in `BinaryStruct` using the `types` module or sub structs inheriting from `BinaryStruct` are handled by the library:
```python
from databytes import BinaryStruct
from databytes import types as t
class Data(BinaryStruct):
field: t.uint16
my_field: str # will not be handled by the library
def __init__(self, my_field: str, buffer: Buffer, offset: int = 0) -> None:
super().__init__(buffer, offset)
self.my_field = my_field
```
### Endianness
The endianess of a struct can be defined in two ways:
- When defining the class by setting the `_endianness` class var:
```python
from databytes import BinaryStruct
from databytes import types as t, Endianness
class Data(BinaryStruct):
_endianness = Endianness.LITTLE
field: t.uint16
```
- When instantiating the struct with the `endianness` argument (in this case the value will override the class value for this instance):
```python
from databytes import BinaryStruct
from databytes import types as t, Endianness
class Data(BinaryStruct):
_endianness = Endianness.BIG # will be ignored so it's not necessary to add it
field: t.uint16
data = Data(bytearray(1000), endianness=Endianness.LITTLE)
```
`Endianness` is an enum defined in `databytes.types` with the following values:
- `Endianness.NATIVE` (default): the endianness of the system
- `Endianness.LITTLE`
- `Endianness.BIG`
- `Endianness.NETWORK` (alias for `Endianness.BIG`)
The default value is forced to `Endianness.NATIVE` and can be changed by setting the `DATABYTES_ENDIANNESS` environment variable (before the `databytes` library is loaded) to `LITTLE`, `BIG`, `NETWORK` (alias for `BIG`) or `NATIVE` (the actual default).
The `_endianness` of a sub-struct defined on its class will be ignored: all sub-struts automatically inherit the endianness of their parent struct.
```python
from databytes import BinaryStruct
from databytes import types as t, Endianness
class Child(BinaryStruct):
_endianness = Endianness.BIG
value: t.uint16
class Data1(BinaryStruct):
_endianness = Endianness.LITTLE
child: Child
class Data2(BinaryStruct):
child: Child
buffer = bytearray(2)
child_alone = Child(buffer)
child_alone.value = 2
assert buffer == b"\x00\x02" # child alone uses big endian from Child class endianness
data1 = Data1(buffer)
data1.child.value = 3
assert buffer == b"\x03\x00" # child uses little endian from Data1 class endianness
data2 = Data2(buffer, endianness=Endianness.LITTLE)
data2.child.value = 4
assert buffer == b"\x04\x00" # child uses little endian from Data2 instance endianness
```
### Pointing to another buffer
It's possible to use the same structure on different buffers:
```python
from databytes import BinaryStruct
from databytes import types as t
class Data(BinaryStruct):
field: t.uint16
buffer1 = bytearray(1000)
buffer2 = bytearray(1000)
buffer3 = bytearray(1000)
# will read/write the data at offset 100 of the buffer1
data = Data(buffer1, 100)
# will now read/write the data at offset 100 of the buffer2
data.attach_buffer(buffer2)
# will now read/write the data at offset 50 of the buffer3
data.attach_buffer(buffer3, 50)
```
### Freeing the buffer
When using shared memory for example, the buffer must be freed before freeing the shared memory. To do that, use the `free_buffer` method:
```python
from databytes import BinaryStruct
from databytes import types as t
class Data(BinaryStruct):
field: t.uint16
shm = SharedMemory(create=True, size=1000)
buffer = shm.buf
data: Data | None = None
try:
data = Data(buffer)
data.value = 2
data.text = "World"
# ...
finally:
if data is not None:
data.free_buffer()
shm.close()
shm.unlink()
```
Of course, here `data` cannot be used anymore unless you call `attach_buffer` on it with another buffer.
### Utils
#### Layout information
You can get the layout of a structure with the `get_layout_info` function from `databytes.utils`:
```python
from databytes import BinaryStruct
from databytes import types as t
from databytes.utils import get_layout_info
class Point(BinaryStruct):
x: t.uint16
y: t.uint16
class Rectangle(BinaryStruct):
corners: Point[2] # array of 2 points
get_layout_info(Rectangle)
```
Will give you:
```python
StructLayoutInfo(
name='Rectangle',
struct_class=<class '__main__.Rectangle'>,
endianness=<Endianness.NATIVE: '='>,
byte_order=<Endianness.LITTLE: '<'>,
struct_format='HHHH',
nb_bytes=8,
offset=0,
fields={
'corners': FieldLayoutInfo(
name='corners',
offset=0,
nb_bytes=8,
struct_format='HHHH',
nb_items=2,
dimensions=(2,),
python_type=<class '__main__.Point'>,
python_full_type=list[__main__.Point],
is_sub_struct=True,
sub_struct=None
)
}
)
```
Call it with `include_sub_structs_details=True` to get more details about the sub-structs, in the `sub_struct` field (recursively)
```python
StructLayoutInfo(
name='Rectangle',
struct_class=<class '__main__.Rectangle'>
endianness=<Endianness.NATIVE: '='>,
byte_order=<Endianness.LITTLE: '<'>,
struct_format='HHHH'
nb_bytes=8
offset=0
fields={
'corners': FieldLayoutInfo(
name='corners'
offset=0
nb_bytes=8
struct_format='HHHH'
nb_items=2
dimensions=(2,)
python_type=<class '__main__.Point'>
python_full_type=list[__main__.Point],
is_sub_struct=True,
sub_struct=StructLayoutInfo(
name='Point',
struct_class=<class '__main__.Point'>,
endianness=<Endianness.NATIVE: '='>,
byte_order=<Endianness.LITTLE: '<'>,
struct_format='HH'
nb_bytes=4
offset=0
fields={
'x': FieldLayoutInfo(
name='x'
offset=0
nb_bytes=2
struct_format='H',
nb_items=1,
dimensions=None,
python_type=<class 'int'>,
python_full_type=<class 'int'>,
is_sub_struct=False,
sub_struct=None
),
'y': FieldLayoutInfo(
name='y',
offset=2,
nb_bytes=2,
struct_format='H',
nb_items=1,
dimensions=None,
python_type=<class 'int'>,
python_full_type=<class 'int'>,
is_sub_struct=False,
sub_struct=None
)
}
)
)
}
)
```
#### Rich tables
You can get a `rich` (https://rich.readthedocs.io/en/latest) table representation of a structure with the `print_rich_table` function from `databytes.utils`:
Taking the previous example strucs:
```python
from databytes.utils import print_rich_table
print_rich_table(Rectangle)
```
Will give you:
```bash
Layout of Rectangle (8 bytes)
βββββββββββ³βββββββββ³βββββββββββ³βββββββββ³βββββββββββββ³ββββββββββββββ
β Name β Offset β Nb bytes β Format β Dimensions β Python Type β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β corners β 0 β 8 β HHHH β (2,) β list[Point] β
βββββββββββ΄βββββββββ΄βββββββββββ΄βββββββββ΄βββββββββββββ΄ββββββββββββββ
```
calling it with `include_sub_structs_details=True` will give you more details about the sub-structs:
```bash
Layout of Rectangle (8 bytes)
βββββββββββ³βββββββββ³βββββββββββ³βββββββββ³βββββββββββββ³ββββββββββββββ
β Name β Offset β Nb bytes β Format β Dimensions β Python Type β
β‘ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β corners β 0 β 8 β HHHH β (2,) β list[Point] β
βββββββββββ΄βββββββββ΄βββββββββββ΄βββββββββ΄βββββββββββββ΄ββββββββββββββ
Layout of Point (4 bytes)
ββββββββ³βββββββββ³βββββββββββ³βββββββββ³βββββββββββββ³ββββββββββββββ
β Name β Offset β Nb bytes β Format β Dimensions β Python Type β
β‘βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ©
β x β 0 β 2 β H β None β int β
β y β 2 β 2 β H β None β int β
ββββββββ΄βββββββββ΄βββββββββββ΄βββββββββ΄βββββββββββββ΄ββββββββββββββ
```
Instead of `print_riche_table` you can use `get_rich_table_string` so you can print it the way you want. Or `get_rich_table` to integrate the result in another rich component.
You can install the `rich` library independantly or by installing databytes with the `[extra]` dependency (`pip install databytes[extra]`).
## Mypy
We have our own `mypy` plugin so that `mypy` understands that `field: t.uint16` will resolve to an `int`, or `field: t.uint16[2]` will resolve to a `list[int]`.
It will also ensure that the dimensions (in `[]`) are literal positive integers.
To use the plugin, in your mypy configuration add `databytes.mypy_plugin` to your list of plugins.
## Development
To set up the development environment, first create a virtual environment using the method of your choice, activate it and, in it:
```bash
# Install development dependencies
make dev
# Prettify code
make format # or `make pretty`
# Run linting tools (ruff and mypy)
make lint
# Run tests
make tests
# Run lint + tests
make checks
```
## License
MIT License - See the LICENSE file for details.
## Author
Stephane "Twidi" Angel (s.angel@twidi.com)
## Links
- Source: https://github.com/twidi/databytes
- Author's website: https://twidi.com
Raw data
{
"_id": null,
"home_page": null,
"name": "databytes",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "binary, struct, data-structures, shared-memory, buffer, serialization, deserialization, memory-mapping, bytes",
"author": null,
"author_email": "\"Stephane \\\"Twidi\\\" Angel\" <s.angel@twidi.com>",
"download_url": "https://files.pythonhosted.org/packages/fc/32/57927d144856f7998a6334d22c0fa82bc3d3e96191533ff64a0703a3e28d/databytes-1.2.1.tar.gz",
"platform": "Linux",
"description": "# DataBytes\n\n[](https://pypi.org/project/databytes/)\n[](https://github.com/twidi/databytes/actions/workflows/ci.yml)\n\nA Python library providing a class-based approach to serialize and deserialize binary data, built on top of Python's `struct` module. DataBytes makes it easy to work with binary structures, shared memory, and memory-mapped files in a type-safe and object-oriented way.\n\n## Features\n\n- Class-based binary structure definitions\n- Type-safe field declarations\n- Support for nested structures and arrays\n- Use standard python binary packing and unpacking via the `struct` package\n- Support for Python 3.10+\n\n## Installation\n\nYou can install `databytes` from [PyPI](https://pypi.org/project/databytes/):\n\n```bash\npip install databytes\n```\n\n## Quick Start\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Point(BinaryStruct):\n name: t.string[12] # fixed-length string of 12 chars\n x: t.uint16\n y: t.uint16\n\nclass Rectangle(BinaryStruct):\n top_left: Point\n bottom_right: Point\n area: t.uint32\n selected_points: Point[2] # array of 2 points\n\n# Create a new structure with a buffer\nassert Rectangle._nb_bytes == 68 # (12+4) for each point + 4 for area\n\n# can be a bytes (read-only), bytearray, memoryview (like the `.buf` of a shared memory), mmap.mmap, array.array, ctypes.Array, numpy bytes array\nbuffer = bytearray(Rectangle._nb_bytes)\n\nrect = Rectangle(buffer)\n\n# Set values\nrect.top_left.name = \"Top Left\"\nrect.top_left.x = 0\nrect.top_left.y = 0\nrect.bottom_right.name = \"Bottom Right\"\nrect.bottom_right.x = 10\nrect.bottom_right.y = 10\nrect.area = 100\nrect.selected_points[0].name = \"Selected 1\"\nrect.selected_points[0].x = 1\nrect.selected_points[0].y = 2\nrect.selected_points[1].name = \"Selected 2\"\nrect.selected_points[1].x = 3\nrect.selected_points[1].y = 4\n\n# buffer is updated\nprint(buffer)\n# bytearray(b'Top Left\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00Bottom Right\\n\\x00\\n\\x00d\\x00\\x00\\x00Selected 1\\x00\\x00\\x01\\x00\\x02\\x00Selected 2\\x00\\x00\\x03\\x00\\x04\\x00')\n\n# Load this buffer into another Rectangle instance\nrect2 = Rectangle(buffer)\nassert rect2.top_left.name == \"Top Left\"\nassert rect2.top_left.x == 0\nassert rect2.top_left.y == 0\nassert rect2.bottom_right.name == \"Bottom Right\"\nassert rect2.bottom_right.x == 10\nassert rect2.bottom_right.y == 10\nassert rect2.area == 100\nassert rect2.selected_points[0].name == \"Selected 1\"\nassert rect2.selected_points[0].x == 1\nassert rect2.selected_points[0].y == 2\nassert rect2.selected_points[1].name == \"Selected 2\"\nassert rect2.selected_points[1].x == 3\nassert rect2.selected_points[1].y == 4\n\n```\n\n\n## Key Concepts\n\n- **Binary Structures**: Define your data structures using Python classes that inherit from `BinaryStruct`\n- **Typing**: Field are declared using type hints\n- **Memory Efficiency**: Direct memory access without unnecessary copies\n- **Shared Memory**: Easy integration with multiprocessing using shared memory buffers\n- **Extensibility**: Create custom field types by extending the base types\n\n## Dependencies\n\n- Python >= 3.10\n- typing-extensions\n- numpy (only used to create the `Buffer` type alias \ud83d\ude11 )\n\nOptional dependencies:\n- rich (for enhanced display capabilities)\n\n## Available types\n\n### Basic Types\n\nAs some types have the same name as builtins, the best way to import them is to use `from databytes import types as t` and use the `t.` prefix:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Data(BinaryStruct):\n field: t.uint16\n```\n\n- `uint8` / `ubyte` (1 byte, coalesce to python int)\n- `int8` / `byte` (1 byte, coalesce to python int)\n- `uint16` / `ushort` (2 bytes, coalesce to python int)\n- `int16` / `short` (2 bytes, coalesce to python int)\n- `uint32` (4 bytes, coalesce to python int)\n- `int32` (4 bytes, coalesce to python int)\n- `uint64` (8 bytes, coalesce to python int)\n- `int64` (8 bytes, coalesce to python int)\n- `float32` / `float` (4 bytes, coalesce to python float)\n- `float64` / `double` (8 bytes, coalesce to python float)\n- `char` (1 byte, coalesce to python bytes)\n- `string` (fixed-length string, automatically padded with nulls, coalesce to python str)\n\n### Custom Types\n\nYou can use as a field another struct:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Point(BinaryStruct):\n x: t.uint16\n y: t.uint16\n\nclass Rectangle(BinaryStruct):\n top_left: Point\n bottom_right: Point\n\nbuffer = bytearray(Rectangle._nb_bytes)\nrect = Rectangle(buffer)\nrect.top_left.x = 0\nrect.top_left.y = 0\nrect.bottom_right.x = 10\nrect.bottom_right.y = 10\n```\n\n*WARNING*: It's actually not possible to set the values of a sub-struct directly without using the attributes. It's due to the fact that the sub-structs instances are tied to their parent buffer, with the correct offset.\n\n```python\npoint_buffer = bytearray(Point._nb_bytes)\npoint = Point(point_buffer)\npoint.x = 5\npoint.y = 5\nrect.bottom_right = point\n\n# when reading the buffer on another instance, we'll have the original values\nrect2 = Rectangle(buffer)\nassert rect2.bottom_right.x == 10\nassert rect2.bottom_right.y == 10\n```\n\n### Arrays\n\nBasic and custom types can be used in arrays of any dimension:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Point(BinaryStruct):\n x: t.uint16\n y: t.uint16\n\nclass Rectangle(BinaryStruct):\n corners: Point[2] # array of 2 points\n matrix_2dim: Point[2, 3] # matrix of 3x2 points\n matrix_3dim: Point[2, 3, 4] # matrix of 4x3x2 points\n \n```\n\n*WARNING*: The dimensions are defined in reverse order: `Point[2, 3, 4]` is a list of 4 lists of 3 lists of 2 points\n\n*WARNING*: It's actually not possible to set the arrays instances directly without using concrete slicing and attributes. It's due to the fact that the sub-structs instances are tied to their parent buffer, with the correct offset.\n\n```python\nclass Point(BinaryStruct):\n x: t.uint16\n y: t.uint16\n\nclass Rectangle(BinaryStruct):\n corners: Point[2] # array of 2 points\n\npoint1_buffer = bytearray(Point._nb_bytes)\npoint2_buffer = bytearray(Point._nb_bytes)\npoint1 = Point(point1_buffer)\npoint1.x = 5\npoint1.y = 5\npoint2 = Point(point2_buffer)\npoint2.x = 10\npoint2.y = 10\n\nbuffer = bytearray(Rectangle._nb_bytes)\nrect = Rectangle(buffer)\n# Won't work\nrect.corners[0] = point1\nrect.corners[1] = point2\n# Won't work either\nrect.corners = [point1, point2]\n# Will work\nrect.corners[0].x = 5 # you can use `= point1.x`\nrect.corners[0].y = 5\nrect.corners[1].x = 10\nrect.corners[1].y = 10\n```\n\n#### Special case for strings.\n\nBy default a string is a string of 1 character. To defined the max length, define the length as the first dimension:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Person(BinaryStruct):\n name: t.string[10] # fixed-length string of 10 chars\n```\n\nWhen retrieving the value, you'll have a real string, not a list of characters. If the string is shorter than the allocated space, it will be null-padded in the buffer but retrieved as a real string without the nulls.\n\nTo define an array of strings, add more dimensions:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Person(BinaryStruct):\n name: t.string[10] # fixed-length string of 10 chars\n aliases: t.string[10, 2] # array of 2 strings of 10 chars\n```\n\n## API\n\n### Key points\n\n- `BinaryStruct` is the base class for all structures (`from databytes import BinaryStruct`)\n- `types` module contains all available types (`from databytes import types as t`)\n- a `BinaryStruct` instance is created taking a buffer as argument (`buffer = bytearray(Rectangle._nb_bytes); rect = Rectangle(buffer)`) and an optional offset for the start of the buffer to use\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Data(BinaryStruct):\n field: t.uint16\n\nbuffer = bytearray(1000)\n\n# here the data will be read/written at offset 100 of the buffer (and will only use the bytes size defined by the fields, here 2 bytes)\ndata = Data(buffer, 100)\n```\n\n### Supported buffers\n\nWe defined the `Buffer` type alias like this:\n\n```python\nBuffer: TypeAlias = bytes | bytearray | memoryview | mmap.mmap | array.array | ctypes.Array | np.ndarray\n```\n\nNot that all buffers are not writeable (like `bytes`, immutable `memoryview`s, immutable numpy arrays).\n\nIf you have another buffer type that we should support, please tell us.\n\nYou can use any buffer as long as:\n- it works with `pack_info` and `unpack_from` from the python `struct` module\n- it has a `__len__` method (we use it to ensure at init time that the buffer is big enough)\n\n(of course, if it's not in our `Buffer` type alias, mypy won't be happy and you'll have to handle it)\n\n### BinaryStruct extra fields\n\nTo not pollute the namespace, extra fields are prefixed with `_`:\n\n- `_nb_bytes` (class var): number of bytes used by the structure (including sub-structs)\n- `_struct_format` (class var): the format, using the python `struct` module format, of the whole structure (just for information)\n- `_endianness` (class var): the endianness of the structure (see below for more details)\n- `_buffer` (instance var): the buffer used by the structure (the buffer passed to the constructor, will be the same for the main struct and all sub-structs)\n- `_offset` (instance var): the offset of this specific structure in the buffer\n\nYou can define your own fields for your own purpose, as only the fields defined in `BinaryStruct` using the `types` module or sub structs inheriting from `BinaryStruct` are handled by the library:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Data(BinaryStruct):\n field: t.uint16\n my_field: str # will not be handled by the library\n\n def __init__(self, my_field: str, buffer: Buffer, offset: int = 0) -> None:\n super().__init__(buffer, offset)\n self.my_field = my_field\n```\n\n### Endianness\n\nThe endianess of a struct can be defined in two ways:\n\n- When defining the class by setting the `_endianness` class var:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t, Endianness\n\nclass Data(BinaryStruct):\n _endianness = Endianness.LITTLE\n field: t.uint16\n```\n\n- When instantiating the struct with the `endianness` argument (in this case the value will override the class value for this instance):\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t, Endianness\n\nclass Data(BinaryStruct):\n _endianness = Endianness.BIG # will be ignored so it's not necessary to add it\n field: t.uint16\n\ndata = Data(bytearray(1000), endianness=Endianness.LITTLE)\n```\n\n`Endianness` is an enum defined in `databytes.types` with the following values:\n\n- `Endianness.NATIVE` (default): the endianness of the system\n- `Endianness.LITTLE`\n- `Endianness.BIG`\n- `Endianness.NETWORK` (alias for `Endianness.BIG`)\n\n\nThe default value is forced to `Endianness.NATIVE` and can be changed by setting the `DATABYTES_ENDIANNESS` environment variable (before the `databytes` library is loaded) to `LITTLE`, `BIG`, `NETWORK` (alias for `BIG`) or `NATIVE` (the actual default).\n\nThe `_endianness` of a sub-struct defined on its class will be ignored: all sub-struts automatically inherit the endianness of their parent struct.\n\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t, Endianness\n\nclass Child(BinaryStruct):\n _endianness = Endianness.BIG\n value: t.uint16\n\nclass Data1(BinaryStruct):\n _endianness = Endianness.LITTLE\n child: Child\n\nclass Data2(BinaryStruct):\n child: Child\n\nbuffer = bytearray(2)\n\nchild_alone = Child(buffer)\nchild_alone.value = 2\nassert buffer == b\"\\x00\\x02\" # child alone uses big endian from Child class endianness\n\ndata1 = Data1(buffer)\ndata1.child.value = 3\nassert buffer == b\"\\x03\\x00\" # child uses little endian from Data1 class endianness\n\ndata2 = Data2(buffer, endianness=Endianness.LITTLE)\ndata2.child.value = 4\nassert buffer == b\"\\x04\\x00\" # child uses little endian from Data2 instance endianness\n\n```\n\n\n### Pointing to another buffer\n\nIt's possible to use the same structure on different buffers:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Data(BinaryStruct):\n field: t.uint16\n\nbuffer1 = bytearray(1000)\nbuffer2 = bytearray(1000)\nbuffer3 = bytearray(1000)\n\n# will read/write the data at offset 100 of the buffer1\ndata = Data(buffer1, 100)\n\n# will now read/write the data at offset 100 of the buffer2\ndata.attach_buffer(buffer2)\n\n# will now read/write the data at offset 50 of the buffer3\ndata.attach_buffer(buffer3, 50)\n\n```\n\n### Freeing the buffer\n\nWhen using shared memory for example, the buffer must be freed before freeing the shared memory. To do that, use the `free_buffer` method:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\n\nclass Data(BinaryStruct):\n field: t.uint16\n\nshm = SharedMemory(create=True, size=1000)\nbuffer = shm.buf\n\ndata: Data | None = None\ntry:\n data = Data(buffer)\n data.value = 2\n data.text = \"World\"\n\n # ...\n\nfinally:\n if data is not None:\n data.free_buffer()\n shm.close()\n shm.unlink()\n``` \n\nOf course, here `data` cannot be used anymore unless you call `attach_buffer` on it with another buffer.\n\n### Utils\n\n#### Layout information\n\nYou can get the layout of a structure with the `get_layout_info` function from `databytes.utils`:\n\n```python\nfrom databytes import BinaryStruct\nfrom databytes import types as t\nfrom databytes.utils import get_layout_info\n\nclass Point(BinaryStruct):\n x: t.uint16\n y: t.uint16\n\nclass Rectangle(BinaryStruct):\n corners: Point[2] # array of 2 points\n\nget_layout_info(Rectangle)\n```\n\nWill give you:\n\n```python\nStructLayoutInfo(\n name='Rectangle', \n struct_class=<class '__main__.Rectangle'>, \n endianness=<Endianness.NATIVE: '='>, \n byte_order=<Endianness.LITTLE: '<'>,\n struct_format='HHHH', \n nb_bytes=8, \n offset=0, \n fields={\n 'corners': FieldLayoutInfo(\n name='corners', \n offset=0, \n nb_bytes=8, \n struct_format='HHHH', \n nb_items=2, \n dimensions=(2,), \n python_type=<class '__main__.Point'>, \n python_full_type=list[__main__.Point], \n is_sub_struct=True, \n sub_struct=None\n )\n }\n)\n```\n\nCall it with `include_sub_structs_details=True` to get more details about the sub-structs, in the `sub_struct` field (recursively)\n```python\nStructLayoutInfo(\n name='Rectangle', \n struct_class=<class '__main__.Rectangle'>\n endianness=<Endianness.NATIVE: '='>, \n byte_order=<Endianness.LITTLE: '<'>,\n struct_format='HHHH'\n nb_bytes=8\n offset=0\n fields={\n 'corners': FieldLayoutInfo(\n name='corners'\n offset=0\n nb_bytes=8\n struct_format='HHHH'\n nb_items=2\n dimensions=(2,)\n python_type=<class '__main__.Point'>\n python_full_type=list[__main__.Point], \n is_sub_struct=True, \n sub_struct=StructLayoutInfo(\n name='Point', \n struct_class=<class '__main__.Point'>, \n endianness=<Endianness.NATIVE: '='>, \n byte_order=<Endianness.LITTLE: '<'>,\n struct_format='HH'\n nb_bytes=4\n offset=0\n fields={\n 'x': FieldLayoutInfo(\n name='x'\n offset=0\n nb_bytes=2\n struct_format='H',\n nb_items=1,\n dimensions=None,\n python_type=<class 'int'>,\n python_full_type=<class 'int'>,\n is_sub_struct=False,\n sub_struct=None\n ),\n 'y': FieldLayoutInfo(\n name='y', \n offset=2, \n nb_bytes=2, \n struct_format='H', \n nb_items=1, \n dimensions=None, \n python_type=<class 'int'>, \n python_full_type=<class 'int'>, \n is_sub_struct=False, \n sub_struct=None\n )\n }\n )\n )\n }\n)\n```\n\n#### Rich tables\n\nYou can get a `rich` (https://rich.readthedocs.io/en/latest) table representation of a structure with the `print_rich_table` function from `databytes.utils`:\n\nTaking the previous example strucs:\n\n```python\nfrom databytes.utils import print_rich_table\n\nprint_rich_table(Rectangle)\n```\n\nWill give you:\n```bash\n Layout of Rectangle (8 bytes) \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name \u2503 Offset \u2503 Nb bytes \u2503 Format \u2503 Dimensions \u2503 Python Type \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 corners \u2502 0 \u2502 8 \u2502 HHHH \u2502 (2,) \u2502 list[Point] \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\ncalling it with `include_sub_structs_details=True` will give you more details about the sub-structs:\n```bash\n Layout of Rectangle (8 bytes) \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name \u2503 Offset \u2503 Nb bytes \u2503 Format \u2503 Dimensions \u2503 Python Type \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 corners \u2502 0 \u2502 8 \u2502 HHHH \u2502 (2,) \u2502 list[Point] \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Layout of Point (4 bytes) \n\u250f\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2533\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n\u2503 Name \u2503 Offset \u2503 Nb bytes \u2503 Format \u2503 Dimensions \u2503 Python Type \u2503\n\u2521\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2547\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2529\n\u2502 x \u2502 0 \u2502 2 \u2502 H \u2502 None \u2502 int \u2502\n\u2502 y \u2502 2 \u2502 2 \u2502 H \u2502 None \u2502 int \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\nInstead of `print_riche_table` you can use `get_rich_table_string` so you can print it the way you want. Or `get_rich_table` to integrate the result in another rich component.\n\nYou can install the `rich` library independantly or by installing databytes with the `[extra]` dependency (`pip install databytes[extra]`).\n\n\n## Mypy\n\nWe have our own `mypy` plugin so that `mypy` understands that `field: t.uint16` will resolve to an `int`, or `field: t.uint16[2]` will resolve to a `list[int]`.\n\nIt will also ensure that the dimensions (in `[]`) are literal positive integers.\n\nTo use the plugin, in your mypy configuration add `databytes.mypy_plugin` to your list of plugins.\n\n\n## Development\n\nTo set up the development environment, first create a virtual environment using the method of your choice, activate it and, in it:\n\n```bash\n# Install development dependencies\nmake dev\n\n# Prettify code\nmake format # or `make pretty`\n\n# Run linting tools (ruff and mypy)\nmake lint\n\n# Run tests\nmake tests\n\n# Run lint + tests\nmake checks\n```\n\n## License\n\nMIT License - See the LICENSE file for details.\n\n## Author\n\nStephane \"Twidi\" Angel (s.angel@twidi.com)\n\n## Links\n\n- Source: https://github.com/twidi/databytes\n- Author's website: https://twidi.com\n",
"bugtrack_url": null,
"license": "Copyright (c) 2024-present St\u00e9phane \"Twidi\" Angel. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
"summary": "A class based binary structure to serialize/deserialize binary data (using python struct under the hood).",
"version": "1.2.1",
"project_urls": {
"author": "https://twidi.com",
"source": "https://github.com/twidi/databytes"
},
"split_keywords": [
"binary",
" struct",
" data-structures",
" shared-memory",
" buffer",
" serialization",
" deserialization",
" memory-mapping",
" bytes"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f13baec48fa23f5f1f94533abfbeff823d3b34d8b9b24ba439de3cae4f787361",
"md5": "bbe06c47570450a4aa57c21d972423b5",
"sha256": "95d4e293b513dd4cd57cb18437aa8bc03a904ef2beff146ed3be1de1eef1879b"
},
"downloads": -1,
"filename": "databytes-1.2.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "bbe06c47570450a4aa57c21d972423b5",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 16186,
"upload_time": "2024-11-30T01:12:35",
"upload_time_iso_8601": "2024-11-30T01:12:35.362725Z",
"url": "https://files.pythonhosted.org/packages/f1/3b/aec48fa23f5f1f94533abfbeff823d3b34d8b9b24ba439de3cae4f787361/databytes-1.2.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "fc3257927d144856f7998a6334d22c0fa82bc3d3e96191533ff64a0703a3e28d",
"md5": "3583a097d2909f42e89cb435edfaad36",
"sha256": "d562954eccc66df2273478507050e817fe586390df945bf5da9ec2628c5a3005"
},
"downloads": -1,
"filename": "databytes-1.2.1.tar.gz",
"has_sig": false,
"md5_digest": "3583a097d2909f42e89cb435edfaad36",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 38279,
"upload_time": "2024-11-30T01:13:01",
"upload_time_iso_8601": "2024-11-30T01:13:01.731061Z",
"url": "https://files.pythonhosted.org/packages/fc/32/57927d144856f7998a6334d22c0fa82bc3d3e96191533ff64a0703a3e28d/databytes-1.2.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-30 01:13:01",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "twidi",
"github_project": "databytes",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "databytes"
}