Add CBOR support.

Holy cow, the freaking CBOR library is too hard to extend lol

Changed files
+152 -6
src
atpasser
+1 -1
README.md
···
-
# ATPasser
+
# ATPasser ╬<!
A simple library for the [Authenticated Transfer Protocol](https://atproto.com/specs/atp) (AT Protocol or atproto for short).
+137
src/atpasser/data/cbor.py
···
+
from datetime import tzinfo
+
import typing
+
import cbor2
+
import cid
+
+
from .data import dataHook, Data
+
+
+
def tagHook(decoder: cbor2.CBORDecoder, tag: cbor2.CBORTag, shareable_index=None):
+
"""
+
A simple tag hook for CID support.
+
"""
+
return cid.from_bytes(tag.value) if tag.tag == 42 else tag
+
+
+
class CBOREncoder(cbor2.CBOREncoder):
+
"""
+
Wrapper of cbor2.CBOREncoder.
+
"""
+
+
def __init__(
+
self,
+
fp: typing.IO[bytes],
+
datetime_as_timestamp: bool = False,
+
timezone: tzinfo | None = None,
+
value_sharing: bool = False,
+
default: (
+
typing.Callable[[cbor2.CBOREncoder, typing.Any], typing.Any] | None
+
) = None,
+
canonical: bool = False,
+
date_as_datetime: bool = False,
+
string_referencing: bool = False,
+
indefinite_containers: bool = False,
+
):
+
super().__init__(
+
fp,
+
datetime_as_timestamp,
+
timezone,
+
value_sharing,
+
default,
+
canonical,
+
date_as_datetime,
+
string_referencing,
+
indefinite_containers,
+
)
+
+
@cbor2.shareable_encoder
+
def cidOrDataEncoder(self: cbor2.CBOREncoder, value: cid.CIDv0 | cid.CIDv1 | Data):
+
"""
+
Encode CID or Data to CBOR Tag.
+
"""
+
if isinstance(value, (cid.CIDv0, cid.CIDv1)):
+
self.encode(cbor2.CBORTag(42, value.encode()))
+
elif isinstance(value, Data):
+
self.encode(value.data())
+
+
+
def _cborObjectHook(decoder: cbor2.CBORDecoder, value):
+
return dataHook(value)
+
+
+
class CBORDecoder(cbor2.CBORDecoder):
+
"""
+
Wrapper of cbor2.CBORDecoder.
+
"""
+
+
def __init__(
+
self,
+
fp: typing.IO[bytes],
+
tag_hook: (
+
typing.Callable[[cbor2.CBORDecoder, cbor2.CBORTag], typing.Any] | None
+
) = tagHook,
+
object_hook: (
+
typing.Callable[
+
[cbor2.CBORDecoder, dict[typing.Any, typing.Any]], typing.Any
+
]
+
| None
+
) = _cborObjectHook,
+
str_errors: typing.Literal["strict", "error", "replace"] = "strict",
+
):
+
super().__init__(fp, tag_hook, object_hook, str_errors)
+
+
+
# Make things for CBOR again.
+
+
from io import BytesIO
+
+
+
def dumps(
+
obj: object,
+
datetime_as_timestamp: bool = False,
+
timezone: tzinfo | None = None,
+
value_sharing: bool = False,
+
default: typing.Callable[[cbor2.CBOREncoder, typing.Any], typing.Any] | None = None,
+
canonical: bool = False,
+
date_as_datetime: bool = False,
+
string_referencing: bool = False,
+
indefinite_containers: bool = False,
+
) -> bytes:
+
with BytesIO() as fp:
+
CBOREncoder(
+
fp,
+
datetime_as_timestamp=datetime_as_timestamp,
+
timezone=timezone,
+
value_sharing=value_sharing,
+
default=default,
+
canonical=canonical,
+
date_as_datetime=date_as_datetime,
+
string_referencing=string_referencing,
+
indefinite_containers=indefinite_containers,
+
).encode(obj)
+
return fp.getvalue()
+
+
+
def dump(
+
obj: object,
+
fp: typing.IO[bytes],
+
datetime_as_timestamp: bool = False,
+
timezone: tzinfo | None = None,
+
value_sharing: bool = False,
+
default: typing.Callable[[cbor2.CBOREncoder, typing.Any], typing.Any] | None = None,
+
canonical: bool = False,
+
date_as_datetime: bool = False,
+
string_referencing: bool = False,
+
indefinite_containers: bool = False,
+
) -> None:
+
CBOREncoder(
+
fp,
+
datetime_as_timestamp=datetime_as_timestamp,
+
timezone=timezone,
+
value_sharing=value_sharing,
+
default=default,
+
canonical=canonical,
+
date_as_datetime=date_as_datetime,
+
string_referencing=string_referencing,
+
indefinite_containers=indefinite_containers,
+
).encode(obj)
+14 -5
src/atpasser/data/wrapper.py
···
# Screw it. I have to make 4 `json`-like functions.
-
def _dataDecorator(func):
+
def _dataDecoratorForDump(func):
@functools.wraps(func)
def wrapper(obj, *args, **kwargs):
kwargs.setdefault("cls", DataEncoder)
···
return wrapper
-
dump = _dataDecorator(json.dump)
-
dumps = _dataDecorator(json.dumps)
-
load = _dataDecorator(json.load)
-
loads = _dataDecorator(json.loads)
+
def _dataDecoratorForLoad(func):
+
@functools.wraps(func)
+
def wrapper(obj, *args, **kwargs):
+
kwargs.setdefault("cls", DataDecoder)
+
return func(obj, *args, **kwargs)
+
+
return wrapper
+
+
+
dump = _dataDecoratorForDump(json.dump)
+
dumps = _dataDecoratorForDump(json.dumps)
+
load = _dataDecoratorForLoad(json.load)
+
loads = _dataDecoratorForLoad(json.loads)
"""
Wrapper of the JSON functions to support ATProto data.
"""