Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f90ec02
draft
felipao-mx Dec 9, 2020
e67aadc
filterer
felipao-mx Dec 10, 2020
85d2c25
rest-api-sin-codigo-mongo
Gleekzone Dec 11, 2020
dfe42ab
required-changes: implementar funciones en rest-api, aun faltan mas c…
Gleekzone Dec 12, 2020
450fea0
fix: change time assignation logic
Gleekzone Dec 15, 2020
8978c19
format
Gleekzone Dec 15, 2020
1accf3e
fix: add functions to models
Gleekzone Dec 16, 2020
4fed776
fix: se modifican nombres de funciones, se agregan nuevas formas de o…
Gleekzone Dec 16, 2020
1d49916
fix: se agrega exc y se elimina Exception
Gleekzone Dec 16, 2020
43f7203
fix: format
Gleekzone Dec 16, 2020
b3b593b
fix-lint
Gleekzone Dec 16, 2020
487b00d
fix: change-required
Gleekzone Dec 17, 2020
72b9666
fix: return int
Gleekzone Dec 17, 2020
48fd0af
list
Gleekzone Dec 21, 2020
95e47c8
remove-import
Gleekzone Dec 21, 2020
ef2849e
fixes
Gleekzone Dec 21, 2020
86f8a12
format
Gleekzone Dec 21, 2020
c9aa577
format
felipao-mx Dec 21, 2020
e6f4d01
test
felipao-mx Dec 21, 2020
9b9bb02
lint
felipao-mx Dec 21, 2020
5433433
lint
felipao-mx Dec 21, 2020
c685d85
missing test
felipao-mx Dec 21, 2020
aebbfb8
test: add-more-test
Gleekzone Dec 22, 2020
b81f1a7
init-models-missing-lines
Gleekzone Dec 22, 2020
7c93282
rename-test
Gleekzone Dec 22, 2020
8b13889
rebase-main
Gleekzone Dec 22, 2020
fbc7c38
add-requirements-test
Gleekzone Dec 22, 2020
3869cdc
fix: add-filter-import
Gleekzone Dec 22, 2020
3a706b8
more-changes-required
Gleekzone Dec 22, 2020
2103455
fix: syntax, type, exc, filters
Gleekzone Dec 22, 2020
a5e981e
fix: add typehint
Gleekzone Dec 22, 2020
e3c02ac
fix: format
Gleekzone Dec 22, 2020
5130127
fix: return statements
Gleekzone Dec 23, 2020
8457644
fix: return items, has_more
Gleekzone Dec 23, 2020
0d412e6
fix: constant-excluded
Gleekzone Dec 23, 2020
724abd4
remove-typehint-Any
Gleekzone Dec 23, 2020
37f7e6e
fix: remove more than one line in a try block
Gleekzone Dec 23, 2020
51996ae
type-hint-list
Gleekzone Dec 23, 2020
54383c9
tests review
felipao-mx Dec 24, 2020
ed5b88e
tests
felipao-mx Dec 24, 2020
48a5c6f
minor fixes
felipao-mx Dec 24, 2020
5d00bea
extras_requires
felipao-mx Dec 24, 2020
03f0eed
fix extras_requires
felipao-mx Dec 24, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 23 additions & 24 deletions agave/blueprints/rest_api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from typing import Optional, Type
from typing import Type
from urllib.parse import urlencode

from chalice import Blueprint, NotFoundError, Response
from cuenca_validations.types import QueryParams
from mongoengine import DoesNotExist, Q
from pydantic import BaseModel, ValidationError

from ..exc import DoesNotExist
from .decorators import copy_attributes


Expand Down Expand Up @@ -112,9 +112,10 @@ 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)
try:
model = cls.model.retrieve(id=id)
except DoesNotExist:
raise NotFoundError('Not valid id')
else:
Expand All @@ -140,14 +141,15 @@ def retrieve(id: str):
# at the moment, there are no resources with a custom
# retrieve method
return cls.retrieve(id) # pragma: no cover

user_id = None
if self.user_id_filter_required():
user_id = self.current_user_id
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)
data = cls.model.retrieve(id, user_id=user_id)
except DoesNotExist:
raise NotFoundError('Not valid id')
return data.to_dict()
return data.dict()

@self.get(path)
@copy_attributes(cls)
Expand Down Expand Up @@ -183,37 +185,34 @@ def query():
return _count(filters)
return _all(query_params, filters)

def _count(filters: Q):
count = cls.model.objects.filter(filters).count()
def _count(filters):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type hint

count = cls.model.count(filters)
return dict(count=count)

def _all(query: QueryParams, filters: Q):
def _all(query: QueryParams, filters):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type hint a filters

