From 199cad3f43a27fe920f6b982a5ba00ece994f0bd Mon Sep 17 00:00:00 2001 From: Ethan Paul Date: Sun, 23 Feb 2020 16:51:22 -0500 Subject: [PATCH] WIP add initial serializers --- keyosk/fields.py | 49 +++++++++++++++++++++++++++++++ keyosk/serializers/__init__.py | 0 keyosk/serializers/account.py | 25 ++++++++++++++++ keyosk/serializers/account_acl.py | 13 ++++++++ keyosk/serializers/domain.py | 38 ++++++++++++++++++++++++ 5 files changed, 125 insertions(+) create mode 100644 keyosk/serializers/__init__.py create mode 100644 keyosk/serializers/account.py create mode 100644 keyosk/serializers/account_acl.py create mode 100644 keyosk/serializers/domain.py diff --git a/keyosk/fields.py b/keyosk/fields.py index b185954..8f74dfa 100644 --- a/keyosk/fields.py +++ b/keyosk/fields.py @@ -1,7 +1,9 @@ """Custom fields for handing de/serializing custom data types""" +from datetime import datetime from enum import Enum from pathlib import Path from typing import Any +from typing import Sequence from typing import Type from typing import Union @@ -80,3 +82,50 @@ class PathString(msh.fields.String): def _deserialize(self, value: str, attr, data, **kwargs) -> Path: return Path(super()._deserialize(value, attr, data, **kwargs)) + + +class Epoch(msh.fields.Field): + """Translate between datetime objects and an integer reperesenting epoch time""" + + def _serialize(self, value: Union[datetime, int], attr, obj, **kwargs) -> int: + """Serialize a datetime object to an integer""" + + if isinstance(value, datetime): + return int(value.timestamp()) + return value + + def _deserialize(self, value: int, attr, data, **kwargs) -> datetime: + """Deserialize an integer to a datetime object""" + + try: + if value < 0: + raise msh.ValidationError(f"Invalid epoch time '{value}'") + except TypeError: + raise msh.ValidationError( + f"Invalid epoch value '{value}' of type '{type(value)}'" + ) + return datetime.fromtimestamp(int(value)) + + +class RawMultiType(msh.fields.Raw): + """Like raw, but limits the types the value can be to a specified list + + .. note:: Like the Marshmallow :class:`Raw` field, no additional validation is done on the value + beyond checking its type. + """ + + def __init__(self, types: Sequence[type], *args, **kwargs): + """Initialize the field + + :param types: Sequence of types that the value to de/serialize can be + """ + super().__init__(*args, **kwargs) + self._types = tuple(types) + + def _deserialize(self, value: Any, attr, data, **kwargs) -> Any: + """Deserialize the value, raising a validation error if it is not of an allowed type""" + if not isinstance(value, self._types): + raise msh.ValidationError( + f"Invalid value '{value}' of type {type(value)}: expected one of {self._types}" + ) + return value diff --git a/keyosk/serializers/__init__.py b/keyosk/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keyosk/serializers/account.py b/keyosk/serializers/account.py new file mode 100644 index 0000000..5699ac6 --- /dev/null +++ b/keyosk/serializers/account.py @@ -0,0 +1,25 @@ +from typing import List + +import marshmallow as msh + +from keyosk import fields as custom_fields +from keyosk.serializers.account_acl import AccountACLSerializer + + +class AccountSerializer(msh.Schema): + + uuid = msh.fields.UUID(required=True) + created = custom_fields.Epoch(required=True) + updated = custom_fields.Epoch(required=True) + username = msh.fields.String(required=True) + enabled = msh.fields.Boolean(required=True) + extras = msh.fields.Dict( + keys=msh.fields.String(), + values=custom_fields.RawMultiType([int, float, bool, str, None]), + required=True, + ) + acls = msh.fields.List(msh.fields.Nested(AccountACLSerializer), required=True) + + @staticmethod + def creation_fields() -> List[str]: + return ["uuid", "created", "updated"] diff --git a/keyosk/serializers/account_acl.py b/keyosk/serializers/account_acl.py new file mode 100644 index 0000000..94302ca --- /dev/null +++ b/keyosk/serializers/account_acl.py @@ -0,0 +1,13 @@ +import marshmallow as msh + + +class AccountACLSerializer(msh.Schema): + + access_list = msh.fields.String(required=True, data_key="access-list") + permission = msh.fields.String(required=True) + with_server_secret = msh.fields.Boolean( + required=True, data_key="with-server-secret" + ) + with_client_secret = msh.fields.Boolean( + required=True, data_key="with-client-secret" + ) diff --git a/keyosk/serializers/domain.py b/keyosk/serializers/domain.py new file mode 100644 index 0000000..e74d1cc --- /dev/null +++ b/keyosk/serializers/domain.py @@ -0,0 +1,38 @@ +import marshmallow as msh + + +class DomainSerializer(msh.Schema): + + uuid = msh.fields.UUID(required=True) + created = msh.fields.DateTime(required=True) + updated = msh.fields.DateTime(required=True) + name = msh.fields.String(required=True) + audience = msh.fields.String(required=True) + title = msh.fields.String(required=True, allow_none=True) + description = msh.fields.String(required=True, allow_none=True) + contact = msh.fields.String(required=True, allow_none=True) + enabled = msh.fields.Boolean(required=True) + enable_client_set_auth = msh.fields.Boolean( + required=True, data_key="enable-client-set-auth" + ) + enable_server_set_auth = msh.fields.Boolean( + required=True, data_key="enable-server-set-auth" + ) + enable_refresh = msh.fields.Boolean(required=True, data_key="enable-refresh") + lifespan_access = msh.fields.Boolean(required=True, data_key="lifespan-access") + lifespan_refresh = msh.fields.Boolean(required=True, data_key="lifespan-refresh") + access_lists = msh.fields.List() + + # created = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow) + # updated = peewee.DateTimeField(null=False, default=datetime.datetime.utcnow) + # name = peewee.CharField(null=False, unique=True) + # audience = peewee.CharField(null=False, unique=True) + # title = peewee.CharField(null=True) + # description = peewee.CharField(null=True) + # contact = peewee.CharField(null=True) + # enabled = peewee.BooleanField(null=False) + # enable_client_set_auth = peewee.BooleanField(null=False) + # enable_server_set_auth = peewee.BooleanField(null=False) + # enable_refresh = peewee.BooleanField(null=False) + # lifespan_access = peewee.IntegerField(null=False) + # lifespan_refresh = peewee.IntegerField(null=False)