Pheres¶
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.
Development status¶
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.
Installation¶
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.
Documentation¶
The documentation is available on github pages.
Overview¶
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.
Typed JSON¶
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)
Jsonable classes¶
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
Typing¶
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.