if query.limit:
limit = min(query.limit, query.page_size)
query.limit = max(0, query.limit - limit) # type: ignore
else:
limit = query.page_size
items = (
cls.model.objects.order_by("-created_at")
.filter(filters)
.limit(limit)
)
item_dicts = [i.to_dict() for i in items]

has_more: Optional[bool] = None
if wants_more := query.limit is None or query.limit > 0:
# only perform this query if it's necessary
has_more = items.limit(limit + 1).count() > limit
wants_more = query.limit is None or query.limit > 0
items, has_more = cls.model.all(
filters, limit=limit, wants_more=wants_more
)

next_page_uri: Optional[str] = None
next_page_uri = None
if wants_more and has_more:
query.created_before = item_dicts[-1]['created_at']
query.created_before = items[-1].created_at.isoformat()
path = self.current_request.context['resourcePath']
params = query.dict()
if self.user_id_filter_required():
params.pop('user_id')
next_page_uri = f'{path}?{urlencode(params)}'
return dict(items=item_dicts, next_page_uri=next_page_uri)
return dict(
items=[i.dict() for i in items], # type: ignore
next_page_uri=next_page_uri,
)

return cls

Expand Down
2 changes: 2 additions & 0 deletions agave/exc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class DoesNotExist(Exception):
'''object does not exist'''
16 changes: 6 additions & 10 deletions agave/filters.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
from typing import Any, Dict

from cuenca_validations.types import QueryParams
from mongoengine import Q


