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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
# Copyright (c) 2022 Tulir Asokan
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from typing import Type, TypeVar, Union
from abc import ABC, abstractmethod
from enum import Enum
import json
from ..primitive import JSON
SerializableSubtype = TypeVar("SerializableSubtype", bound="SerializableAttrs")
class Serializable:
"""Serializable is the base class for types with custom JSON serializers."""
def serialize(self) -> JSON:
"""Convert this object into objects directly serializable with `json`."""
raise NotImplementedError()
@classmethod
def deserialize(cls: Type[SerializableSubtype], raw: JSON) -> SerializableSubtype:
"""Convert the given data parsed from JSON into an object of this type."""
raise NotImplementedError()
def json(self) -> str:
"""Serialize this object and dump the output as JSON."""
return json.dumps(self.serialize())
@classmethod
def parse_json(cls: Type[SerializableSubtype], data: Union[str, bytes]) -> SerializableSubtype:
"""Parse the given string as JSON and deserialize the result into this type."""
return cls.deserialize(json.loads(data))
class SerializerError(Exception):
"""
SerializerErrors are raised if something goes wrong during serialization or deserialization.
"""
pass
class UnknownSerializationError(SerializerError):
def __init__(self) -> None:
super().__init__("Unknown serialization error")
class AbstractSerializable(ABC, Serializable):
"""
An abstract Serializable that adds ``@abstractmethod`` decorators.
"""
@abstractmethod
def serialize(self) -> JSON:
pass
@classmethod
@abstractmethod
def deserialize(cls: Type[SerializableSubtype], raw: JSON) -> SerializableSubtype:
pass
class SerializableEnum(Serializable, Enum):
"""
A simple Serializable implementation for Enums.
Examples:
>>> class MyEnum(SerializableEnum):
... FOO = "foo value"
... BAR = "hmm"
>>> MyEnum.FOO.serialize()
"foo value"
>>> MyEnum.BAR.json()
'"hmm"'
"""
def __init__(self, _) -> None:
"""
A fake ``__init__`` to stop the type checker from complaining.
Enum's ``__new__`` overrides this.
"""
super().__init__()
def serialize(self) -> str:
"""
Convert this object into objects directly serializable with `json`, i.e. return the value
set to this enum value.
"""
return self.value
@classmethod
def deserialize(cls: Type[SerializableSubtype], raw: str) -> SerializableSubtype:
"""
Convert the given data parsed from JSON into an object of this type, i.e. find the enum
value for the given string using ``cls(raw)``.
"""
try:
return cls(raw)
except ValueError as e:
raise SerializerError() from e
def __str__(self):
return str(self.value)
def __repr__(self):
return f"{self.__class__.__name__}.{self.name}"
|