Browse Source

Allow transformation service to handle serialization and deserialization

merge-requests/1/head
Drew Short 6 years ago
parent
commit
e2de2699fd
  1. 54
      server/atheneum/service/serialization_service.py
  2. 100
      server/atheneum/service/transformation_service.py
  3. 52
      server/atheneum/service/user_service.py
  4. 63
      server/atheneum/service/user_token_service.py
  5. 2
      server/atheneum/utility/json_utility.py
  6. 22
      server/tests/service/test_transformation_service.py

54
server/atheneum/service/serialization_service.py

@ -1,54 +0,0 @@
"""Handle Model Serialization."""
from typing import Dict, Callable, Any, List, Optional, Type
from atheneum import errors
from atheneum.db import db
class BaseSerializer: # pylint: disable=too-few-public-methods
"""Base Model serializer."""
def __init__(self, model: Type[db.Model]) -> None:
"""Initialize the base serializer."""
self._fields: Dict[str, Callable[[db.Model], Any]] = {}
self.model = model
def serialize(self, options: Optional[List[str]]) -> Any:
"""Convert Model field and factories to dicts."""
field_factories = self._serializers()
if not options:
options = list(field_factories.keys())
ret = {}
for key in options:
if key not in field_factories:
raise errors.ValidationError(
'Invalid key: %r. Valid keys: %r.' % (
key, list(sorted(field_factories.keys()))))
factory = field_factories[key]
val = factory()
if val is not None:
ret[key] = val
return ret
def _serializers(self) -> Dict[str, Callable[[], Any]]:
"""Field definitions."""
raise NotImplementedError()
_model_serializers: Dict[str, Type[BaseSerializer]] = {}
def register_serializer(
model_name: str, model_serializer: Type[BaseSerializer]) -> None:
"""Add a model to the serializer mapping."""
_model_serializers[model_name] = model_serializer
def serialize_model(model_obj: db.Model) -> Any:
"""Lookup a Model and hand off to the serializer."""
try:
return _model_serializers[
type(model_obj).__name__](model_obj).serialize(None)
except KeyError:
raise NotImplementedError(
'{} has no registered serializers'.format(model_obj.__name__))

100
server/atheneum/service/transformation_service.py

@ -0,0 +1,100 @@
"""Handle Model Serialization."""
import logging
from typing import Dict, Callable, Any, List, Optional, Type
from atheneum import errors
from atheneum.db import db
LOGGER = logging.getLogger(__name__)
class BaseTransformer:
"""Base Model serializer."""
type: Type[db.Model]
def __init__(self, model: Type[db.Model]) -> None:
"""Initialize the base serializer."""
self._fields: Dict[str, Callable[[db.Model], Any]] = {}
self.model = model
def serialize(self, options: Optional[List[str]]) -> Any:
"""Convert Model field and factories to dicts."""
field_factories = self._serializers()
if not options:
options = list(field_factories.keys())
ret = {}
for key in options:
if key not in field_factories:
raise errors.ValidationError(
'Invalid key: %r. Valid keys: %r.' % (
key, list(sorted(field_factories.keys()))))
factory = field_factories[key]
val = factory()
if val is not None:
ret[key] = val
return ret
def deserialize(self,
json_model: dict,
options: Optional[List[str]]) -> Any:
"""Convert dict to Model."""
field_factories = self._deserializers()
if not options:
options = list(field_factories.keys())
for key in options:
if key not in field_factories:
raise errors.ValidationError(
'Invalid key: %r. Valid keys: %r.' % (
key, list(sorted(field_factories.keys()))))
factory = field_factories[key]
try:
value = json_model[key]
if value is not None:
factory(self.model, value)
except KeyError as key_error:
LOGGER.error('Unable to transform field: %s %s', key, key_error)
return self.model
def _serializers(self) -> Dict[str, Callable[[], Any]]:
"""Field definitions."""
raise NotImplementedError()
def _deserializers(
self) -> Dict[str, Callable[[db.Model, Any], None]]:
"""Field definitions."""
raise NotImplementedError()
_model_transformers: Dict[str, Type[BaseTransformer]] = {}
def register_transformer(
model_name: str, model_serializer: Type[BaseTransformer]) -> None:
"""Add a model to the serializer mapping."""
_model_transformers[model_name] = model_serializer
def serialize_model(model_obj: db.Model,
options: Optional[List[str]] = None) -> Any:
"""Lookup a Model and hand off to the serializer."""
try:
return _model_transformers[
type(model_obj).__name__](model_obj).serialize(options)
except KeyError:
raise NotImplementedError(
'{} has no registered serializers'.format(model_obj.__name__))
def deserialize_model(
model_type: str,
json_model_object: dict,
options: Optional[List[str]] = None) -> Type[db.Model]:
"""Lookup a Model and hand it off to the deserializer."""
try:
transformer = _model_transformers[model_type]
return transformer(
transformer.type()).deserialize(json_model_object, options)
except KeyError:
raise NotImplementedError(
'{} has no registered serializers'.format(model_type))

52
server/atheneum/service/user_service.py

