Rewrite the whole data model, the data is now a wrapper from JSON to Python "data".

Changed files
+129 -179
src
atpasser
+1
src/atpasser/data/__init__.py
···
···
+
from .wrapper import *
+76
src/atpasser/data/data.py
···
···
+
import base64
+
from cid import CIDv0, CIDv1, cid, make_cid
+
import json
+
+
+
class Data:
+
"""
+
A class representing data with "$type" key.
+
+
Attributes:
+
type (str): The type of the data.
+
json (str): Original object in JSON format.
+
"""
+
+
def __init__(self, dataType: str, json: str = "{}") -> None:
+
"""
+
Initalizes data object.
+
+
Parameters:
+
type (str): The type of the data.
+
json (str): Original object in JSON format.
+
"""
+
self.type = dataType
+
self.json = json
+
+
def data(self):
+
"""
+
Loads data as a Python-friendly format.
+
+
Returns:
+
dict: Converted data from JSON object.
+
"""
+
return json.loads(self.json, object_hook=dataHook)
+
+
+
def dataHook(data: dict):
+
"""
+
Treated as `JSONDecoder`'s `object_hook`
+
+
Parameters:
+
data: data in format that `JSONDecoder` like ;)
+
"""
+
if "$bytes" in data:
+
return base64.b64decode(data["$bytes"])
+
elif "$link" in data:
+
return make_cid(data["$link"])
+
elif "$type" in data:
+
dataType = data["$type"]
+
del data["$type"]
+
return Data(dataType, json.dumps(data))
+
else:
+
return data
+
+
+
def _convertDataToFakeJSON(data):
+
if isinstance(data, bytes):
+
return {"$bytes": base64.b64encode(data)}
+
elif isinstance(data, (CIDv0, CIDv1)):
+
return {"link": data.encode()}
+
elif isinstance(data, dict):
+
for item in data:
+
data[item] = _convertDataToFakeJSON(data[item])
+
elif isinstance(data, (tuple, list, set)):
+
return [_convertDataToFakeJSON(item) for item in data]
+
else:
+
return data
+
+
+
class DataEncoder(json.JSONEncoder):
+
"""
+
A superset of `JSONEncoder` to support ATProto data.
+
"""
+
+
def default(self, o):
+
result = _convertDataToFakeJSON(o)
+
return super().default(result)
+52
src/atpasser/data/wrapper.py
···
···
+
from json import loads
+
from typing import Callable, Any
+
from .data import *
+
import functools
+
+
# Pyright did the whole job. Thank it.
+
+
+
class DataDecoder(json.JSONDecoder):
+
"""
+
A superset of `JSONDecoder` to support ATProto data.
+
"""
+
+
def __init__(
+
self,
+
*,
+
object_hook: Callable[[dict[str, Any]], Any] | None = dataHook,
+
parse_float: Callable[[str], Any] | None = None,
+
parse_int: Callable[[str], Any] | None = None,
+
parse_constant: Callable[[str], Any] | None = None,
+
strict: bool = True,
+
object_pairs_hook: Callable[[list[tuple[str, Any]]], Any] | None = None,
+
) -> None:
+
super().__init__(
+
object_hook=object_hook,
+
parse_float=parse_float,
+
parse_int=parse_int,
+
parse_constant=parse_constant,
+
strict=strict,
+
object_pairs_hook=object_pairs_hook,
+
)
+
+
+
# Screw it. I have to make 4 `json`-like functions.
+
+
+
def _dataDecorator(func):
+
@functools.wraps(func)
+
def wrapper(obj, *args, **kwargs):
+
kwargs.setdefault("cls", DataEncoder)
+
return func(obj, *args, **kwargs)
+
+
return wrapper
+
+
+
dump = _dataDecorator(json.dump)
+
dumps = _dataDecorator(json.dumps)
+
load = _dataDecorator(json.load)
+
loads = _dataDecorator(json.loads)
+
"""
+
Wrapper of the JSON functions to support ATProto data.
+
"""
-179
src/atpasser/dataFormatter/__init__.py
···
-
import base64 # Converts between Base64 and byte object
-
from cid import CIDv0, CIDv1, cid, make_cid # Converts CID to/from object
-
import json
-
-
# JSON is a string, however we uses the term JSON for dicts which read
-
# directly using the json library itself, and data for dicts that are
-
# friendly to Python by implementing bytes and CID support *or* data with
-
# a type
-
-
-
class Data:
-
"""
-
Data with a type.
-
"""
-
-
def __init__(self, type: str, content: dict) -> None:
-
self.type = type
-
self.content = jsonToData(content)
-
-
-
def jsonToData(json):
-
"""
-
Convert 'JSON' to 'data'
-
"""
-
if isinstance(json, dict):
-
if "$bytes" in json: # bytes shown as `$bytes` object in JSON
-
bytesInB64 = json["$bytes"]
-
return base64.b64decode(bytesInB64)
-
elif "$link" in json: # CID link
-
cidInString = json["$link"]
-
return make_cid(cidInString)
-
elif "$type" in json: # data have their lexicons
-
type = json["$type"]
-
del json["$type"] # loop if we don't del
-
return Data(type, json)
-
else: # general dict
-
result = {}
-
for item in json:
-
result[item] = jsonToData(json[item])
-
return result
-
elif isinstance(json, (list, tuple, set)): # only lists are valid
-
return [jsonToData(item) for item in json]
-
else:
-
return json
-
-
-
def dataToJson(data):
-
"""
-
Convert 'data' to 'JSON'
-
"""
-
if isinstance(data, bytes): # bytes
-
return {"$bytes": base64.b64encode(data)}
-
elif isinstance(data, (CIDv0, CIDv1)): # CID link
-
return {"$link": str(data)}
-
elif isinstance(data, dict): # general dict
-
result = {}
-
for item in data:
-
result[item] = dataToJson(data[item])
-
return result
-
elif isinstance(data, (list, tuple, set)): # only lists are valid
-
return [dataToJson(item) for item in data]
-
else:
-
return data
-
-
-
# We should need a wrapper, converts REAL json TEXT/FILE to so-called data
-
-
-
def dump(
-
obj,
-
fp,
-
*w,
-
skipkeys=False,
-
ensure_ascii=True,
-
check_circular=True,
-
allow_nan=True,
-
cls=None,
-
indent=None,
-
separators=None,
-
default=None,
-
sort_keys=False,
-
**kw
-
):
-
"""
-
json.dump() like function which replaces json to data instead
-
"""
-
json_result = dataToJson(obj)
-
json.dump(
-
json_result,
-
fp,
-
*w,
-
skipkeys=skipkeys,
-
ensure_ascii=ensure_ascii,
-
check_circular=check_circular,
-
allow_nan=allow_nan,
-
cls=cls,
-
indent=indent,
-
separators=separators,
-
default=default,
-
sort_keys=sort_keys,
-
**kw,
-
)
-
-
-
def dumps(
-
obj,
-
*w,
-
skipkeys=False,
-
ensure_ascii=True,
-
check_circular=True,
-
allow_nan=True,
-
cls=None,
-
indent=None,
-
separators=None,
-
default=None,
-
sort_keys=False,
-
**kw
-
):
-
"""
-
json.dumps() like function which replaces json to data instead
-
"""
-
json_result = dataToJson(obj)
-
json.dumps(
-
json_result,
-
*w,
-
skipkeys=skipkeys,
-
ensure_ascii=ensure_ascii,
-
check_circular=check_circular,
-
allow_nan=allow_nan,
-
cls=cls,
-
indent=indent,
-
separators=separators,
-
default=default,
-
sort_keys=sort_keys,
-
**kw,
-
)
-
-
-
def load(
-
fp,
-
*w,
-
cls=None,
-
object_hook=None,
-
parse_float=None,
-
parse_int=None,
-
parse_constant=None,
-
object_pairs_hook=None,
-
**kw
-
):
-
"""
-
json.load() like function which replaces json to data instead
-
"""
-
result = json.load(
-
fp,
-
*w,
-
cls=cls,
-
object_hook=object_hook,
-
parse_float=parse_float,
-
parse_int=parse_int,
-
parse_constant=parse_constant,
-
object_pairs_hook=object_pairs_hook,
-
**kw,
-
)
-
return jsonToData(result)
-
-
-
def loads(
-
s,
-
*w,
-
cls=None,
-
object_hook=None,
-
parse_float=None,
-
parse_int=None,
-
parse_constant=None,
-
object_pairs_hook=None,
-
**kw
-
):
-
result = json.loads(s,*w,cls=cls,object_hook=object_hook,parse_float=parse_float,parse_int=parse_int,parse_constant=parse_constant,object_pairs_hook=object_pairs_hook,**kw)
-
return jsonToData(result)
···