commit
b4fb234e47
@ -0,0 +1,26 @@ |
|||||||
|
__pycache__/ |
||||||
|
*.py[cod] |
||||||
|
*$py.class |
||||||
|
*.so |
||||||
|
.Python |
||||||
|
env/ |
||||||
|
.venv/ |
||||||
|
build/ |
||||||
|
develop-eggs/ |
||||||
|
dist/ |
||||||
|
downloads/ |
||||||
|
eggs/ |
||||||
|
.eggs/ |
||||||
|
lib/ |
||||||
|
lib64/ |
||||||
|
parts/ |
||||||
|
sdist/ |
||||||
|
var/ |
||||||
|
*.egg-info/ |
||||||
|
.installed.cfg |
||||||
|
*.egg |
||||||
|
.idea/ |
||||||
|
*.pem |
||||||
|
*test.py |
||||||
|
trash |
||||||
|
mongo_data_dir/* |
@ -0,0 +1,18 @@ |
|||||||
|
class Get: |
||||||
|
method = 'GET' |
||||||
|
|
||||||
|
|
||||||
|
class List: |
||||||
|
method = 'GET' |
||||||
|
|
||||||
|
|
||||||
|
class Delete: |
||||||
|
method = 'DELETE' |
||||||
|
|
||||||
|
|
||||||
|
class Update: |
||||||
|
method = 'PUT' |
||||||
|
|
||||||
|
|
||||||
|
class Create: |
||||||
|
method = 'POST' |
@ -0,0 +1,46 @@ |
|||||||
|
from urllib.parse import urljoin |
||||||
|
|
||||||
|
from flask import Flask |
||||||
|
from mongoengine import Document |
||||||
|
|
||||||
|
from restapi.views import ApiView |
||||||
|
from restapi import Methods |
||||||
|
|
||||||
|
|
||||||
|
class MongoApi: |
||||||
|
app: Flask = None |
||||||
|
authentication_methods: list = [] |
||||||
|
|
||||||
|
def __init__(self, app: Flask, authentication_methods): |
||||||
|
self.app = app |
||||||
|
self.authentication_methods = authentication_methods |
||||||
|
|
||||||
|
def register_model(self, model: any, uri: str = None, name: str = None) -> None: |
||||||
|
if not getattr(model, '_meta').get('methods'): |
||||||
|
raise Exception("{} model methods not set".format(model.__name__)) |
||||||
|
if not uri: |
||||||
|
uri = model.__name__.lower() |
||||||
|
if uri[:-1] != "/": |
||||||
|
uri = uri + "/" |
||||||
|
if uri[0] != "/": |
||||||
|
uri = "/" + uri |
||||||
|
if not name: |
||||||
|
name = model.__name__ |
||||||
|
self.app.add_url_rule( |
||||||
|
uri, |
||||||
|
methods=(method.method for method in getattr(model, '_meta').get('methods') if |
||||||
|
method not in (Methods.Get, Methods.Update)), |
||||||
|
view_func=ApiView.as_view(name, model=model, authentication_methods=self.authentication_methods)) |
||||||
|
|
||||||
|
single = [] |
||||||
|
if Methods.Update in getattr(model, '_meta').get('methods'): |
||||||
|
single.append('PUT') |
||||||
|
|
||||||
|
if Methods.Get in getattr(model, '_meta').get('methods'): |
||||||
|
single.append('GET') |
||||||
|
if single: |
||||||
|
self.app.add_url_rule( |
||||||
|
urljoin(uri, '<pk>/'), |
||||||
|
methods=single, |
||||||
|
view_func=ApiView.as_view("{}_GET_PUT".format(name), model=model, |
||||||
|
authentication_methods=self.authentication_methods)) |
@ -0,0 +1,9 @@ |
|||||||
|
from flask import request |
||||||
|
from flask_jwt_extended.utils import verify_token_claims |
||||||
|
from flask_jwt_extended.view_decorators import _decode_jwt_from_request |
||||||
|
|
||||||
|
|
||||||
|
def get_jwt_from_request(): |
||||||
|
jwt_data, jwt_header = _decode_jwt_from_request(request_type='access') |
||||||
|
verify_token_claims(jwt_data) |
||||||
|
return jwt_data['identity'] |
@ -0,0 +1,212 @@ |
|||||||
|
from bson import ObjectId |
||||||
|
from flask import request |
||||||
|
from mongoengine import Document, ObjectIdField, ListField, \ |
||||||
|
EmbeddedDocumentField, ReferenceField, ImageField, FileField, DecimalField, QuerySet |
||||||
|
from base64 import b64encode |
||||||
|
|
||||||
|
from mongoengine.base import BaseField |
||||||
|
|
||||||
|
|
||||||
|
class Resource: |
||||||
|
model: Document = None |
||||||
|
qs: dict = {} |
||||||
|
eqs: dict = {} |
||||||
|
build_reference: bool = False |
||||||
|
base64_image: bool = True |
||||||
|
base64_image_as_full: bool = False |
||||||
|
limit: int = 10 |
||||||
|
offset: int = 0 |
||||||
|
fields: list = [] |
||||||
|
ignore_fields: list = [] |
||||||
|
mask_fields: dict = {} |
||||||
|
meta: dict = {} |
||||||
|
as_pk: str = 'id' |
||||||
|
query_document: Document = None |
||||||
|
mongo_qs = None |
||||||
|
|
||||||
|
def __init__(self, model: Document): |
||||||
|
self.model = model |
||||||
|
self.limit = 10 |
||||||
|
if 'no_image' in request.args: |
||||||
|
self.base64_image = False |
||||||
|
if 'with_sub_docs' in request.args: |
||||||
|
self.build_reference = request.args.get('with_sub_docs') |
||||||
|
if 'thumb' in request.args: |
||||||
|
self.base64_image = True |
||||||
|
self.base64_image_as_full = False |
||||||
|
if 'full_image' in request.args: |
||||||
|
self.base64_image = True |
||||||
|
self.base64_image_as_full = True |
||||||
|
if 'limit' in request.args: |
||||||
|
self.limit = int(request.args.get('limit')) |
||||||
|
if 'fields' in request.args: |
||||||
|
self.fields = request.args.get('fields') |
||||||
|
if 'offset' in request.args: |
||||||
|
self.offset = int(request.args.get('offset')) |
||||||
|
self.meta = getattr(self.model, '_meta') |
||||||
|
|
||||||
|
if 'ignore_fields' in self.meta: |
||||||
|
self.ignore_fields = self.meta.get('ignore_fields') |
||||||
|
if 'with_sub_docs' in self.meta: |
||||||
|
self.build_reference = self.meta.get('with_sub_docs') |
||||||
|
if 'mask_fields' in self.meta: |
||||||
|
self.mask_fields = self.meta.get('mask_fields') |
||||||
|
if 'document' in self.meta: |
||||||
|
self.model = self.meta.get('document') |
||||||
|
if 'query' in self.meta: |
||||||
|
self.qs = self.meta.get('query') |
||||||
|
if 'as_pk' in self.meta: |
||||||
|
self.as_pk = self.meta.get('as_pk') |
||||||
|
if 'query_document' in self.meta: |
||||||
|
self.query_document = self.meta.get('query_document') |
||||||
|
|
||||||
|
def external_query(self, qs: dict): |
||||||
|
self.eqs = qs |
||||||
|
|
||||||
|
def query(self, qs: dict) -> None: |
||||||
|
|
||||||
|
self.qs = qs |
||||||
|
|
||||||
|
def to_qs(self, pk: str = None) -> tuple: |
||||||
|
query = {} |
||||||
|
if self.qs: |
||||||
|
for key, val in self.qs.items(): |
||||||
|
if callable(val): |
||||||
|
query.update({key: val()}) |
||||||
|
else: |
||||||
|
query.update({key: val}) |
||||||
|
if self.query_document: |
||||||
|
query = {} |
||||||
|
if self.eqs: |
||||||
|
for key, val in self.eqs.items(): |
||||||
|
if callable(val): |
||||||
|
query.update({key: val()}) |
||||||
|
else: |
||||||
|
query.update({key: val}) |
||||||
|
data = self.query_document.objects.filter(**query) |
||||||
|
field_name = "" |
||||||
|
for i in self.model._fields_ordered: |
||||||
|
try: |
||||||
|
if isinstance(getattr(self.model, i).document_type, self.query_document.__class__): |
||||||
|
field_name = i + "__in" |
||||||
|
except: |
||||||
|
pass |
||||||
|
query.clear() |
||||||
|
query = {field_name: [i.id for i in data]} |
||||||
|
for key, val in self.qs.items(): |
||||||
|
if callable(val): |
||||||
|
query.update({key: val()}) |
||||||
|
else: |
||||||
|
query.update({key: val}) |
||||||
|
print(query) |
||||||
|
data = self.model.objects.filter(**query) |
||||||
|
if pk: |
||||||
|
data.get(**{self.as_pk: pk}) |
||||||
|
return data |
||||||
|
|
||||||
|
def to_json(self, pk: str = None) -> tuple: |
||||||
|
|
||||||
|
if self.mongo_qs is None: |
||||||
|
self.mongo_qs = self.to_qs(pk) |
||||||
|
|
||||||
|
data = self.mongo_qs |
||||||
|
count = data.count() |
||||||
|
if pk: |
||||||
|
json_data = self.parse_fields(self.mongo_qs.get(**{self.as_pk:pk}),self.model) |
||||||
|
else: |
||||||
|
data = data[self.offset:self.limit + self.offset] |
||||||
|
json_data = [] |
||||||
|
for item in data: |
||||||
|
item_data = self.parse_fields(item, self.model) |
||||||
|
json_data.append(item_data) |
||||||
|
return count, json_data |
||||||
|
|
||||||
|
def parse_field(self, field: BaseField, value: any) -> any: |
||||||
|
|
||||||
|
if isinstance(field, ObjectIdField): |
||||||
|
return str(value) |
||||||
|
elif isinstance(field, ReferenceField): |
||||||
|
|
||||||
|
if self.build_reference: |
||||||
|
return self.parse_fields(value, field.document_type) |
||||||
|
if value: |
||||||
|
return str(value.id) |
||||||
|
else: |
||||||
|
return value |
||||||
|
|
||||||
|
elif isinstance(field, ImageField): |
||||||
|
if self.base64_image and value: |
||||||
|
if self.base64_image_as_full: |
||||||
|
file = b64encode(value.read()).decode("utf-8") |
||||||
|
return { |
||||||
|
"size": value.size, |
||||||
|
"upload_date": value.gridout.upload_date, |
||||||
|
"format": value.format, |
||||||
|
"base64": file |
||||||
|
} |
||||||
|
else: |
||||||
|
file = b64encode(value.thumbnail.read()).decode("utf-8") |
||||||
|
|
||||||
|
return { |
||||||
|
"upload_date": value.gridout.upload_date, |
||||||
|
"format": value.format, |
||||||
|
"base64": file |
||||||
|
} |
||||||
|
else: |
||||||
|
if value: |
||||||
|
return { |
||||||
|
"size": value.size |
||||||
|
} |
||||||
|
else: |
||||||
|
return None |
||||||
|
elif isinstance(field, DecimalField): |
||||||
|
return float(value) |
||||||
|
elif isinstance(field, FileField): |
||||||
|
return "file" |
||||||
|
elif isinstance(field, ListField): |
||||||
|
if isinstance(field.field, EmbeddedDocumentField): |
||||||
|
return [self.parse_fields(item, field.field.document_type) for item in value] |
||||||
|
elif isinstance(field.field, ReferenceField) and self.build_reference: |
||||||
|
return [self.parse_fields(item, field.field.document_type) for item in value] |
||||||
|
else: |
||||||
|
return [self.parse_field(field.field, item) for item in value] |
||||||
|
elif isinstance(value, QuerySet): |
||||||
|
if value.count() > 0: |
||||||
|
return [self.parse_fields(i, value._document) for i in value] |
||||||
|
|
||||||
|
else: |
||||||
|
return value |
||||||
|
|
||||||
|
def parse_fields(self, item: Document, model: Document) -> dict: |
||||||
|
parsed_item = {} |
||||||
|
|
||||||
|
fields = list(getattr(model, "_fields_ordered")) |
||||||
|
if self.model != model: |
||||||
|
in_meta = getattr(model,'_meta') |
||||||
|
if in_meta.get('ignore_fields'): |
||||||
|
self.ignore_fields += in_meta.get('ignore_fields') |
||||||
|
for key, val in model.__dict__.items(): |
||||||
|
if isinstance(getattr(model, key), property): |
||||||
|
fields.append(key) |
||||||
|
for i in fields: |
||||||
|
if i not in self.ignore_fields: |
||||||
|
field = getattr(model, i) |
||||||
|
if i not in self.mask_fields: |
||||||
|
if not item is None: |
||||||
|
value = getattr(item, i) |
||||||
|
else: |
||||||
|
value = None |
||||||
|
else: |
||||||
|
value = getattr(item, i) |
||||||
|
if self.mask_fields.get(i) == 'all': |
||||||
|
value = '*************' |
||||||
|
elif self.mask_fields.get(i) == 'email': |
||||||
|
value = '{}****@{}***.{}'.format(value[0], value.split("@")[1][0], |
||||||
|
'.'.join(value.split("@")[1].split(".")[1:])) |
||||||
|
else: |
||||||
|
value = '*************' |
||||||
|
if not value is None: |
||||||
|
parsed_item.update({i: self.parse_field(field, value)}) |
||||||
|
else: |
||||||
|
parsed_item.update({i: None}) |
||||||
|
return parsed_item |
@ -0,0 +1,157 @@ |
|||||||
|
import json |
||||||
|
import time |
||||||
|
|
||||||
|
import mongoengine |
||||||
|
from flask import jsonify, request |
||||||
|
from flask_views.base import View |
||||||
|
from mongoengine import ValidationError, DoesNotExist, InvalidQueryError |
||||||
|
from werkzeug.exceptions import Unauthorized, NotFound |
||||||
|
|
||||||
|
from restapi.resource import Resource |
||||||
|
|
||||||
|
|
||||||
|
class ApiView(View): |
||||||
|
model = None |
||||||
|
authentication_methods = [] |
||||||
|
|
||||||
|
def __init__(self, model,authentication_methods): |
||||||
|
self.start = time.time() |
||||||
|
self.model = model |
||||||
|
self.resource = Resource(self.model) |
||||||
|
self.authentication_methods = authentication_methods |
||||||
|
|
||||||
|
def dispatch_request(self, *args, **kwargs): |
||||||
|
# keep all the logic in a helper method (_dispatch_request) so that |
||||||
|
# it's easy for subclasses to override this method without them also having to copy/paste all the |
||||||
|
# authentication logic, etc. |
||||||
|
return self._dispatch_request(*args, **kwargs) |
||||||
|
|
||||||
|
def _dispatch_request(self, *args, **kwargs): |
||||||
|
authorized = True if len(self.authentication_methods) == 0 else False |
||||||
|
for authentication_method in self.authentication_methods: |
||||||
|
if authentication_method().authorized(): |
||||||
|
authorized = True |
||||||
|
if not authorized: |
||||||
|
return {'error': 'Unauthorized'}, '401 Unauthorized' |
||||||
|
|
||||||
|
try: |
||||||
|
return super(ApiView, self).dispatch_request(*args, **kwargs) |
||||||
|
except mongoengine.queryset.DoesNotExist as e: |
||||||
|
return {'error': 'Empty query: ' + str(e)}, '404 Not Found' |
||||||
|
except ValidationError as e: |
||||||
|
return e.args[0], '400 Bad Request' |
||||||
|
except Unauthorized: |
||||||
|
return {'error': 'Unauthorized'}, '401 Unauthorized' |
||||||
|
except NotFound as e: |
||||||
|
return {'error': str(e)}, '404 Not Found' |
||||||
|
|
||||||
|
def get(self, *args, **kwargs): |
||||||
|
""" |
||||||
|
TODO: check permissions |
||||||
|
:param args: |
||||||
|
:param kwargs: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
if 'pk' in kwargs: |
||||||
|
try: |
||||||
|
qs = self.resource.to_qs(pk=kwargs.get('pk')) |
||||||
|
count, data = self.resource.to_json(pk=kwargs.get('pk')) |
||||||
|
response = { |
||||||
|
'response': data, |
||||||
|
'status': True, |
||||||
|
'info': { |
||||||
|
'took': time.time() - self.start |
||||||
|
}, |
||||||
|
} |
||||||
|
except (ValidationError, DoesNotExist) as e: |
||||||
|
kwargs.update({'code': 404}) |
||||||
|
response = { |
||||||
|
"status": False, |
||||||
|
"errors": "Object not found" |
||||||
|
} |
||||||
|
else: |
||||||
|
if "query" in request.args and self.model._meta.get('can_query'): |
||||||
|
self.resource.external_query(json.loads(request.args.get('query'))) |
||||||
|
qs = self.resource.to_qs() |
||||||
|
count, data = self.resource.to_json() |
||||||
|
response = { |
||||||
|
'response': data, |
||||||
|
'info': { |
||||||
|
'offset': self.resource.offset, |
||||||
|
'limit': self.resource.limit, |
||||||
|
'status': True, |
||||||
|
'total': count, |
||||||
|
'took': time.time() - self.start |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return jsonify(response), kwargs.get('code', 200) |
||||||
|
|
||||||
|
def post(self, *args, **kwargs): |
||||||
|
""" |
||||||
|
TODO: check permissions |
||||||
|
|
||||||
|
:param args: |
||||||
|
:param kwargs: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
try: |
||||||
|
item = self.model(**request.json) |
||||||
|
item.validate() |
||||||
|
except ValidationError as v: |
||||||
|
return jsonify({ |
||||||
|
'status': False, |
||||||
|
'errors': [{"field": field, "error": str(error)} for field, error in v.__dict__.get('errors').items() if |
||||||
|
isinstance(error, ValidationError) and field[0] != "_"] |
||||||
|
}), 400 |
||||||
|
except Exception as e: |
||||||
|
return jsonify({ |
||||||
|
'status': False, |
||||||
|
'errors': str(e) |
||||||
|
}), 400 |
||||||
|
data = item.save() |
||||||
|
return self.get(pk=data.id, code=201) |
||||||
|
|
||||||
|
def put(self, *args, **kwargs): |
||||||
|
""" |
||||||
|
TODO: check permissions |
||||||
|
|
||||||
|
:param args: |
||||||
|
:param kwargs: |
||||||
|
:return: |
||||||
|
""" |
||||||
|
if "pk" not in kwargs: |
||||||
|
return jsonify({ |
||||||
|
'status': False, |
||||||
|
"error": "Method not allowed" |
||||||
|
}), 403 |
||||||
|
else: |
||||||
|
try: |
||||||
|
self.model.objects(id=kwargs.get('pk')).update(**request.json) |
||||||
|
return self.get(pk=kwargs.get('pk')) |
||||||
|
except ValidationError as v: |
||||||
|
print(v.__dict__) |
||||||
|
return jsonify({ |
||||||
|
'status': False, |
||||||
|
'errors': [{"field": v.__dict__.get('field_name'), "error": v.__dict__.get('_message')}] |
||||||
|
}), 400 |
||||||
|
except InvalidQueryError as e: |
||||||
|
return jsonify({ |
||||||
|
'status': False, |
||||||
|
'errors': str(e) |
||||||
|
}), 400 |
||||||
|
|
||||||
|
def has_read_permission(self, request, qs): |
||||||
|
return qs |
||||||
|
|
||||||
|
def has_add_permission(self, request, obj): |
||||||
|
return True |
||||||
|
|
||||||
|
def has_change_permission(self, request, obj): |
||||||
|
return True |
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj): |
||||||
|
return True |
||||||
|
|
||||||
|
def delete(self, *args, **kwargs): |
||||||
|
"delete method" |
Loading…
Reference in new issue