@ -5,18 +5,31 @@ from typing import Optional, Dict, Callable, Any
from atheneum.db import db
from atheneum.model import User
from atheneum.utility import authentication_utility
from atheneum.service.serialization_service import (
BaseSerializer,
register_serializer
from atheneum.service.transformation_service import (
BaseTransformer,
register_transformer
)
from atheneum.utility import authentication_utility
LOGGER = logging.getLogger(__name__)
class UserSerializer(BaseSerializer):
class UserTransformer(BaseTransformer):
"""Serialize User model."""
type = User
def _deserializers(
self) -> Dict[str, Callable[[User, Any], None]]:
"""Define the fields and the accompanying deserializer factory."""
return {
'name': self.deserialize_name,
'creationTime': self.deserialize_creation_time,
'lastLoginTime': self.deserialize_last_login_time,
'version': self.deserialize_version,
'role': self.deserialize_role,
}
def _serializers(self) -> Dict[str, Callable[[], Any]]:
"""Define the fields and the accompanying serializer factory."""
return {
@ -31,24 +44,51 @@ class UserSerializer(BaseSerializer):
"""User name."""
return self.model.name
@staticmethod
def deserialize_name(model: User, name: str) -> None:
"""User name."""
model.name = name
def serialize_creation_time(self) -> datetime:
"""User creation time."""
return self.model.creation_time
@staticmethod
def deserialize_creation_time(
model: User, creation_time: datetime) -> None:
"""User creation time."""
model.creation_time = creation_time
def serialize_last_login_time(self) -> datetime:
"""User last login time."""
return self.model.last_login_time
@staticmethod
def deserialize_last_login_time(
model: User, last_login_time: datetime) -> None:
"""User last login time."""
model.last_login_time = last_login_time
def serialize_version(self) -> int:
"""User version."""
return self.model.version
@staticmethod
def deserialize_version(model: User, version: int) -> None:
"""User version."""
model.version = version
def serialize_role(self) -> str:
"""User role."""
return self.model.role
@staticmethod
def deserialize_role(model: User, role: str) -> None:
"""User role."""
model.role = role
register_serializer(User.__name__, UserSerializer)
register_transformer(User.__name__, UserTransformer)
def register(name: str, password: str, role: str) -> User:

63
server/atheneum/service/user_token_service.py

@ -5,15 +5,30 @@ from typing import Optional, Dict, Callable, Any
from atheneum.db import db
from atheneum.model import User, UserToken
from atheneum.service.serialization_service import (
BaseSerializer,
register_serializer
from atheneum.service.transformation_service import (
BaseTransformer,
register_transformer
)
class UserTokenSerializer(BaseSerializer):
class UserTokenTransformer(BaseTransformer):
"""Serialize User model."""
type = UserToken
def _deserializers(
self) -> Dict[str, Callable[[UserToken, Any], None]]:
"""Define the fields and the accompanying serializer factory."""
return {
'token': self.deserialize_token,
'note': self.deserialize_note,
'enabled': self.deserialize_enabled,
'expirationTime': self.deserialize_expiration_time,
'creationTime': self.deserialize_creation_time,
'lastUsageTime': self.deserialize_last_usage_time,
'version': self.deserialize_version
}
def _serializers(self) -> Dict[str, Callable[[], Any]]:
"""Define the fields and the accompanying serializer factory."""
return {
@ -30,32 +45,70 @@ class UserTokenSerializer(BaseSerializer):
"""User token."""
return self.model.token
@staticmethod
def deserialize_token(model: UserToken, token: str) -> None:
"""User token."""
model.token = token
def serialize_note(self) -> str:
"""User token note."""
return self.model.note
@staticmethod
def deserialize_note(model: UserToken, note: str) -> None:
"""User token note."""
model.note = note
def serialize_enabled(self) -> bool:
"""User token enabled."""
return self.model.enabled
@staticmethod
def deserialize_enabled(model: UserToken, enabled: bool) -> None:
"""User token enabled."""
model.enabled = enabled
def serialize_expiration_time(self) -> datetime:
"""User token expiration time."""
return self.model.expiration_time
@staticmethod
def deserialize_expiration_time(
model: UserToken, expiration_time: datetime) -> None:
"""User token expiration time."""
model.expiration_time = expiration_time
def serialize_creation_time(self) -> datetime:
"""User token creation time."""
return self.model.creation_time
@staticmethod
def deserialize_creation_time(
model: UserToken, creation_time: datetime) -> None:
"""User token creation time."""
model.creation_time = creation_time
def serialize_last_usage_time(self) -> datetime:
"""User token last usage time."""
return self.model.last_usage_time
@staticmethod
def deserialize_last_usage_time(
model: UserToken, last_usage_time: datetime) -> None:
"""User token last usage time."""
model.last_usage_time = last_usage_time
def serialize_version(self) -> int:
"""User token version."""
return self.model.version
@staticmethod
def deserialize_version(model: UserToken, version: int) -> None:
"""User token version."""
model.version = version
register_serializer(UserToken.__name__, UserTokenSerializer)
register_transformer(UserToken.__name__, UserTokenTransformer)
def generate_token() -> uuid.UUID:

2
server/atheneum/utility/json_utility.py

@ -6,7 +6,7 @@ import rfc3339
from flask.json import JSONEncoder
from atheneum.db import db
from atheneum.service.serialization_service import serialize_model
from atheneum.service.transformation_service import serialize_model
class CustomJSONEncoder(JSONEncoder):

22
server/tests/service/test_transformation_service.py

@ -0,0 +1,22 @@
from atheneum.model import UserToken
from atheneum.service.transformation_service import (
serialize_model,
deserialize_model
)
def test_serialize_model():
user_token = UserToken()
user_token.token = 'test'
result = serialize_model(user_token)
assert result is not None
assert result['token'] == 'test'
def test_deserialize_model():
user_token_json = {
'token': 'test'
}
result = deserialize_model('UserToken', user_token_json)
assert result is not None
assert result.token == 'test'
Loading…
Cancel
Save