An ebook/comic library service and web client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
4.4 KiB

  1. """Handle Model Serialization."""
  2. import logging
  3. import re
  4. from typing import Dict, Callable, Any, List, Optional, Type
  5. from atheneum import errors
  6. from atheneum.db import db
  7. LOGGER = logging.getLogger(__name__)
  8. class BaseTransformer:
  9. """Base Model serializer."""
  10. type: Type[db.Model]
  11. def __init__(self, model: Type[db.Model]) -> None:
  12. """Initialize the base serializer."""
  13. self.model = model
  14. def serialize(self, options: Optional[List[str]]) -> Any:
  15. """Convert Model field and factories to dicts."""
  16. field_factories = self._serializers()
  17. if not options:
  18. options = list(field_factories.keys())
  19. ret = {}
  20. for key in options:
  21. if key not in field_factories:
  22. raise errors.ValidationError(
  23. 'Invalid key: %r. Valid keys: %r.' % (
  24. key, list(sorted(field_factories.keys()))))
  25. factory = field_factories[key]
  26. val = factory()
  27. if val is not None:
  28. ret[key] = val
  29. return ret
  30. def deserialize(self,
  31. json_model: Optional[dict],
  32. options: Optional[List[str]]) -> Any:
  33. """
  34. Convert dict to Model.
  35. If the dict is None or empty, return an empty model.
  36. :param json_model: The dict representing the serialized model
  37. :param options: the fields to deserialize
  38. :return: an instance of the model
  39. """
  40. if json_model is None or not json_model:
  41. return self.model
  42. field_factories = self._deserializers()
  43. if not options:
  44. options = list(field_factories.keys())
  45. for key in options:
  46. if key not in field_factories:
  47. raise errors.ValidationError(
  48. 'Invalid key: %r. Valid keys: %r.' % (
  49. key, list(sorted(field_factories.keys()))))
  50. factory = field_factories[key]
  51. try:
  52. value = json_model[key]
  53. if value is not None:
  54. factory(self.model, value)
  55. except KeyError as key_error:
  56. LOGGER.error(
  57. 'Unable to transform field: %s %s', key, key_error)
  58. return self.model
  59. def _serializers(self) -> Dict[str, Callable[[], Any]]:
  60. """Field definitions."""
  61. raise NotImplementedError()
  62. def _deserializers(
  63. self) -> Dict[str, Callable[[db.Model, Any], None]]:
  64. """Field definitions."""
  65. raise NotImplementedError()
  66. _model_transformers: Dict[str, Type[BaseTransformer]] = {}
  67. def register_transformer(
  68. model_serializer: Type[BaseTransformer]) -> Type[BaseTransformer]:
  69. """Add a model to the serializer mapping."""
  70. model_name = model_serializer.type.__name__
  71. if model_name not in _model_transformers:
  72. _model_transformers[model_name] = model_serializer
  73. else:
  74. raise KeyError(
  75. ' '.join([
  76. 'A transformer for type "{}" already exists with class "{}".',
  77. 'Cannot register a new transformer with class "{}"'
  78. ]).format(
  79. model_name,
  80. _model_transformers[model_name].__name__,
  81. model_serializer.__name__))
  82. return model_serializer
  83. def serialize_model(model_obj: db.Model,
  84. options: Optional[List[str]] = None) -> Any:
  85. """Lookup a Model and hand off to the serializer."""
  86. try:
  87. return _model_transformers[
  88. type(model_obj).__name__](model_obj).serialize(options)
  89. except KeyError:
  90. raise NotImplementedError(
  91. '{} has no registered serializers'.format(model_obj.__name__))
  92. def deserialize_model(
  93. model_type: Type[db.Model],
  94. json_model_object: Optional[dict],
  95. options: Optional[List[str]] = None) -> db.Model:
  96. """Lookup a Model and hand it off to the deserializer."""
  97. try:
  98. transformer = _model_transformers[model_type.__name__]
  99. return transformer(
  100. transformer.type()).deserialize(json_model_object, options)
  101. except KeyError:
  102. raise NotImplementedError(
  103. '{} has no registered serializers'.format(model_type))
  104. def convert_key_from_json(key: str) -> str:
  105. """Convert a key from camelCase."""
  106. substitute = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key)
  107. return re.sub('([a-z0-9])([A-Z])', r'\1_\2', substitute).lower()