Contents¶
Overview¶
docs | |
---|---|
tests | |
package |
PyJackson is a serialization library based on type hinting
Example¶
Just type hint __init__ and you are ready to go:
import pyjackson
class MyPayload:
def __init__(self, string_field: str, int_field: int):
self.string_field = string_field
self.int_field = int_field
pyjackson.serialize(MyPayload('value', 10)) # {'string_field': 'value', 'int_field': 10}
pyjackson.deserialize({'string_field': 'value', 'int_field': 10}, MyPayload) # MyPayload('value', 10)
More features and examples here and in examples dir.
Installation¶
pip install pyjackson
Documentation¶
Licence¶
- Free software: Apache Software License 2.0
Usage¶
Quickstart¶
To use PyJackson in a project, define a class with type hinted constructor arguments
1 2 3 4 | class MyPayload:
def __init__(self, string_field: str, int_field: int):
self.string_field = string_field
self.int_field = int_field
|
Now you are able to serialize instance of your class to dict and back with
serialize()
and deserialize()
1 2 3 4 | instance = MyPayload('value', 10)
payload = pyjackson.serialize(instance) # {'string_field': 'value', 'int_field': 10}
new_instance = pyjackson.deserialize(payload, MyPayload) # MyPayload('value', 10)
|
It also works with nested structures and supports typing module generic annotations
1 2 3 4 5 6 7 8 | class PayloadList:
def __init__(self, payload_list: typing.List[MyPayload]):
self.payload_list = payload_list
plist = PayloadList([instance])
payloads = pyjackson.serialize(plist)
# {'payload_list': [{'string_field': 'value', 'int_field': 10}]}
|
This code can be found in examples/quickstart.py
Type Hierarchy¶
If you have a hierarchy of types and you want to be able to deserialize them using base type, you need
to register your base type with type_field()
decorator. First argument is a name of
class field, where you will put aliases for child types.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from pyjackson import deserialize, serialize
from pyjackson.decorators import type_field
@type_field('type_alias')
class Parent:
type_alias = 'parent' # also could be None for abstract parents
class Child1(Parent):
type_alias = 'child1'
def __init__(self, a: int):
self.a = a
class Child2(Parent):
type_alias = 'child2'
def __init__(self, b: str):
self.b = b
serialize(Child1(1), Parent) # {'type_alias': 'child1', 'a': 1}
deserialize({'type_alias': 'child2', 'b': 'b'}, Parent) # Child2('b')
|
Custom Serialization¶
If you want custom serialization logic for one of your class, or if you need to serialize external types with no type hints, you can implement custom serializer for them. Serializer is bound to the type you register and will be used when this type is encountered.
For example, you have class without type hints, or __init__ differ from actual fields. Just implement StaticSerializer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class External:
def __init__(self, a):
self.b = a
class ExternalSerializer(StaticSerializer):
real_type = External
@classmethod
def serialize(cls, instance: External) -> dict:
return {'a': instance.b}
@classmethod
def deserialize(cls, obj: dict) -> object:
return External(obj['a'])
|
Now you can serialize External
1 2 | payload = serialize(External('value')) # {'a': 'value'}
new_instance = deserialize(payload, External) # External('value')
|
Like with simple types, it can be used in nested structures
1 2 3 4 5 6 7 | class Container:
def __init__(self, externals: List[External]):
self.externals = externals
container_payload = serialize(Container([External('value')]))
new_container = deserialize(container_payload, Container)
|
If you need to parametrize yor serializer, you can implement generic Serializer, adding your parameters to serializers __init__. For example, you want to serialize lists of certain size with same values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class SizedListSerializer(Serializer):
real_type = list
def __init__(self, size: int):
self.size = size
def serialize(self, instance: list) -> dict:
if len(set(instance)) != 1:
raise ValueError('Cannot serialize list with different values')
return {'value': instance[0]}
def deserialize(self, obj: dict) -> object:
value = obj['value']
return [value for _ in range(self.size)]
serializer = SizedListSerializer(3)
list_payload = serialize([1, 1, 1], serializer) # {'value': 1}
new_list = deserialize(list_payload, serializer) # [1, 1 ,1]
|
You can also use serializers in type hints
1 2 3 4 5 6 7 | class OtherContainer:
def __init__(self, sized_list: SizedListSerializer(3)):
self.sized_list = sized_list
other_payload = serialize(OtherContainer([2, 2, 2])) # {'sized_list': {'value': 2}}
new_other_container = deserialize(other_payload, OtherContainer) # OtherContainer([2, 2, 2])
|
You can find this code in examples/custom_serialization.py
pyjackson package¶
-
pyjackson.
deserialize
(obj, as_class: Union[Type[CT_co], pyjackson.generics.Serializer])[source]¶ Convert python dict into given class
Parameters: - obj – dict (or list or any primitive) to deserialize
- as_class – type or serializer
Returns: deserialized instance of as_class (or real_type of serializer)
Raise: DeserializationError
-
pyjackson.
dump
(fp, obj, as_class: type = None)[source]¶ Serialize obj to JSON as as_class and write it to file-like fp
Parameters: - fp – file-like object to write
- obj – object to serialize
- as_class – type or serializer
Returns: bytes written
-
pyjackson.
dumps
(obj, as_class: type = None)[source]¶ Serialize obj to JSON string as as_class
Parameters: - obj – object to serialize
- as_class – type or serializer
Returns: JSON string representation
-
pyjackson.
load
(fp, as_class: type)[source]¶ Deserialize content of file-like fp to as_class instance
Parameters: - fp – file-like object to read
- as_class – type or serializer
Returns: deserialized instance of as_class (or real_type of serializer)
-
pyjackson.
loads
(payload: str, as_class: type)[source]¶ Deserialize payload to as_class instance
Parameters: - payload – JSON string
- as_class – type or serializer
Returns: deserialized instance of as_class (or real_type of serializer)
-
pyjackson.
read
(path: str, as_class: Type[T]) → T[source]¶ Deserialize object from file in path as as_class
Parameters: - path – path to file with JSON representation
- as_class – type or serializer
Returns: deserialized instance of as_class (or real_type of serializer)
-
pyjackson.
serialize
(obj, as_class: Union[Type[CT_co], pyjackson.generics.Serializer] = None)[source]¶ Convert object into JSON-compatible dict (or other structure)
Parameters: - obj – object to serialize
- as_class – type to serialize as or serializer
Returns: JSON-compatible object
-
pyjackson.
write
(path: str, obj, as_class: type = None)[source]¶ Serialize obj to JSON and write it to path
Parameters: - path – path to write JSON representation
- obj – object to serialize
- as_class – type or serializer
Returns: bytes written
Submodules¶
pyjackson.core module¶
-
class
pyjackson.core.
Position
[source]¶ Bases:
enum.Enum
Enum to change field with type information position
-
INSIDE
= 0¶
-
OUTSIDE
= 1¶
-
-
class
pyjackson.core.
Unserializable
[source]¶ Bases:
object
Mixin type to signal that type is not serializable.
pyjackson.serialize()
will throw explicit error if called with instance of Unserializable (or object with nested Unserializable)
-
class
pyjackson.core.
Field
(name: str, type: type, has_default: bool, default: Any = None)[source]¶ Bases:
pyjackson.core.Comparable
pyjackson.decorators module¶
-
pyjackson.decorators.
make_string
(*fields, include_name=True)[source]¶ Decorator to create a __str__ method for class based on __init__ arguments
Usage: directly
@make_string()
on class declaration to include all fields or@make_string(*fields, include_name)()
to alter paramsParameters: - fields – list of strings with field names
- include_name – whether to include class name
-
pyjackson.decorators.
as_list
(cls: Type[CT_co])[source]¶ Mark class to serialize it to list instead of dict
Parameters: cls – class to mark
-
pyjackson.decorators.
type_field
(field_name, position: pyjackson.core.Position = <Position.INSIDE: 0>, allow_reregistration=False)[source]¶ Class decorator for polymorphic hierarchies to define class field name, where subclass’s type alias will be stored Use it on hierarchy root class, add class field with defined name to any subclasses The same field name will be used during deserialization
Parameters: - field_name – class field name to put alias for type
- position – where to put type alias
- allow_reregistration – whether to allow reregistration of same alias or throw error
pyjackson.errors module¶
-
exception
pyjackson.errors.
DeserializationError
[source]¶ Bases:
pyjackson.errors.PyjacksonError
Deserialization exception
-
exception
pyjackson.errors.
SerializationError
[source]¶ Bases:
pyjackson.errors.PyjacksonError
Serialization exception
-
exception
pyjackson.errors.
UnserializableError
(obj)[source]¶ Bases:
pyjackson.errors.SerializationError
Raised when unserializable object is being serialized
pyjackson.generics module¶
pyjackson.pydantic_ext module¶
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
Bug reports¶
When reporting a bug please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
Documentation improvements¶
PyJackson could always use more documentation, whether as part of the official PyJackson docs, in docstrings, or even on the web in blog posts, articles, and such.
Feature requests and feedback¶
The best way to send feedback is to file an issue at https://github.com/mike0sv/pyjackson/issues.
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that code contributions are welcome :)
Development¶
To set up pyjackson for local development:
Fork pyjackson (look for the “Fork” button).
Clone your fork locally:
git clone git@github.com:mike0sv/pyjackson.git
Create a branch for local development:
git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes, run all the checks, doc builder and spell checker with tox one command:
tox
Commit your changes and push your branch to GitHub:
git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature
Submit a pull request through the GitHub website.
Pull Request Guidelines¶
If you need some code review or feedback while you’re developing the code just make the pull request.
For merging, you should:
- Include passing tests (run
tox
) [1]. - Update documentation when there’s new API, functionality etc.
- Add a note to
CHANGELOG.rst
about the changes. - Add yourself to
AUTHORS.rst
.
[1] | If you don’t have all the necessary python versions available locally you can rely on Travis - it will run the tests for each change you add in the pull request. It will be slower though … |
Tips¶
To run a subset of tests:
tox -e envname -- pytest -k test_myfeature
To run all the test environments in parallel (you need to pip install detox
):
detox
Authors¶
- Mikhail Sveshnikov - https://github.com/mike0sv
Changelog¶
0.0.28 (2021-06-02)¶
- Support for python 3.9
0.0.27 (2020-12-05)¶
- Fix decorated class module name
0.0.26 (2020-07-07)¶
- Experimental pydantic support
0.0.25 (2020-03-23)¶
- Support for int and float keys in dicts
0.0.24 (2020-02-22)¶
- Support for python 3.8
0.0.23 (2019-12-16)¶
- Fixed bug in subtype resolving
0.0.21 (2019-11-25)¶
- Fixed default type name
0.0.19 (2019-11-25)¶
- Allow subtype reregistration flag
0.0.18 (2019-11-22)¶
- Added support for full class path in type field (with importing logic)
0.0.17 (2019-11-21)¶
- Added Any support for serde skipping
0.0.16 (2019-11-15)¶
- Raise on subtype resolve error and fix for camel case forward ref resolving
0.0.15 (2019-11-11)¶
- Set class docstring and qualname of hierarchy root to be valid
0.0.14 (2019-11-05)¶
- Added decorator for camel case field renaming
0.0.13 (2019-11-03)¶
- Added decorator for field renaming
0.0.12 (2019-10-28)¶
- Fixed is_serializable for Field
0.0.11 (2019-10-28)¶
- Fixed is_serializable for Signature
0.0.10 (2019-10-16)¶
- Set class name and module of hierarchy root to be valid
0.0.9 (2019-10-09)¶
- Removed empty Serialzier __init__ method and fix for staticmethod in serializer
0.0.8 (2019-10-07)¶
- Changed is_collection to not include dict type
0.0.7 (2019-10-04)¶
- Added datetime.datetime serializer
0.0.6 (2019-10-02)¶
- Added Tuple[X, Y] and Tuple[X, …] support
0.0.5 (2019-09-30)¶
- Fixed comparison of serializers
0.0.4 (2019-09-17)¶
- Added some examples and minor fixes
0.0.3 (2019-09-17)¶
- First release on PyPI.