-
Notifications
You must be signed in to change notification settings - Fork 1
add redis #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
add redis #25
Changes from all commits
f90ec02
e67aadc
85d2c25
dfe42ab
450fea0
8978c19
1accf3e
4fed776
1d49916
43f7203
b3b593b
487b00d
72b9666
48fd0af
95e47c8
ef2849e
86f8a12
c9aa577
e6f4d01
9b9bb02
5433433
c685d85
aebbfb8
b81f1a7
7c93282
8b13889
fbc7c38
3869cdc
3a706b8
2103455
a5e981e
e3c02ac
5130127
8457644
0d412e6
724abd4
37f7e6e
51996ae
54383c9
ed5b88e
48a5c6f
5d00bea
03f0eed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
|
|
||
|
|
||
|
|
@@ -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: | ||
|
|
@@ -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) | ||
|
|
@@ -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): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| class DoesNotExist(Exception): | ||
| '''object does not exist''' | ||
keryc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 |
| 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 | ||
felipao-mx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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']) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| __all__ = ['MongoModel'] | ||
|
|
||
| from .mongo_model import MongoModel |
| 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) |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| __all__ = ['RedisModel', 'String'] | ||
|
|
||
| from .redis_model import RedisModel, String |
| 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 |
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. podrías ampliar un poco más tu idea?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Si claro
|
||
|
|
||
| @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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| __version__ = '0.0.5' | ||
| __version__ = '0.0.6.dev0' |
| 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 | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.