def generic_query(query: QueryParams) -> Q:
filters = Q()
if query.created_before:
filters &= Q(created_at__lt=query.created_before)
if query.created_after:
filters &= Q(created_at__gt=query.created_after)
exclude_fields = {
def exclude_fields(query: QueryParams) -> Dict[str, Any]:
excluded_fields = {
'created_before',
'created_after',
'active',
'limit',
'page_size',
'key',
}
fields = query.dict(exclude=exclude_fields)
fields = query.dict(exclude=excluded_fields)
if 'count' in fields:
del fields['count']
return filters & Q(**fields)
return fields
24 changes: 22 additions & 2 deletions agave/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
__all__ = ['BaseModel']
__all__ = []

from .base import BaseModel
try:
import mongoengine # noqa
except ImportError: # pragma: no cover
...
else:
from .mongo import MongoModel # noqa
from .mongo.filters import generic_mongo_query # noqa

__all__.extend(['MongoModel', 'generic_mongo_query'])


try:
import rom # noqa
except ImportError: # pragma: no cover
...
else:

from .redis import RedisModel # noqa
from .redis.filters import generic_redis_query # noqa

__all__.extend(['RedisModel', 'generic_redis_query'])
14 changes: 7 additions & 7 deletions agave/models/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import ClassVar, Dict
from typing import Callable, ClassVar

from ..lib.mongoengine.model_helpers import mongo_to_dict
from cuenca_validations.typing import DictStrAny


class BaseModel:
Expand All @@ -10,14 +10,14 @@ class BaseModel:
def __init__(self, *args, **values):
return super().__init__(*args, **values)

def to_dict(self) -> Dict:
def _dict(self, dict_func: Callable) -> DictStrAny:
private_fields = [f for f in dir(self) if f.startswith('_')]
excluded = self._excluded + private_fields
mongo_dict: dict = mongo_to_dict(self, excluded)
model_dict = dict_func(self, excluded)

for field in self._hidden:
mongo_dict[field] = '********'
return mongo_dict
model_dict[field] = '********'
return model_dict

def __repr__(self) -> str:
return str(self.to_dict()) # pragma: no cover
return str(self.dict()) # type: ignore # pragma: no cover
3 changes: 3 additions & 0 deletions agave/models/mongo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = ['MongoModel']

from .mongo_model import MongoModel
14 changes: 14 additions & 0 deletions agave/models/mongo/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from cuenca_validations.types import QueryParams
from mongoengine import Q

from ...filters import exclude_fields


def generic_mongo_query(query: QueryParams) -> Q:
filters = Q()
if query.created_before:
filters &= Q(created_at__lt=query.created_before)
if query.created_after:
filters &= Q(created_at__gt=query.created_after)
fields = exclude_fields(query)
return filters & Q(**fields)
47 changes: 47 additions & 0 deletions agave/models/mongo/mongo_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import List, Optional, Tuple

import mongoengine as mongo
from cuenca_validations.typing import DictStrAny
from mongoengine import Document, Q

from agave import exc
from agave.lib.mongoengine.model_helpers import mongo_to_dict
from agave.models.base import BaseModel


class MongoModel(BaseModel, Document):
meta = {'allow_inheritance': True}

def dict(self) -> DictStrAny:
return self._dict(mongo_to_dict)

@classmethod
def retrieve(
cls, id: str, *, user_id: Optional[str] = None
) -> 'MongoModel':
query = Q(id=id)
if user_id:
query = query & Q(user_id=user_id)
try:
obj = cls.objects.get(query)
except mongo.DoesNotExist:
raise exc.DoesNotExist
return obj

@classmethod
def count(cls, filters: Q) -> int:
return cls.objects.filter(filters).count()

@classmethod
def all(
cls, filters: Q, *, limit: int, wants_more: bool
) -> Tuple[List['MongoModel'], bool]:
items = (
cls.objects.order_by("-created_at").filter(filters).limit(limit)
)

has_more = False
if wants_more:
has_more = items.limit(limit + 1).count() > limit

return list(items), has_more
3 changes: 3 additions & 0 deletions agave/models/redis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = ['RedisModel', 'String']

from .redis_model import RedisModel, String
31 changes: 31 additions & 0 deletions agave/models/redis/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import datetime as dt

from cuenca_validations.types import QueryParams
from cuenca_validations.typing import DictStrAny

from ...filters import exclude_fields


def generic_redis_query(query: QueryParams, **kwargs) -> DictStrAny:
filters = dict()
if query.created_before or query.created_after:
# Restamos o sumamos un microsegundo porque la comparación
# aquí es inclusiva
created_at_lt = (
query.created_before.replace(tzinfo=None)
+ dt.timedelta(microseconds=-1)
if query.created_before
else None
)
created_at_gt = (
query.created_after.replace(tzinfo=None)
+ dt.timedelta(microseconds=1)
if query.created_after
else None
)
filters['created_at'] = (created_at_gt, created_at_lt)
fields = exclude_fields(query)
fields = {**fields, **kwargs}
if not filters:
filters = fields
return filters
73 changes: 73 additions & 0 deletions agave/models/redis/redis_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Dict, List, Optional, Tuple

from cuenca_validations.typing import DictStrAny
from cuenca_validations.validators import sanitize_item
from rom import Column, Model, PrimaryKey

from agave.exc import DoesNotExist
from agave.models.base import BaseModel

EXCLUDED = ['o_id']


class String(Column):
"""
No utilizo la clase String de rom porque todo lo maneja en bytes
codificado en latin-1.
"""

_allowed = str

def _to_redis(self, value):
return value.encode('utf-8')

def _from_redis(self, value):
return value.decode('utf-8')


def redis_to_dict(obj, exclude_fields: List[str]) -> DictStrAny:
excluded = EXCLUDED + exclude_fields
response = {
key: sanitize_item(value)
for key, value in obj._data.items()
if key not in excluded
}
return response


class RedisModel(BaseModel, Model):
meta = {'allow_inheritance': True}
o_id = PrimaryKey() # Para que podamos usar `id` en los modelos

def dict(self) -> DictStrAny:
return self._dict(redis_to_dict)
Comment on lines +38 to +43
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si este modeo es muy similar a MongoModel, podemos tener una clase base no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

podrías ampliar un poco más tu idea?
Esta clase al igual que MongoModel heredan de BaseModel que es donde se definió los elementos en común. Ves algo más que podamos unificar?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si claro

  • Ambos tienen el meta = {'allow_inheritance': True}
  • El retrieve o unico distinto en ambos es el query en si.
  • Incluso el count puede estar en el base si el cls.query y el cls.object tuvieran el mismo nombre.


@classmethod
def retrieve(
cls, id: str, *, user_id: Optional[str] = None
) -> 'RedisModel':
params = dict(id=id)
if user_id:
params['user_id'] = user_id
obj = cls.query.filter(**params).first()
if not obj:
raise DoesNotExist
return obj

@classmethod
def count(cls, filters: Dict) -> int:
return cls.query.filter(**filters).count()

@classmethod
def all(
cls, filters: Dict, *, limit: int, wants_more: bool
) -> Tuple[List['RedisModel'], bool]:
items = (
cls.query.filter(**filters).order_by('-created_at').limit(0, limit)
)

has_more = False
if wants_more:
has_more = items.limit(0, limit + 1).count() > limit

return list(items), has_more
2 changes: 1 addition & 1 deletion agave/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.0.5'
__version__ = '0.0.6.dev0'
4 changes: 0 additions & 4 deletions examples/chalicelib/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
__all__ = ['Account', 'Transaction']

from .accounts import Account
from .transactions import Transaction
12 changes: 0 additions & 12 deletions examples/chalicelib/models/accounts.py

This file was deleted.

Loading