Drew Short
7 years ago
9 changed files with 164 additions and 2 deletions
-
1.gitignore
-
3server/atheneum/__init__.py
-
1server/atheneum/api/__init__.py
-
25server/atheneum/api/user_api.py
-
20server/atheneum/errors.py
-
52server/atheneum/service/serialization_service.py
-
43server/atheneum/service/user_service.py
-
5server/atheneum/utility/json_utility.py
-
16server/tests/api/test_user_api.py
@ -1,2 +1,3 @@ |
|||||
"""API blueprint exports.""" |
"""API blueprint exports.""" |
||||
from atheneum.api.authentication_api import AUTH_BLUEPRINT |
from atheneum.api.authentication_api import AUTH_BLUEPRINT |
||||
|
from atheneum.api.user_api import USER_BLUEPRINT |
@ -0,0 +1,25 @@ |
|||||
|
"""User API blueprint and endpoint definitions.""" |
||||
|
from flask import Blueprint, abort |
||||
|
|
||||
|
from atheneum.api.decorators import return_json |
||||
|
from atheneum.api.model import APIResponse |
||||
|
from atheneum.middleware import authentication_middleware |
||||
|
from atheneum.service import user_service |
||||
|
|
||||
|
USER_BLUEPRINT = Blueprint( |
||||
|
name='user', import_name=__name__, url_prefix='/user') |
||||
|
|
||||
|
|
||||
|
@USER_BLUEPRINT.route('/<name>', methods=['GET']) |
||||
|
@return_json |
||||
|
@authentication_middleware.require_token_auth |
||||
|
def get_user(name: str) -> APIResponse: |
||||
|
""" |
||||
|
Get a token for continued authentication. |
||||
|
|
||||
|
:return: A login token for continued authentication |
||||
|
""" |
||||
|
user = user_service.find_by_name(name) |
||||
|
if user is not None: |
||||
|
return APIResponse(user, 200) |
||||
|
return abort(404) |
@ -0,0 +1,20 @@ |
|||||
|
"""Error definitions for Atheneum.""" |
||||
|
from typing import Dict |
||||
|
|
||||
|
|
||||
|
class BaseError(RuntimeError): |
||||
|
"""Atheneum Base Error Class.""" |
||||
|
|
||||
|
def __init__( |
||||
|
self, |
||||
|
message: str = 'Unknown error', |
||||
|
extra_fields: Dict[str, str] = None) -> None: |
||||
|
"""Populate The Error Definition.""" |
||||
|
super().__init__(message) |
||||
|
self.extra_fields = extra_fields |
||||
|
|
||||
|
|
||||
|
class ValidationError(BaseError): |
||||
|
"""Atheneum Validation Error.""" |
||||
|
|
||||
|
pass |
@ -0,0 +1,52 @@ |
|||||
|
"""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] |
||||
|
ret[key] = factory() |
||||
|
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__)) |
@ -0,0 +1,16 @@ |
|||||
|
from flask.testing import FlaskClient |
||||
|
|
||||
|
from tests.conftest import AuthActions |
||||
|
|
||||
|
|
||||
|
def test_get_user_happy_path(auth: AuthActions, client: FlaskClient): |
||||
|
auth.login() |
||||
|
auth_header = auth.get_authorization_header_token() |
||||
|
result = client.get( |
||||
|
'/user/{}'.format(client.application.config['test_username']), |
||||
|
headers={ |
||||
|
auth_header[0]: auth_header[1] |
||||
|
}) |
||||
|
assert result.status_code == 200 |
||||
|
assert result.json is not None |
||||
|
assert result.json['name'] == client.application.config['test_username'] |
Write
Preview
Loading…
Cancel
Save
Reference in new issue