Skip to content

Commit

Permalink
Merge pull request #308 from phenobarbital/dev
Browse files Browse the repository at this point in the history
New changes over ModelView
  • Loading branch information
phenobarbital authored Nov 3, 2024
2 parents eef92c2 + 8b87799 commit 99ed136
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 25 deletions.
2 changes: 2 additions & 0 deletions navigator/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from navconfig import BASE_DIR, config, DEBUG
from navconfig.logging import logging

logging.getLogger('google.cloud.storage').setLevel(logging.INFO)

#### BASIC Configuration
APP_NAME = config.get("APP_NAME", fallback="Navigator")
APP_HOST = config.get('APP_HOST', fallback="localhost")
Expand Down
2 changes: 1 addition & 1 deletion navigator/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__description__ = (
"Navigator Web Framework based on aiohttp, " "with batteries included."
)
__version__ = "2.10.30"
__version__ = "2.11.0"
__author__ = "Jesus Lara"
__author_email__ = "jesuslarag@gmail.com"
__license__ = "BSD"
8 changes: 6 additions & 2 deletions navigator/views/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from aiohttp import web, hdrs
import traceback
from functools import wraps
import babel
try:
import babel
BABEL_INSTALLED = True
except ModuleNotFoundError:
BABEL_INSTALLED = False
from asyncdb import AsyncDB, AsyncPool
from datamodel import BaseModel
from datamodel.fields import Field
Expand Down Expand Up @@ -443,7 +447,7 @@ async def _get_meta_info(self, meta: str, fields: list):
locale = self.request.app['locale']
except KeyError:
locale = None
if locale:
if BABEL_INSTALLED is True and locale:
try:
lang = self.request.headers.get(
'Accept-Language',
Expand Down
183 changes: 164 additions & 19 deletions navigator/views/model.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Union, Any
from collections.abc import Awaitable
from typing import Optional, Union, Any
import importlib
import asyncio
from aiohttp import web
from navconfig.logging import logger
from datamodel import BaseModel
from datamodel.abstract import ModelMeta
from datamodel.types import JSON_TYPES
from datamodel.converters import parse_type
from datamodel.exceptions import ValidationError
from asyncdb.models import Model
Expand Down Expand Up @@ -73,6 +74,12 @@ class ModelView(AbstractModel):
_required: list = []
_primaries: list = []
_hidden: list = []
# New Callables to be used on response:
_get_callback: Optional[Awaitable] = None
_put_callback: Optional[Awaitable] = None
_post_callback: Optional[Awaitable] = None
_patch_callback: Optional[Awaitable] = None
_delete_callback: Optional[Awaitable] = None

def __init__(self, request, *args, **kwargs):
if self.model_name is not None:
Expand Down Expand Up @@ -183,11 +190,27 @@ async def _model_response(
result[field] = getattr(response, field, None)
else:
result = response
return self.json_response(
response = self.json_response(
result,
headers=headers,
status=status
)
# calling a callback into a send-and-forget response:
loop = asyncio.get_event_loop()
if callable(self._get_callback):
try:
# Run the coroutine in a new thread
asyncio.run_coroutine_threadsafe(
self._get_callback(response, result),
loop
)
except Exception as ex:
self.logger.warning(
f"Error in _get_callback: {ex}"
)
return response

_get_response = _model_response # alias

def get_primary(self, data: dict, args: dict = None) -> Any:
"""get_primary.
Expand Down Expand Up @@ -385,6 +408,35 @@ async def _get_filters():
self.get_model.Meta.connection = None
return data

async def _filtering(self, queryparams: dict) -> web.Response:
# Making a filter based on field received.
filter_param = queryparams.get('_filter')
if filter_param:
# Split the filter parameter into field and value
field, value = filter_param.split('=')
# Get the table and schema names
table_name = self.get_model.Meta.name
schema_name = self.get_model.Meta.schema
# TODO: discover the field type from Model.
ftype = self.get_model.column(field)
# Build the SQL query
query = f"""
SELECT {field} FROM {schema_name}.{table_name}
WHERE {field} LIKE '{value}%'"""
async with await self.handler(request=self.request) as conn:
result, error = await conn.query(query)
if error:
self.logger.warning(
f"Unable to filter by criteria {filter_param}"
)
return None
return self.json_response(
result,
status=200
)
else:
return None

@service_auth
async def get(self):
"""GET Model information."""
Expand All @@ -400,6 +452,10 @@ async def get(self):
if response is not None:
return response
try:
# Add Filtering
response = await self._filtering(qp)
if response is not None:
return response
# TODO: Add Query
# TODO: Add Pagination (offset, limit)
data = await self._get_data(qp, args)
Expand Down Expand Up @@ -488,7 +544,21 @@ async def _patch_response(self, result, status: int = 202) -> web.Response:
Post-processing data after saved and before summit.
"""
return self.json_response(result, status=status)
response = self.json_response(result, status=status)
# calling a callback into a send-and-forget response:
loop = asyncio.get_event_loop()
if callable(self._patch_callback):
try:
# Run the coroutine in a new thread
asyncio.run_coroutine_threadsafe(
self._patch_callback(response, result),
loop
)
except Exception as ex:
self.logger.warning(
f"Error in _patch_callback: {ex}"
)
return response

def required_by_put(self):
return []
Expand Down Expand Up @@ -746,12 +816,47 @@ async def patch(self):
**error
)

async def _post_response(self, result, status: int = 200, fields: list = None) -> web.Response:
async def _post_response(self, result: BaseModel, status: int = 200, fields: list = None) -> web.Response:
"""_post_response.
Post-processing data after saved and before summit.
"""
return self.json_response(result, status=status)
response = self.json_response(result, status=status)
# calling a callback into a send-and-forget response:
loop = asyncio.get_event_loop()
if callable(self._post_callback):
try:
# Run the coroutine in a new thread
asyncio.run_coroutine_threadsafe(
self._post_callback(response, result),
loop
)
except Exception as ex:
self.logger.warning(
f"Error in _post_callback: {ex}"
)
return response

async def _put_response(self, result: BaseModel, status: int = 200, fields: list = None) -> web.Response:
"""_put_response.
Post-processing data after saved and before summit.
"""
response = self.json_response(result, status=status)
# calling a callback into a send-and-forget response:
loop = asyncio.get_event_loop()
if callable(self._put_callback):
try:
# Run the coroutine in a new thread
asyncio.run_coroutine_threadsafe(
self._put_callback(response, result),
loop
)
except Exception as ex:
self.logger.warning(
f"Error in _put_callback: {ex}"
)
return response

@service_auth
async def put(self):
Expand Down Expand Up @@ -797,7 +902,7 @@ async def put(self):
self.model.Meta.connection = conn
try:
result = await self.model.create(data)
return await self._post_response(
return await self._put_response(
result,
status=201,
fields=fields
Expand Down Expand Up @@ -847,9 +952,25 @@ async def put(self):
"message": f"Invalid data for {self.__name__}"
}
)
result = await obj.insert()
status = 201
return await self._post_response(result, status=status, fields=fields)
try:
result = await obj.insert()
status = 201
except DriverError as exc:
return self.error(
response={
"message": f"Unable to insert over {self.__name__}",
"error": str(exc)
}
)
return await self._put_response(result, status=status, fields=fields)
except NoDataFound as exc:
return self.error(
response={
"message": f"Unable to insert over {self.__name__}",
"error": str(exc)
},
status=404
)
except StatementError as ex:
err = str(ex)
if 'duplicate' in err:
Expand All @@ -859,19 +980,19 @@ async def put(self):
}
else:
error = {
"message": f"Unable to insert {self.__name__}",
"message": f"Unable to insert over {self.__name__}",
"error": err
}
return self.error(response=error, status=400)
except ModelError as ex:
error = {
"message": f"Unable to insert {self.__name__}",
"message": f"Unable to insert over {self.__name__}",
"error": str(ex)
}
return self.error(response=error, status=400)
except ValidationError as ex:
error = {
"error": f"Unable to insert {self.__name__} info",
"error": f"Unable to insert over {self.__name__} info",
"payload": ex.payload,
}
return self.error(response=error, status=400)
Expand Down Expand Up @@ -944,8 +1065,16 @@ async def post(self):
obj.Meta.connection = conn
if not obj.is_valid():
continue
r = await obj.insert()
result.append(r)
try:
r = await obj.insert()
result.append(r)
except DriverError as exc:
return self.error(
response={
"message": f"Unable to insert over {self.__name__}",
"error": str(exc)
}
)
except ModelError as ex:
error = {
"message": f"Invalid {self.__name__}",
Expand Down Expand Up @@ -985,6 +1114,13 @@ async def post(self):
status=202,
fields=fields
)
except DriverError as exc:
return self.error(
response={
"message": f"Unable to insert over {self.__name__}",
"error": str(exc)
}
)
except ModelError as ex:
error = {
"error": f"Missing Info for Model {self.__name__}",
Expand Down Expand Up @@ -1015,15 +1151,24 @@ async def post(self):
### need to created:
qry = self.model(**data)
if qry.is_valid():
result = await qry.insert()
return await self._model_response(
try:
result = await qry.insert()
except DriverError as exc:
return self.error(
response={
"message": f"Unable to Insert over {self.__name__}",
"error": str(exc),
},
status=406
)
return await self._post_response(
result,
status=201,
fields=fields
)
else:
return self.error(
response=f"Unable to Insert {self.__name__}",
response=f"Unable to Insert over {self.__name__}",
)
except ModelError as ex:
error = {
Expand All @@ -1039,7 +1184,7 @@ async def post(self):
return self.error(response=error, status=406)
except ValidationError as ex:
error = {
"error": f"Unable to insert {self.__name__} info",
"error": f"Unable to insert over {self.__name__} info",
"payload": str(ex.payload),
}
return self.error(response=error, status=400)
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ def readme():
"asn1crypto==1.4.0",
"aiohttp-jrpc==0.1.0",
"jinja2==3.1.4",
"aiohttp-utils==3.1.1",
"psycopg2-binary>=2.9.9",
"aiosocks==0.2.6",
'python-slugify==8.0.1',
Expand All @@ -169,7 +168,7 @@ def readme():
"psutil==6.0.0",
"aiormq==6.8.1",
"Faker==22.2.0",
"google-cloud-storage==2.17.0"
"google-cloud-storage>=2.17.0"
],
extras_require={
"locale": [
Expand All @@ -179,7 +178,7 @@ def readme():
"aiomcache==0.8.2",
],
"uvloop": [
"uvloop==0.20.0",
"uvloop==0.21.0",
]
},
ext_modules=cythonize(extensions),
Expand Down

0 comments on commit 99ed136

Please sign in to comment.