Source code for arxiv.canonical.serialize.encoder

"""Provides a :class:`.CanonicalEncoder` for domain objects."""

import json
import re

from datetime import datetime, date
from enum import Enum
from typing import Any, Union, List, Dict, Type
from uuid import UUID

from backports.datetime_fromisoformat import MonkeyPatch

from .. import classic
from .. import domain


MonkeyPatch.patch_fromisoformat()


def _camel_to_snake(camel: str) -> str:
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()


[docs]class CanonicalEncoder(json.JSONEncoder): """Encodes domain objects in this package for serialization."""
[docs] def unpack(self, obj: Any) -> Any: """Recursively search for domain objects, and unpack them to dicts.""" if isinstance(obj, dict): return {self.unpack(key): self.unpack(value) for key, value in obj.items()} elif isinstance(obj, list): return [self.unpack(value) for value in obj] elif isinstance(obj, domain.CanonicalBase): type_snake = _camel_to_snake(type(obj).__name__) unpack_obj = getattr(self, f'unpack_{type_snake}', self.unpack_default) data = unpack_obj(obj) data['@type'] = type(obj).__name__ return data elif isinstance(obj, Enum): return obj.value elif isinstance(obj, tuple): return tuple(self.unpack(value) for value in obj) elif isinstance(obj, (date, datetime)): return obj.isoformat() return obj
[docs] def encode(self, obj: Any) -> Any: """Serialize objects in this application domain.""" return super(CanonicalEncoder, self).encode(self.unpack(obj))
[docs] def unpack_default(self, obj: Any) -> Dict: """Fallback unpack method for any domain object.""" return {key: self.unpack(getattr(obj, key)) for key in obj.__annotations__.keys()}
[docs] def unpack_canonical_file(self, obj: domain.CanonicalFile) -> Dict: """Unpack a :class:`.domain.File`.""" return {key: self.unpack(getattr(obj, key)) for key in obj.__annotations__.keys() if key != 'content'}
[docs] def unpack_uuid(self, obj: UUID) -> Dict: return {'hex': obj.hex}