Source code for arxiv.canonical.serialize.decoder
"""Provides a :class:`.CanonicalDecoder` for domain objects."""
import json
from datetime import datetime, date
from enum import Enum
from typing import Any, Union, List, Dict, GenericMeta
from typing import TypingMeta # type: ignore ; it's really there...
from uuid import UUID
from backports.datetime_fromisoformat import MonkeyPatch
from .. import domain
MonkeyPatch.patch_fromisoformat()
[docs]class CanonicalDecoder(json.JSONDecoder):
"""Decode domain objects."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Pass :func:`object_hook` to the base constructor."""
kwargs['object_hook'] = kwargs.get('object_hook', self.object_hook)
super(CanonicalDecoder, self).__init__(*args, **kwargs)
def _try_isoparse(self, value: Any) -> Any:
"""Attempt to parse a value as an ISO8601 datetime."""
if type(value) is not str:
return value
try:
return date.fromisoformat(value) # type: ignore ; pylint: disable=no-member
except ValueError:
try:
return datetime.fromisoformat(value) # type: ignore ; pylint: disable=no-member
except ValueError:
return value
[docs] def object_hook(self, obj: dict, **extra: Any) -> Any: # pylint: disable=method-hidden
"""Decode domain objects in this package."""
if isinstance(obj, dict):
for key, value in obj.items():
if type(value) is list:
obj[key] = [self._try_isoparse(v) for v in value]
else:
obj[key] = self._try_isoparse(value)
# Look for and instantiate the domain class that corresponds to the
# stated type of the data.
obj_type = obj.pop('@type', None)
if obj_type is None:
return obj
for domain_class in domain.domain_classes:
if domain_class.__name__ == obj_type:
# Look for easy wins on casting field data to the correct
# type. The main use-case is for enums.
for field, ftype in domain_class.__annotations__.items(): # pylint: disable=protected-access
# These are things like Union, List, etc that don't
# have a concrete type. Too hard to take this on.
if isinstance(ftype, GenericMeta) \
or isinstance(type(ftype), TypingMeta):
continue
# Otherwise, this is a concrete type. We can try
# to cast here.
if field in obj \
and not isinstance(obj[field], ftype):
obj[field] = ftype(obj[field])
return domain_class(**obj)
return obj