Skip to content
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

Updated requirements to newer mongoengine/eve version. #23

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
840927f
Updated README for Legacy Announcement
mmellison Apr 26, 2015
a5cb123
Updated requirements to newer eve version. Tests have been performed …
Dec 16, 2015
e5cf87c
According to Eve documentation, by default Eve APIs are meant to be r…
Dec 16, 2015
cc4c766
Eve prescribes that by default the APIs should be read-only, therefor…
Dec 16, 2015
1e7e5a5
Merge branch 'develop' of https://github.com/liuq/eve-mongoengine int…
Dec 16, 2015
5610570
Modified eve-mongoengine dependencies.
Jan 7, 2016
e1249ad
Added customization for adding extra (possibly computed) fields to th…
Sep 30, 2018
6ca7918
Fixed to comply with all the tests.
Sep 30, 2018
6651b6d
Fixed also find_one() in datalayer.
Sep 30, 2018
9759d43
Moved meta properties dispatching.
Sep 30, 2018
304bf97
Added also a call to check_permissions in the model, so that a fine-g…
Sep 30, 2018
a3bc3ec
Added a comment for a fix that will be needed.
Sep 30, 2018
ca81b5a
_extra field is now customizable through the EVE_MONGOENGINE_EXTRA_FI…
Oct 1, 2018
8c63021
Fixed test on empty resource deletion (now the eve behavior is to ret…
Oct 1, 2018
079c657
Etag is now correctly set by eve-mongoengine and written to the DB.
Oct 1, 2018
7e6e7d1
Revert "Fixed test on empty resource deletion (now the eve behavior i…
Oct 2, 2018
fb20fa1
Brought back to a stable version and added some tests for the underly…
Oct 2, 2018
4aac4dd
Etag now is stored in the DB.
Oct 2, 2018
8e87311
Now eve additional information is stored in the DB and supplied throu…
Oct 5, 2018
e18c7fe
Added a basic test for versioning, which currently does not work.
Oct 5, 2018
db58995
Removed custom permission checking. It is ready to become the new ver…
Oct 12, 2018
e5d3a90
Updated to be compliant with eve >= 0.8.2
Apr 12, 2019
3bb93e4
Added permission checking.
Apr 12, 2019
e6ab2f5
Added:
Apr 13, 2019
39072b5
Fixed deleted hook.
Apr 13, 2019
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
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ See the `legacy` tag for the last legacy release (0.0.10).

Do not use `develop` in production code. Instead, the `master` branch always points to the latest production release, and should be used instead.

.. warning::
This branch or related tag is for a legacy version of the extension. Anything released before version 0.1 is considered legacy.

=======
About
=====

Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ Usage
app = Eve(settings=my_settings)
# init extension
ext = EveMongoengine(app)
# register model to eve
ext.add_model(Person)
# register model to eve and set further eve parameters, e.g., projections
ext.add_model(Person, datasource={ 'projection': { 'name': 1 } })

# let's roll
app.run()
Expand Down
167 changes: 106 additions & 61 deletions eve_mongoengine/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""
eve_mongoengine
~~~~~~~~~~~~~~~
Expand All @@ -13,28 +12,24 @@
:license: BSD, see LICENSE for more details.
"""

from datetime import datetime

import mongoengine

from .schema import SchemaMapper
from .datalayer import MongoengineDataLayer
from .struct import Settings
from .validation import EveMongoengineValidator
from ._compat import itervalues, iteritems

from .utils import clean_doc, remove_eve_mongoengine_fields, get_utc_time, fix_underscore
from mongoengine import signals
import json
import hashlib
from flask import current_app
from eve.methods.common import resolve_document_etag
from eve.utils import config

from .__version__ import get_version
__version__ = get_version()


def get_utc_time():
"""
Returns current datetime in system-wide UTC format wichout microsecond
part.
"""
return datetime.utcnow().replace(microsecond=0)

__version__ = get_version()

class EveMongoengine(object):
"""
Expand All @@ -54,17 +49,24 @@ class EveMongoengine(object):

This class tries hard to be extendable and hackable as possible, every
possible value is either a method param (for IoC-DI) or class attribute,
which can be overwriten in subclass.
which can be overwritten in subclass.
"""

#: Default HTTP methods allowed to manipulate with whole resources.
#: These are assigned to settings of every registered model, if not given
#: others.
default_resource_methods = ['GET', 'POST', 'DELETE']
default_resource_methods = ["GET"]

#: Default HTTP methods allowed to manipulate with items (single records).
#: These are assigned to settings of every registered model, if not given
#: others.
default_item_methods = ['GET', 'PATCH', 'PUT', 'DELETE']
default_item_methods = ["GET"]

#: Default role for resource access
default_resource_role = 'eve_resource_role'

#: Default role for item access
default_item_role = 'eve_item_role'

#: The class used as Eve validator, which is also one of Eve's constructor
#: params. In EveMongoengine, we need to overwrite it. If extending, assign
Expand All @@ -91,15 +93,9 @@ def __init__(self, app=None):

def _parse_config(self):
# parse app config
config = self.app.config
try:
self.last_updated = config['LAST_UPDATED']
except KeyError:
self.last_updated = '_updated'
try:
self.date_created = config['DATE_CREATED']
except KeyError:
self.date_created = '_created'
self.last_updated = self.app.config.get("LAST_UPDATED", "_updated") or config.LAST_UPDATED
self.date_created = self.app.config.get("DATE_CREATED", "_created") or config.DATE_CREATED
self.etag = self.app.config.get("ETAG", "_etag") or config.ETAG

def init_app(self, app):
"""
Expand All @@ -118,17 +114,40 @@ def init_app(self, app):
self._parse_config()
# overwrite default data layer to get proper mongoengine functionality
app.data = self.datalayer_class(self)

# add self as an additional app field (if not, when you use the factory pattern the reference
# to the mongoengine object could get lost)
app.eve_mongoengine = self

def _set_default_settings(self, settings):
"""
Initializes default settings options for registered model.
"""
if 'resource_methods' not in settings:
if "resource_methods" not in settings:
# TODO: maybe get from self.app.supported_resource_methods
settings['resource_methods'] = list(self.default_resource_methods)
if 'item_methods' not in settings:
settings["resource_methods"] = list(self.default_resource_methods)
if "item_methods" not in settings:
# TODO: maybe get from self.app.supported_item_methods
settings['item_methods'] = list(self.default_item_methods)
settings["item_methods"] = list(self.default_item_methods)
settings["allowed_roles"] = settings.get("allowed_roles", []) + [self.default_resource_role]
settings["allowed_item_roles"] = settings.get("allowed_item_roles", []) + [self.default_item_role]


@staticmethod
def _fix_fields(sender, document, **kwargs):
"""
Hook which updates all eve fields before every Document.save() call.
"""
eve_fields = document._eve_fields
doc = json.loads(document.to_json())
remove_eve_mongoengine_fields(doc, eve_fields.values())

resolve_document_etag(doc, sender._eve_resource)
document[eve_fields['etag']] = doc[config.ETAG]

now = get_utc_time()
document[eve_fields['updated']] = now
if 'created' in kwargs and kwargs['created']:
document[eve_fields['created']] = now

def add_model(self, models, lowercase=True, **settings):
"""
Expand All @@ -149,40 +168,58 @@ def add_model(self, models, lowercase=True, **settings):
models = [models]
for model_cls in models:
if not issubclass(model_cls, mongoengine.Document):
raise TypeError("Class '%s' is not a subclass of "
"mongoengine.Document." % model_cls.__name__)
raise TypeError(
"Class '%s' is not a subclass of "
"mongoengine.Document." % model_cls.__name__
)

resource_name = model_cls.__name__
if lowercase:
resource_name = resource_name.lower()

# add new fields to model class to get proper Eve functionality
self.fix_model_class(model_cls)
signals.pre_save_post_validation.connect(self._fix_fields, sender=model_cls)
self.models[resource_name] = model_cls

schema = self.schema_mapper_class.create_schema(model_cls,
lowercase)
schema = self.schema_mapper_class.create_schema(model_cls, lowercase)
# create resource settings
resource_settings = Settings({'schema': schema})
# FIXME: probably the ETAG should be created considering also dates
resource_settings = Settings({
"schema": schema,
"etag_ignore_fields": [config.DATE_CREATED, config.LAST_UPDATED, config.ETAG, '_id', '_cls']
})
resource_settings.update(settings)
# register to the app
self.app.register_resource(resource_name, resource_settings)
# add sub-resource functionality for every ReferenceField
subresources = self.schema_mapper_class.get_subresource_settings
for registration in subresources(model_cls, resource_name,
resource_settings, lowercase):
for registration in subresources(
model_cls, resource_name, resource_settings, lowercase
):
self.app.register_resource(*registration)
self.models[registration[0]] = model_cls
model_cls._eve_resource = resource_name
# register eve database hooks, so that it would be possible
# to customize a fine-grain permission checking directly
# into the mongoengine model
for event in 'on_fetched_resource', 'on_fetched_item', 'on_fetched_diffs', \
'on_insert', 'on_inserted', 'on_replace', 'on_replaced', \
'on_update', 'on_updated', 'on_delete_resource', 'on_deleted_resource', \
'on_delete_item', 'on_deleted_item':
if hasattr(model_cls, event):
eh = getattr(self.app, "{}_{}".format(event, resource_name))
eh += getattr(model_cls, event)

def fix_model_class(self, model_cls):
"""
Internal method invoked during registering new model.

Adds necessary fields (updated and created) into model class
Adds necessary fields (updated, created and etag) into model class
to ensure Eve's default functionality.

This is a helper for correct manipulation with mongoengine documents
within Eve. Eve needs 'updated' and 'created' fields for it's own
within Eve. Eve needs 'updated', 'created' and 'etag' fields for it's own
purpose, but we cannot ensure that they are present in the model
class. And even if they are, they may be of other field type or
missbehave.
Expand All @@ -191,27 +228,46 @@ def fix_model_class(self, model_cls):
:class:`mongoengine.Document`) to be fixed up.
"""
date_field_cls = mongoengine.DateTimeField
etag_field_cls = mongoengine.StringField

# TODO: maybe yes, instead
# field names have to be non-prefixed
last_updated_field_name = self.last_updated.lstrip('_')
date_created_field_name = self.date_created.lstrip('_')
last_updated_field_name = fix_underscore(self.last_updated)
date_created_field_name = fix_underscore(self.date_created)
etag_field_name = fix_underscore(self.etag)
model_cls._eve_fields = { 'updated': last_updated_field_name,
'created': date_created_field_name,
'etag': etag_field_name }

new_fields = {
# TODO: updating last_updated field every time when saved
last_updated_field_name: date_field_cls(db_field=self.last_updated,
default=get_utc_time),
date_created_field_name: date_field_cls(db_field=self.date_created,
default=get_utc_time)
last_updated_field_name: date_field_cls(
db_field=self.last_updated, default=get_utc_time
),
date_created_field_name: date_field_cls(
db_field=self.date_created, default=get_utc_time
),
etag_field_name: etag_field_cls(
db_field=self.etag
)
}
correct_field_types = {
last_updated_field_name: date_field_cls,
date_created_field_name: date_field_cls,
etag_field_name: etag_field_cls
}

for attr_name, attr_value in iteritems(new_fields):
# If the field does exist, we just check if it has right
# type (mongoengine.DateTimeField) and pass
if attr_name in model_cls._fields:
attr_value = model_cls._fields[attr_name]
if not isinstance(attr_value, mongoengine.DateTimeField):
info = (attr_name, attr_value.__class__.__name__)
raise TypeError("Field '%s' is needed by Eve, but has"
" wrong type '%s'." % info)
if not isinstance(attr_value, correct_field_types[attr_name]):
info = (attr_name, attr_value.__class__.__name__)
raise TypeError(
"Field '%s' is needed by Eve, but has"
" wrong type '%s'." % info
)
continue
# The way how we introduce new fields into model class is copied
# out of mongoengine.base.DocumentMetaclass
Expand All @@ -232,20 +288,9 @@ def fix_model_class(self, model_cls):
model_cls._db_field_map[attr_name] = attr_value.db_field
model_cls._reverse_db_field_map[attr_value.db_field] = attr_name

# this is just copied from mongoengine and frankly, i just dont
# this is just copied from mongoengine and frankly, i just don't
# have a clue, what it does...
iterfields = itervalues(model_cls._fields)
created = [(v.creation_counter, v.name) for v in iterfields]
model_cls._fields_ordered = tuple(i[1] for i in sorted(created))


def fix_last_updated(sender, document, **kwargs):
"""
Hook which updates LAST_UPDATED field before every Document.save() call.
"""
from eve.utils import config
field_name = config.LAST_UPDATED.lstrip('_')
if field_name in document:
document[field_name] = get_utc_time()

mongoengine.signals.pre_save.connect(fix_last_updated)
8 changes: 4 additions & 4 deletions eve_mongoengine/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
# This file must remain compatible with
# both Python >= 2.6 and Python 3.3+

VERSION = (0, 1, 0) # 0.1.0
VERSION = (0, 1, 1) # 0.1.1


def get_version():
if isinstance(VERSION[-1], int):
return '.'.join(map(str, VERSION))
return '.'.join(map(str, VERSION[:-1])) + VERSION[-1]

return ".".join(map(str, VERSION))
return ".".join(map(str, VERSION[:-1])) + VERSION[-1]
3 changes: 1 addition & 2 deletions eve_mongoengine/_compat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""
eve_mongoengine._compat
~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -32,7 +31,7 @@
iterkeys = lambda x: iter(x.keys())
itervalues = lambda x: iter(x.values())
u = lambda x: x
b = lambda x: x.encode('iso-8859-1') if not isinstance(x, bytes) else x
b = lambda x: x.encode("iso-8859-1") if not isinstance(x, bytes) else x
next = next
unichr = chr
imap = map
Expand Down
Loading