From d35b380cdb4000a49ae3e1df2d0bf487bab37f98 Mon Sep 17 00:00:00 2001 From: Andrew Rodionoff Date: Wed, 2 Apr 2014 11:59:44 +0400 Subject: [PATCH 01/15] added delayed app initialization --- flask_mongorest/__init__.py | 42 ++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/flask_mongorest/__init__.py b/flask_mongorest/__init__.py index daa28b85..0f60b30d 100644 --- a/flask_mongorest/__init__.py +++ b/flask_mongorest/__init__.py @@ -4,10 +4,20 @@ class MongoRest(object): - def __init__(self, app, **kwargs): + + def __init__(self, app=None, **kwargs): self.app = app self.url_prefix = kwargs.pop('url_prefix', '') - app.register_blueprint(Blueprint(self.url_prefix, __name__, template_folder='templates')) + self.template_folder = kwargs.pop('template_folder', 'templates') + if app is not None: + self.init_app(app, **kwargs) + + def init_app(self, app): + self.app = app + app.register_blueprint( + Blueprint(self.url_prefix, + __name__, + template_folder=self.template_folder)) def register(self, **kwargs): def decorator(klass): @@ -18,12 +28,28 @@ def decorator(klass): url = '%s%s' % (self.url_prefix, url) pk_type = kwargs.pop('pk_type', 'string') view_func = klass.as_view(name) - if List in klass.methods: - self.app.add_url_rule(url, defaults={'pk': None}, view_func=view_func, methods=[List.method], **kwargs) + if List in klass.methods: + self.app.add_url_rule( + url, + defaults={'pk': None}, + view_func=view_func, + methods=[List.method], + **kwargs) if Create in klass.methods or BulkUpdate in klass.methods: - self.app.add_url_rule(url, view_func=view_func, methods=[x.method for x in klass.methods if x in (Create, BulkUpdate)], **kwargs) - self.app.add_url_rule('%s<%s:%s>/' % (url, pk_type, 'pk'), view_func=view_func, methods=[x.method for x in klass.methods], **kwargs) + self.app.add_url_rule( + url, + view_func=view_func, + methods=[ + x.method for x in klass.methods if x in ( + Create, + BulkUpdate)], + **kwargs) + self.app.add_url_rule( + '%s<%s:%s>/' % (url, + pk_type, + 'pk'), + view_func=view_func, + methods=[x.method for x in klass.methods], + **kwargs) return klass return decorator - - From 01c976a9d15710d50c2f94ce500e4511882203b2 Mon Sep 17 00:00:00 2001 From: Andrew Rodionoff Date: Thu, 3 Apr 2014 10:28:27 +0400 Subject: [PATCH 02/15] worked out delayed call to app.add_url_rule --- flask_mongorest/__init__.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/flask_mongorest/__init__.py b/flask_mongorest/__init__.py index 0f60b30d..546f9b0e 100644 --- a/flask_mongorest/__init__.py +++ b/flask_mongorest/__init__.py @@ -2,22 +2,32 @@ from functools import wraps from flask_mongorest.methods import Create, Update, BulkUpdate, Fetch, List, Delete +class DelayedApp(object): + def __init__(self): + self.url_rules = [] + + def add_url_rule(self, *args, **kwargs): + self.url_rules.append((args, kwargs)) class MongoRest(object): def __init__(self, app=None, **kwargs): - self.app = app self.url_prefix = kwargs.pop('url_prefix', '') self.template_folder = kwargs.pop('template_folder', 'templates') if app is not None: self.init_app(app, **kwargs) + else: + self.app = DelayedApp() def init_app(self, app): - self.app = app app.register_blueprint( Blueprint(self.url_prefix, __name__, template_folder=self.template_folder)) + if isinstance(self.app, DelayedApp): + for args, kwargs in self.app.url_rules: + app.add_url_rule(*args, **kwargs) + self.app = app def register(self, **kwargs): def decorator(klass): From 6b75fe05a6b8fad186b197652a6c6a90f7a3ddd5 Mon Sep 17 00:00:00 2001 From: Andrew Rodionoff Date: Thu, 3 Apr 2014 11:09:50 +0400 Subject: [PATCH 03/15] fixed eager app init --- flask_mongorest/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/flask_mongorest/__init__.py b/flask_mongorest/__init__.py index 546f9b0e..632c77c5 100644 --- a/flask_mongorest/__init__.py +++ b/flask_mongorest/__init__.py @@ -24,9 +24,10 @@ def init_app(self, app): Blueprint(self.url_prefix, __name__, template_folder=self.template_folder)) - if isinstance(self.app, DelayedApp): - for args, kwargs in self.app.url_rules: - app.add_url_rule(*args, **kwargs) + if hasattr(self, 'app'): + if isinstance(self.app, DelayedApp): + for args, kwargs in self.app.url_rules: + app.add_url_rule(*args, **kwargs) self.app = app def register(self, **kwargs): From 59f95246cf4109843813fe20f5548415f7aace91 Mon Sep 17 00:00:00 2001 From: Anthony Nemitz Date: Tue, 2 Sep 2014 17:08:20 -0700 Subject: [PATCH 04/15] On mongoengine ValidationError return the error dict --- flask_mongorest/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_mongorest/views.py b/flask_mongorest/views.py index 2fd7dfc6..095572fd 100644 --- a/flask_mongorest/views.py +++ b/flask_mongorest/views.py @@ -46,7 +46,7 @@ def _dispatch_request(self, *args, **kwargs): except mongoengine.queryset.DoesNotExist as e: return {'error': 'Empty query: ' + str(e)}, '404 Not Found' except mongoengine.ValidationError as e: - return e.message, '400 Bad Request' + return {'field-errors': e.errors}, '400 Bad Request' except ValidationError as e: return e.message, '400 Bad Request' except Unauthorized as e: From aaf3b5621a42f6bbef4256acce2e4c8c53f1debc Mon Sep 17 00:00:00 2001 From: Anthony Nemitz Date: Tue, 2 Sep 2014 19:16:04 -0700 Subject: [PATCH 05/15] handle empty uri params --- flask_mongorest/resources.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/flask_mongorest/resources.py b/flask_mongorest/resources.py index ffb8b500..b594ce21 100644 --- a/flask_mongorest/resources.py +++ b/flask_mongorest/resources.py @@ -439,6 +439,14 @@ def apply_filters(self, qs, params=None): url = urlparse(value) uri = url.path value = uri.lstrip(self.uri_prefix) + + # special handling of empty / null params + # http://werkzeug.pocoo.org/docs/0.9/utils/ url_decode returns '' for empty params + if value == '': + value = None + elif value in ['""', "''"]: + value = '' + negate = False op_name = '' parts = key.split('__') From d85e908b0822b58b910884cdd65326da40f04322 Mon Sep 17 00:00:00 2001 From: Phil Freo Date: Thu, 9 Oct 2014 15:36:07 -0700 Subject: [PATCH 06/15] handle_serialization_error so that a single serialization error in a List method doesn't break the whole response --- flask_mongorest/views.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/flask_mongorest/views.py b/flask_mongorest/views.py index 095572fd..eaa3b774 100644 --- a/flask_mongorest/views.py +++ b/flask_mongorest/views.py @@ -88,9 +88,16 @@ def get(self, **kwargs): else: raise ValueError('Unsupported value of resource.get_objects') + data = [] + for obj in objs: + try: + data.append(self._resource.serialize(obj, params=request.args)) + except Exception as e: + self.handle_serialization_error(e, obj) + # Serialize the objects one by one ret = { - 'data': [self._resource.serialize(obj, params=request.args) for obj in objs] + 'data': data } if has_more != None: @@ -103,6 +110,9 @@ def get(self, **kwargs): ret = self._resource.serialize(obj, params=request.args) return ret + def handle_serialization_error(self, exc, obj): + pass + def post(self, **kwargs): if 'pk' in kwargs: raise NotFound("Did you mean to use PUT?") From df2155e373d7d4e08101dd39f6365c771e0c188c Mon Sep 17 00:00:00 2001 From: Phil Freo Date: Thu, 9 Oct 2014 15:46:37 -0700 Subject: [PATCH 07/15] Move handle_serialization_error to Resource --- flask_mongorest/resources.py | 3 +++ flask_mongorest/views.py | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/flask_mongorest/resources.py b/flask_mongorest/resources.py index b594ce21..203766a6 100644 --- a/flask_mongorest/resources.py +++ b/flask_mongorest/resources.py @@ -277,6 +277,9 @@ def get(obj, field_name, field_instance=None): return data + def handle_serialization_error(self, exc, obj): + pass + def value_for_field(self, obj, field): # If we specify a field which doesn't exist on the resource or on the # object, this method lets us return a custom value. diff --git a/flask_mongorest/views.py b/flask_mongorest/views.py index eaa3b774..60acb5c1 100644 --- a/flask_mongorest/views.py +++ b/flask_mongorest/views.py @@ -93,7 +93,9 @@ def get(self, **kwargs): try: data.append(self._resource.serialize(obj, params=request.args)) except Exception as e: - self.handle_serialization_error(e, obj) + fixed_obj = self._resource.handle_serialization_error(e, obj) + if fixed_obj: + data.append(fixed_obj) # Serialize the objects one by one ret = { @@ -110,9 +112,6 @@ def get(self, **kwargs): ret = self._resource.serialize(obj, params=request.args) return ret - def handle_serialization_error(self, exc, obj): - pass - def post(self, **kwargs): if 'pk' in kwargs: raise NotFound("Did you mean to use PUT?") From 2659a1ca5a4e469c731570bc7b294f6b9f957c9e Mon Sep 17 00:00:00 2001 From: Phil Freo Date: Thu, 9 Oct 2014 15:53:48 -0700 Subject: [PATCH 08/15] better check of fixed_obj in handle_serialization_error --- flask_mongorest/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_mongorest/views.py b/flask_mongorest/views.py index 60acb5c1..34b04ae3 100644 --- a/flask_mongorest/views.py +++ b/flask_mongorest/views.py @@ -94,7 +94,7 @@ def get(self, **kwargs): data.append(self._resource.serialize(obj, params=request.args)) except Exception as e: fixed_obj = self._resource.handle_serialization_error(e, obj) - if fixed_obj: + if fixed_obj is not None: data.append(fixed_obj) # Serialize the objects one by one From 25d2fcd5e0350b56f9da6f5d079931d923ced27a Mon Sep 17 00:00:00 2001 From: Anthony Nemitz Date: Tue, 2 Sep 2014 17:08:20 -0700 Subject: [PATCH 09/15] On mongoengine ValidationError return the error dict From 8d919303b0c6053dfd4c5f3a3cab970f76a71710 Mon Sep 17 00:00:00 2001 From: Anthony Nemitz Date: Tue, 2 Sep 2014 19:16:04 -0700 Subject: [PATCH 10/15] handle empty uri params From 52388d7e18ff3b0dfc8b4c26888b759a1e59d80d Mon Sep 17 00:00:00 2001 From: Andrew Rodionoff Date: Thu, 4 Dec 2014 09:47:19 +0300 Subject: [PATCH 11/15] added some documentation references --- flask_mongorest/__init__.py | 59 ++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/flask_mongorest/__init__.py b/flask_mongorest/__init__.py index b68de6ee..f0c6c8ab 100644 --- a/flask_mongorest/__init__.py +++ b/flask_mongorest/__init__.py @@ -1,30 +1,56 @@ from flask import Blueprint from flask_mongorest.methods import Create, BulkUpdate, List -class DelayedApp(object): + +class _DelayedApp(object): + + ''' + Stores URL rules for later merging with application URL map. + + ''' + def __init__(self): self.url_rules = [] def add_url_rule(self, *args, **kwargs): self.url_rules.append((args, kwargs)) + class MongoRest(object): def __init__(self, app=None, **kwargs): + ''' + Takes optional Flask application instance. If supplied, `init_app` will be + called to update application url map. + + ''' + self.url_prefix = kwargs.pop('url_prefix', '') self.template_folder = kwargs.pop('template_folder', 'templates') if app is not None: self.init_app(app, **kwargs) else: - self.app = DelayedApp() + self.app = _DelayedApp() def init_app(self, app): + ''' + Provides delayed application instance initialization to support + Flask application factory pattern. For further details on application + factories see: + + http://flask.pocoo.org/docs/dev/patterns/appfactories/ + + and + + http://mattupstate.com/python/2013/06/26/how-i-structure-my-flask-applications.html + + ''' app.register_blueprint( Blueprint(self.url_prefix, __name__, template_folder=self.template_folder)) if hasattr(self, 'app'): - if isinstance(self.app, DelayedApp): + if isinstance(self.app, _DelayedApp): for args, kwargs in self.app.url_rules: app.add_url_rule(*args, **kwargs) self.app = app @@ -32,7 +58,8 @@ def init_app(self, app): def register(self, **kwargs): def decorator(klass): - # Construct a url based on a 'name' kwarg with a fallback to a Mongo document's name + # Construct a url based on a 'name' kwarg with a fallback to a + # Mongo document's name document_name = klass.resource.document.__name__.lower() name = kwargs.pop('name', document_name) url = kwargs.pop('url', '/%s/' % document_name) @@ -45,10 +72,28 @@ def decorator(klass): pk_type = kwargs.pop('pk_type', 'string') view_func = klass.as_view(name) if List in klass.methods: - self.app.add_url_rule(url, defaults={'pk': None}, view_func=view_func, methods=[List.method], **kwargs) + self.app.add_url_rule( + url, + defaults={ + 'pk': None}, + view_func=view_func, + methods=[ + List.method], + **kwargs) if Create in klass.methods or BulkUpdate in klass.methods: - self.app.add_url_rule(url, view_func=view_func, methods=[x.method for x in klass.methods if x in (Create, BulkUpdate)], **kwargs) - self.app.add_url_rule('%s<%s:%s>/' % (url, pk_type, 'pk'), view_func=view_func, methods=[x.method for x in klass.methods if x not in (List, BulkUpdate)], **kwargs) + self.app.add_url_rule( + url, + view_func=view_func, + methods=[ + x.method for x in klass.methods if x in ( + Create, + BulkUpdate)], + **kwargs) + self.app.add_url_rule( + '%s<%s:%s>/' % + (url, pk_type, 'pk'), view_func=view_func, methods=[ + x.method for x in klass.methods if x not in ( + List, BulkUpdate)], **kwargs) return klass return decorator From fb639058224219b8f459d6a1a8beffadddb9597f Mon Sep 17 00:00:00 2001 From: Andrew Rodionoff Date: Thu, 4 Dec 2014 09:48:31 +0300 Subject: [PATCH 12/15] switched example app to delayed initialization (factory pattern) --- example/app.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example/app.py b/example/app.py index 2607e1fa..71cee8b9 100644 --- a/example/app.py +++ b/example/app.py @@ -30,8 +30,8 @@ }, ) -db = MongoEngine(app) -api = MongoRest(app) +db = MongoEngine() +api = MongoRest() class User(db.Document): email = db.EmailField(unique=True, required=True) @@ -328,6 +328,8 @@ def _dispatch_request(self, *args, **kwargs): super(TestViewMethodView, self)._dispatch_request(*args, **kwargs) return { 'method': self._resource.view_method.__name__ } +db.init_app(app) +api.init_app(app) if __name__ == "__main__": port = int(os.environ.get('PORT', 8000)) From f833cdbd90f205547f21f9773465f10c31768d4b Mon Sep 17 00:00:00 2001 From: Frank Valcarcel Date: Sat, 9 Jul 2016 00:11:15 -0400 Subject: [PATCH 13/15] code fixes from PR #85 review --- example/app.py | 2 +- flask_mongorest/__init__.py | 58 ++++++++++++++----------------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/example/app.py b/example/app.py index 7956fbe7..4718a413 100644 --- a/example/app.py +++ b/example/app.py @@ -6,7 +6,7 @@ from flask.ext.mongorest.views import ResourceView from flask.ext.mongorest.resources import Resource from flask.ext.mongorest import operators as ops -from flask.ext.mongorest.methods import * +from flask.ext.mongorest.methods import Create, Update, Fetch, List, Delete, BulkUpdate from flask.ext.mongorest.authentication import AuthenticationBase from example import schemas, documents diff --git a/flask_mongorest/__init__.py b/flask_mongorest/__init__.py index f0c6c8ab..86035c41 100644 --- a/flask_mongorest/__init__.py +++ b/flask_mongorest/__init__.py @@ -1,13 +1,11 @@ from flask import Blueprint from flask_mongorest.methods import Create, BulkUpdate, List - class _DelayedApp(object): - - ''' + """ Stores URL rules for later merging with application URL map. - ''' + """ def __init__(self): self.url_rules = [] @@ -17,23 +15,22 @@ def add_url_rule(self, *args, **kwargs): class MongoRest(object): - - def __init__(self, app=None, **kwargs): - ''' + def __init__(self, app=None, template_folder='templates', **kwargs): + """ Takes optional Flask application instance. If supplied, `init_app` will be called to update application url map. - ''' + """ self.url_prefix = kwargs.pop('url_prefix', '') - self.template_folder = kwargs.pop('template_folder', 'templates') + self.template_folder = template_folder if app is not None: self.init_app(app, **kwargs) else: self.app = _DelayedApp() def init_app(self, app): - ''' + """ Provides delayed application instance initialization to support Flask application factory pattern. For further details on application factories see: @@ -43,16 +40,15 @@ def init_app(self, app): and http://mattupstate.com/python/2013/06/26/how-i-structure-my-flask-applications.html + """ - ''' app.register_blueprint( - Blueprint(self.url_prefix, - __name__, - template_folder=self.template_folder)) - if hasattr(self, 'app'): - if isinstance(self.app, _DelayedApp): + Blueprint(self.url_prefix, __name__, template_folder=self.template_folder)) + + if hasattr(self, 'app') and isinstance(self.app, _DelayedApp): for args, kwargs in self.app.url_rules: app.add_url_rule(*args, **kwargs) + self.app = app def register(self, **kwargs): @@ -72,28 +68,16 @@ def decorator(klass): pk_type = kwargs.pop('pk_type', 'string') view_func = klass.as_view(name) if List in klass.methods: - self.app.add_url_rule( - url, - defaults={ - 'pk': None}, - view_func=view_func, - methods=[ - List.method], - **kwargs) + self.app.add_url_rule(url, defaults={'pk': None}, view_func=view_func, + methods=[List.method], **kwargs) if Create in klass.methods or BulkUpdate in klass.methods: - self.app.add_url_rule( - url, - view_func=view_func, - methods=[ - x.method for x in klass.methods if x in ( - Create, - BulkUpdate)], - **kwargs) - self.app.add_url_rule( - '%s<%s:%s>/' % - (url, pk_type, 'pk'), view_func=view_func, methods=[ - x.method for x in klass.methods if x not in ( - List, BulkUpdate)], **kwargs) + methods = [x.method for x in klass.methods if x in (Create, BulkUpdate)] + self.app.add_url_rule(url, view_func=view_func, methods=methods, **kwargs) + + methods = [x.method for x in klass.methods if x not in (List, BulkUpdate)] + self.app.add_url_rule('%s<%s:%s>/' % (url, pk_type, 'pk'), + view_func=view_func, methods=methods, **kwargs) + return klass return decorator From 0edd3028a3e197d5af015286a02fbf6592d683ed Mon Sep 17 00:00:00 2001 From: Frank Valcarcel Date: Wed, 24 Aug 2016 18:03:36 -0400 Subject: [PATCH 14/15] removes mongoengine.ValidationError from views, outside scope for PR #85 --- flask_mongorest/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask_mongorest/views.py b/flask_mongorest/views.py index ea782e99..5a6e84de 100644 --- a/flask_mongorest/views.py +++ b/flask_mongorest/views.py @@ -56,8 +56,6 @@ def _dispatch_request(self, *args, **kwargs): return super(ResourceView, self).dispatch_request(*args, **kwargs) except mongoengine.queryset.DoesNotExist as e: return {'error': 'Empty query: ' + str(e)}, '404 Not Found' - except mongoengine.ValidationError as e: - return {'field-errors': e.errors}, '400 Bad Request' except ValidationError as e: return e.message, '400 Bad Request' except Unauthorized as e: From a8804b51f3a37ddccecbed1d97f892cc9b635b9e Mon Sep 17 00:00:00 2001 From: Frank Valcarcel Date: Wed, 24 Aug 2016 18:17:01 -0400 Subject: [PATCH 15/15] settles flake8 visual indent warnings --- flask_mongorest/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask_mongorest/__init__.py b/flask_mongorest/__init__.py index 86035c41..b7ed1a7e 100644 --- a/flask_mongorest/__init__.py +++ b/flask_mongorest/__init__.py @@ -68,15 +68,15 @@ def decorator(klass): pk_type = kwargs.pop('pk_type', 'string') view_func = klass.as_view(name) if List in klass.methods: - self.app.add_url_rule(url, defaults={'pk': None}, view_func=view_func, - methods=[List.method], **kwargs) + self.app.add_url_rule( + url, defaults={'pk': None}, view_func=view_func, methods=[List.method], **kwargs) if Create in klass.methods or BulkUpdate in klass.methods: methods = [x.method for x in klass.methods if x in (Create, BulkUpdate)] self.app.add_url_rule(url, view_func=view_func, methods=methods, **kwargs) methods = [x.method for x in klass.methods if x not in (List, BulkUpdate)] - self.app.add_url_rule('%s<%s:%s>/' % (url, pk_type, 'pk'), - view_func=view_func, methods=methods, **kwargs) + self.app.add_url_rule( + '%s<%s:%s>/' % (url, pk_type, 'pk'), view_func=view_func, methods=methods, **kwargs) return klass