Skip to main content

Serialize and deserialize almost anything easily.

Project description

Packify

This is a simple package that allows one to serialize and deserialize practically any data structure to and from bytes.

Installation

pip install packify

Usage

Usage is simple: import the package and call the pack/unpack functions.

from packify import pack, unpack
from decimal import Decimal

data = {
    123: 432.1,
    "abc": "cba",
    b"abc": b"cba",
    "None": None,
    "true": True,
    "false": False,
    'list': [
        '123',
        123,
        b'123',
        True,
    ],
    'tuple': (
        '123',
        123,
        b'123',
        False,
    ),
    'set': {
        '123',
        123,
        b'123',
    },
    'Decimal': Decimal('123.321'),
}

packed = pack(data)
unpacked = unpack(packed)
assert type(packed) is bytes
assert unpacked == data

The following types are supported:

  • int
  • bool
  • float
  • Decimal
  • str
  • bytes
  • bytearray
  • NoneType
  • list
  • tuple
  • set
  • dict
Using with named tuples

If you want to use named tuples, you can do so by packing as a regular tuple and then unpacking it into the named tuple class.

from packify import pack, unpack
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])

original = Point(1, 2)
packed = pack(tuple(original))
unpacked = Point(*unpack(packed))
assert unpacked == original

The recursive features of pack and unpack will not work with named tuples nested within data structures. Using a Packable implementation is a better approach if you need custom data types nested within data structures (see below).


Additionally, a simple duck-type interface/protocol, Packable, is included. Any more complex data structure can be handled if it implements the Packable interface. Packable is defined as follows:

@runtime_checkable
class Packable(Protocol):
    def pack(self) -> bytes:
        """Packs the instance into bytes."""
        ...

    @classmethod
    def unpack(cls, data: bytes, /, *, inject: dict = {}) -> Packable:
        """Unpacks an instance from bytes. Must accept dependency
            injection to unpack other Packable types.
        """
        ...

If a class that implements Packable is used, then it needs to be included in the inject parameter for calls to unpack.

Example
from dataclasses import dataclass, field
from packify import pack, unpack

@dataclass
class Thing:
    data: str = field()
    amount: int = field()
    fraction: float = field()
    parts: list = field()
    def __eq__(self, other) -> bool:
        return type(self) is type(other) and self.pack() == other.pack()
    def pack(self) -> bytes:
        return pack((self.data, self.amount, self.fraction, self.parts))
    @classmethod
    def unpack(cls, data: bytes, /, *, inject: dict = {}):
        return cls(*unpack(data, inject={**globals(), **inject}))

thing = Thing("hello world", 123, 420.69, ['a', b'b', 3])
packed = pack(thing)
unpacked = unpack(packed, inject={'Thing': Thing})
assert unpacked == thing
# alternately, the easier but less specific method is to copy globals
unpacked = unpack(packed, inject={**globals()})
assert unpacked == thing

As long as the class implements the Packable protocol, it can be included in lists, sets, tuples, and dicts (assuming it is hashable for set or to be used as a dict key), and it will just work.

The pack function will raise a UsageError if the data is not serializable, and the unpack function will raise a UsageError if it is unable to find a Packable class to unpack the relevant item.

For convenience/use in annotations, a SerializableType is exported which includes the above type information.

Additional documentation can be found in dox.md, which was generated automagically by autodox.

More Resources

Check out the Pycelium discord server. If you experience a problem, please discuss it on the Discord server. All suggestions for improvement are also welcome, and the best place for that is also Discord. If you experience a bug and do not use Discord, open an issue on Github.

Tests

Since it is a focused package, there are only 11 tests, and they are mostly e2e tests of both the pack and unpack functions. After using this for a year, I found an edge case, and there is a test to prove it has been fixed. I also added 2 fuzz tests to broaden the coverage of the test suite during a major refactor. To run the tests, clone the repository and use the following:

python tests/test_serialization.py
python tests/test_fuzzy.py

License

Copyright (c) 2023, 2024, 2025 Jonathan Voss (k98kurz)

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

packify-0.3.0.tar.gz (10.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

packify-0.3.0-py3-none-any.whl (7.3 kB view details)

Uploaded Python 3

File details

Details for the file packify-0.3.0.tar.gz.

File metadata

  • Download URL: packify-0.3.0.tar.gz
  • Upload date:
  • Size: 10.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.11.0rc1

File hashes

Hashes for packify-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2c655ee418b774102e76577e7c5b9cabd41b0772489589a68b28dce8bbff9b74
MD5 5acec2a11f087cada8b3b5cab0aa53ef
BLAKE2b-256 d1d1a4267b01b9bbbd53d415a47bd708db60cbe518232f6de4fcb1635c9ef70c

See more details on using hashes here.

File details

Details for the file packify-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: packify-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 7.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.11.0rc1

File hashes

Hashes for packify-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e010c5759f857a3ef67e8b3205e20d4d84acac70db33457a30d99445588b3e31
MD5 a62c60aaf9aa92394e271f2a66366d00
BLAKE2b-256 672b3f13db332ba0678416a827795e7559f63f04340b9c7fca5699ff9392e7b8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page