Pheres is a typed extension of python's json
module.
By typed, I mean that it can typecheck values at runtime.
It provides:
- typed JSON decoding with
TypedJSONDecoder
(link) - JSON type analysis with
typeof
(link) - runtime typechecking with
typecheck
(link) - easy typechecked conversion between python objects, custom classes and JSON str or files
with the
@jsonable
decorator (link)
The name comes from greek mythology: Pheres is a son of Jason (JSON).
For more information, refer to the documentation.
Pheres is currently in alpha.
This is essentially a personal project I'm using to learn to do (hopefully) better code,
and python tools I find interesting (typing
module, mypy
, pytest
,
hypothesis
, black
...).
This means that:
- features are incomplete and new features may appear
- type annotations are not complete
- the API might change (but I try to avoid that)
- the tests are incomplete
- there may be bugs
If you find bugs or have suggestions, you are welcome to report them on the bug tracker.
Pheres is not yet available on PyPI, so you must intall it from github:
pip install git+https://github.com/QuentinSoubeyran/pheres
We are working on releasing Pheres to PyPI.
The documentation is available on github pages.
Pheres is typed and will require types in many places.
Types are specified with type hints from the builtin typing
module.
Note: Starting with Python 3.9, you can also use builtin types for generic type annotations thanks to PEP 585.
Pheres provide the pheres.types
submodule for quick imports of all
the required types in the current namespace. See the documentation
for why this is necessary.
The TypedJSONDecoder
class is a typed version of the builtin json.JSONDecoder
,
with a few tweaks to make it easier to use:
import pheres as ph from pheres.types import * GraphT = dict[str, list[str]] GraphDecoder = ph.TypedJSONDecoder[GraphT] graph = GraphDecoder.load("graph.json") not_a_graph = """{ "v1": ["v1", "v2"], "v2": "v2" }""" # Raises pheres.TypedJSONDecodeError GraphDecoder.loads(not_a_graph)
The @jsonable
decorator is an easy way to make
a class serializable and deserializable to JSON. It is compatible with
dataclasses.dataclass
and attr.s
:
from dataclasses import dataclass import pheres as ph from pheres.types import * @dataclass @ph.jsonable(after="Contacts") class People: name: str surname: str number: int contacts: "Contacts" def phone(name: str) -> None: for contact in self.contacts: if contact.name == name: print("Calling %s" % contact.number) break else: print("I do not know %s's number" % name) @ph.jsonable.Array["People", ...](internal="contacts") class Contacts: contacts: list[People] def __init__(self, *contacts) -> None: self.contacts = list(contacts) def has(self, p: People) -> bool: return p in self.contacts def __iter__(self): return iter(self.contacts) alice = People.from_json("""{ "name": "alice", "surname": "", "number": 9999999, "contacts": [ { "name": "Bob", "surname": "", "number": 12345678, "contacts": [] } ] }""") print(alice.to_json()) assert alice == People.from_json(alice.to_json()) people_list = ph.dumps([alice]) database = ph.TypedJSONDecoder[list[People]].loads(people_list) database[0].phone("Bob")
While this example is overly simple, it highlights the main features of @jsonable
:
- Definitions similar to those of, and compatible with,
dataclasses.dataclass
- Different types of jsonable classes, depending on the JSON representation
- The ability to nest jsonable classes together, and to create cyclic definitions
Pheres also contains some utilities to analyse the types of loaded JSON:
import pheres as ph jdata = ph.load("data/my_file.json") if ph.typeof(jdata) is ph.JSONObject: print("Root document found!")
See the documentation for details.