Skip to content

Commit

Permalink
Platforms (#114)
Browse files Browse the repository at this point in the history
* port platforms

* add retrieve_object to reduce duplicated code and validate platforms and users on update and delete

* lint

* same logic as fast-agave

* version

* remove duplicated code

* bump version
  • Loading branch information
felipao-mx authored Mar 3, 2022
1 parent d4719bd commit b5f595b
Show file tree
Hide file tree
Showing 15 changed files with 344 additions and 95 deletions.
80 changes: 56 additions & 24 deletions agave/blueprints/rest_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mimetypes
from typing import Optional, Type, cast
from typing import Any, Optional, Type, cast
from urllib.parse import urlencode

from chalice import Blueprint, NotFoundError, Response
Expand Down Expand Up @@ -27,6 +27,10 @@ def delete(self, path: str, **kwargs):
def current_user_id(self):
return self.current_request.user_id

@property
def current_platform_id(self):
return self.current_request.platform_id

def user_id_filter_required(self):
"""
This method is required to be implemented with your own business logic.
Expand All @@ -36,6 +40,36 @@ def user_id_filter_required(self):
'this method should be override'
) # pragma: no cover

def platform_id_filter_required(self):
"""
This method is required to be implemented with your own business logic.
You are responsible of determining when `user_id` filter is required.
"""
raise NotImplementedError(
'this method should be override'
) # pragma: no cover

def retrieve_object(self, resource_class: Any, resource_id: str) -> Any:
resource_id = (
self.current_user_id if resource_id == 'me' else resource_id
)
query = Q(id=resource_id)
if self.platform_id_filter_required() and hasattr(
resource_class.model, 'platform_id'
):
query = query & Q(platform_id=self.current_platform_id)

if self.user_id_filter_required() and hasattr(
resource_class.model, 'user_id'
):
query = query & Q(user_id=self.current_user_id)

try:
data = resource_class.model.objects.get(query)
except DoesNotExist:
raise NotFoundError('Not valid id')
return data

def validate(self, validation_type: Type[BaseModel]):
"""This decorator validate the request body using a custom pydantyc model
If validation fails return a BadRequest response with details
Expand Down Expand Up @@ -103,12 +137,8 @@ def wrapper_resource_class(cls):

@copy_attributes(cls)
def delete(id: str):
try:
model = cls.model.objects.get(id=id)
except DoesNotExist:
raise NotFoundError('Not valid id')
else:
return cls.delete(model)
model = self.retrieve_object(cls, id)
return cls.delete(model)

route(delete)

Expand All @@ -125,13 +155,11 @@ def update(id: str):
params = self.current_request.json_body or dict()
try:
data = cls.update_validator(**params)
model = cls.model.objects.get(id=id)
except ValidationError as e:
return Response(e.json(), status_code=400)
except DoesNotExist:
raise NotFoundError('Not valid id')
else:
return cls.update(model, data)

model = self.retrieve_object(cls, id)
return cls.update(model, data)

route(update)

Expand All @@ -149,18 +177,12 @@ def retrieve(id: str):
The most of times this implementation is enough and is not
necessary define a custom "retrieve" method
"""
try:
id_query = Q(id=id)
if self.user_id_filter_required():
id_query = id_query & Q(user_id=self.current_user_id)
data = cls.model.objects.get(id_query)
except DoesNotExist:
raise NotFoundError('Not valid id')
obj = self.retrieve_object(cls, id)

# This case is when the return is not an application/$
# but can be some type of file such as image, xml, zip or pdf
if hasattr(cls, 'download'):
file = cls.download(data)
file = cls.download(obj)
mimetype = cast(
str, self.current_request.headers.get('accept')
)
Expand All @@ -177,9 +199,9 @@ def retrieve(id: str):
status_code=200,
)
elif hasattr(cls, 'retrieve'):
result = cls.retrieve(data)
result = cls.retrieve(obj)
else:
result = data.to_dict()
result = obj.to_dict()

return result

Expand Down Expand Up @@ -209,9 +231,17 @@ def query():
query_params = cls.query_validator(**params)
except ValidationError as e:
return Response(e.json(), status_code=400)
# Set user_id request as query param
if self.user_id_filter_required():

if self.platform_id_filter_required() and hasattr(
cls.model, 'platform_id'
):
query_params.platform_id = self.current_platform_id

if self.user_id_filter_required() and hasattr(
cls.model, 'user_id'
):
query_params.user_id = self.current_user_id

filters = cls.get_query_filter(query_params)
if (
hasattr(query_params, 'active')
Expand Down Expand Up @@ -257,6 +287,8 @@ def _all(query: QueryParams, filters: Q):
params = query.dict()
if self.user_id_filter_required():
params.pop('user_id')
if self.platform_id_filter_required():
params.pop('platform_id')
next_page_uri = f'{path}?{urlencode(params)}'
return dict(items=item_dicts, next_page_uri=next_page_uri)

Expand Down
2 changes: 1 addition & 1 deletion agave/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.1.8'
__version__ = '0.2.0'
12 changes: 11 additions & 1 deletion examples/chalicelib/blueprints/authed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from chalice import Blueprint

from ...config import TEST_DEFAULT_PLATFORM_ID, TEST_DEFAULT_USER_ID


class AuthedBlueprint(Blueprint):
"""
Expand All @@ -28,7 +30,8 @@ def decorator(user_handler: Callable):
def authed_handler(*args, **kwargs):
# your authentication logic goes here
# before execute `user_handler` function.
self.current_request.user_id = 'US123456789'
self.current_request.user_id = TEST_DEFAULT_USER_ID
self.current_request.platform_id = TEST_DEFAULT_PLATFORM_ID
return user_handler(*args, **kwargs)

self._register_handler( # type: ignore
Expand Down Expand Up @@ -61,3 +64,10 @@ def user_id_filter_required(self):
:return:
"""
return False

def platform_id_filter_required(self):
"""
It overrides `RestApiBlueprint.platform_id_filter_required()` method.
:return:
"""
return False
4 changes: 3 additions & 1 deletion examples/chalicelib/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
__all__ = ['Account', 'Card', 'Transaction', 'File']
__all__ = ['Account', 'Biller', 'Card', 'Transaction', 'File', 'User']

from .accounts import Account
from .billers import Biller
from .cards import Card
from .files import File
from .transactions import Transaction
from .users import User
1 change: 1 addition & 0 deletions examples/chalicelib/models/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ class Account(BaseModel, Document):
id = StringField(primary_key=True, default=uuid_field('AC'))
name = StringField(required=True)
user_id = StringField(required=True)
platform_id = StringField(required=True)
created_at = DateTimeField()
deactivated_at = DateTimeField()
12 changes: 12 additions & 0 deletions examples/chalicelib/models/billers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import datetime as dt

from mongoengine import DateTimeField, Document, StringField

from agave.models import BaseModel
from agave.models.helpers import uuid_field


class Biller(BaseModel, Document):
id = StringField(primary_key=True, default=uuid_field('BL'))
created_at = DateTimeField(default=dt.datetime.utcnow)
name = StringField(required=True)
13 changes: 13 additions & 0 deletions examples/chalicelib/models/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import datetime as dt

from mongoengine import DateTimeField, Document, StringField

from agave.models import BaseModel
from agave.models.helpers import uuid_field


class User(BaseModel, Document):
id = StringField(primary_key=True, default=uuid_field('US'))
created_at = DateTimeField(default=dt.datetime.utcnow)
name = StringField(required=True)
platform_id = StringField(required=True)
4 changes: 3 additions & 1 deletion examples/chalicelib/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
__all__ = ['app', 'Account', 'Card', 'File', 'Transaction']
__all__ = ['app', 'Account', 'Biller', 'Card', 'File', 'Transaction', 'User']

from .accounts import Account
from .base import app
from .billers import Biller
from .cards import Card
from .files import File
from .transactions import Transaction
from .users import User
4 changes: 2 additions & 2 deletions examples/chalicelib/resources/accounts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import datetime as dt

from chalice import NotFoundError, Response
from mongoengine import DoesNotExist
from chalice import Response

from agave.filters import generic_query

Expand All @@ -23,6 +22,7 @@ def create(request: AccountRequest) -> Response:
account = AccountModel(
name=request.name,
user_id=app.current_user_id,
platform_id=app.current_platform_id,
)
account.save()
return Response(account.to_dict(), status_code=201)
Expand Down
12 changes: 12 additions & 0 deletions examples/chalicelib/resources/billers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from agave.filters import generic_query

from ..models import Biller as BillerModel
from ..validators import BillerQuery
from .base import app


@app.resource('/billers')
class Biller:
model = BillerModel
query_validator = BillerQuery
get_query_filter = generic_query
12 changes: 12 additions & 0 deletions examples/chalicelib/resources/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from agave.filters import generic_query

from ..models import User as UserModel
from ..validators import UserQuery
from .base import app


@app.resource('/users')
class User:
model = UserModel
query_validator = UserQuery
get_query_filter = generic_query
9 changes: 9 additions & 0 deletions examples/chalicelib/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@
class AccountQuery(QueryParams):
name: Optional[str] = None
user_id: Optional[str] = None
platform_id: Optional[str] = None
active: Optional[bool] = None


class TransactionQuery(QueryParams):
user_id: Optional[str] = None


class BillerQuery(QueryParams):
name: str


class UserQuery(QueryParams):
platform_id: str


class AccountRequest(BaseModel):
name: str

Expand Down
4 changes: 4 additions & 0 deletions examples/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
TEST_DEFAULT_USER_ID = 'US123456789'
TEST_DEFAULT_PLATFORM_ID = 'PT123456'
TEST_SECOND_USER_ID = 'US987654321'
TEST_SECOND_PLATFORM_ID = 'PT987654321'
Loading

0 comments on commit b5f595b

Please sign in to comment.