Skip to content

Commit

Permalink
Merge branch 'support_for_decimal_type_#1048'
Browse files Browse the repository at this point in the history
Closes #1045
  • Loading branch information
nicolaiarocci committed Aug 29, 2017
2 parents 4dfbc5b + 46af495 commit cee2f7e
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 12 deletions.
15 changes: 12 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ python:
- pypy
install: travis_retry pip install tox-travis
services:
- mongodb
#- mongodb
- redis-server
before_script:
# work-around to make travis-ci working with mongod 3.4
# https://github.com/travis-ci/travis-ci/issues/3694
# https://github.com/travis-ci/apt-package-whitelist/issues/516
- wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.4.7.tgz -O /tmp/mongodb.tgz
- tar -xvf /tmp/mongodb.tgz
- mkdir /tmp/data
- ${PWD}/mongodb-linux-x86_64-3.4.7/bin/mongod --dbpath /tmp/data --bind_ip 127.0.0.1 --noauth &> /dev/null &
- until nc -z localhost 27017; do echo Waiting for MongoDB; sleep 1; done
- sleep 15
# timer is needed in order to get mongo to properly initialize on travis-ci
# See https://github.com/travis-ci/travis-ci/issues/1967#issuecomment-42008605
- sleep 15
- mongo eve_test --eval 'db.addUser("test_user", "test_pw");'
- "${PWD}/mongodb-linux-x86_64-3.4.7/bin/mongo eve_test --eval 'db.createUser({\"user\": \"test_user\", \"pwd\": \"test_pw\", \"roles\": [\"readWrite\", \"dbAdmin\"]},{\"w\": \"majority\" , \"wtimeout\": 5000 })'"
#- mongo eve_test --eval 'db.addUser("test_user", "test_pw");'
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Development

Version 0.8
~~~~~~~~~~~
- New: support fpr MongoDB decimal type ``bson.decimal128.Decimal128`` (Amedeo
Bussi).
- Update: upgrade PyMongo dependency to v3.5 (Amedeo Bussi).
- Fix: serialization bug that randomly skips fields if "x_of" is encountered.
See PR #1042 for details (Raychee).
- New: ``on_delete_resource_originals`` fired when soft deletion occurs (Amedeo
Expand Down
1 change: 1 addition & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,7 @@ defining the field validation rules. Allowed validation rules are:
- ``polygon``
- ``multipolygon``
- ``geometrycollection``
- ``decimal``

See :ref:`GeoJSON <geojson_feature>` for more
informations geo fields.
Expand Down
3 changes: 2 additions & 1 deletion docs/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ Extending Data Validation
Data validation is based on the Cerberus_ validation system and it is therefore
extensible. As a matter of fact, Eve's MongoDB data-layer itself extends
Cerberus validation, implementing the ``unique`` and ``data_relation``
constraints and the ``ObjectId`` data type on top of the standard rules.
constraints, the ``ObjectId`` data type and the ``decimal128`` on top of
the standard rules.
.. _custom_validation_rules:
Expand Down
7 changes: 7 additions & 0 deletions eve/io/mongo/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from .flask_pymongo import PyMongo
from pymongo import WriteConcern
from werkzeug.exceptions import HTTPException
import decimal
from bson import decimal128

from eve.auth import resource_auth
from eve.io.base import DataLayer, ConnectionException, BaseJSONEncoder
Expand Down Expand Up @@ -53,6 +55,8 @@ def default(self, obj):
if obj.database:
retval['$db'] = obj.database
return retval
if isinstance(obj, decimal128.Decimal128):
return str(obj)
# delegate rendering to base class method
return super(MongoJSONEncoder, self).default(obj)

Expand Down Expand Up @@ -85,6 +89,9 @@ class Mongo(DataLayer):
'dbref': lambda value:
DBRef(value['$col'], value['$id'], value['$db']
if '$db' in value else None) if value is not None else None,
'decimal': lambda value:
decimal128.Decimal128(decimal.Decimal(str(value)))
if value is not None else None,
}

# JSON serializer is a class attribute. Allows extensions to replace it
Expand Down
6 changes: 5 additions & 1 deletion eve/io/mongo/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
:copyright: (c) 2017 by Nicola Iarocci.
:license: BSD, see LICENSE for more details.
"""
from bson import ObjectId
from bson import ObjectId, decimal128
from bson.dbref import DBRef
from flask import current_app as app
from werkzeug.datastructures import FileStorage
Expand Down Expand Up @@ -167,6 +167,10 @@ def _validate_type_objectid(self, value):
if isinstance(value, ObjectId):
return True

def _validate_type_decimal(self, value):
if isinstance(value, decimal128.Decimal128):
return True

def _validate_type_dbref(self, value):
if isinstance(value, DBRef):
return True
Expand Down
16 changes: 15 additions & 1 deletion eve/tests/io/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime

import simplejson as json
from bson import ObjectId
from bson import ObjectId, decimal128
from bson.dbref import DBRef
from cerberus import SchemaError
from unittest import TestCase
Expand Down Expand Up @@ -94,6 +94,20 @@ def test_unique_success(self):
app_context running here """
pass

def test_decimal_fail(self):
schema = {'decimal': {'type': 'decimal'}}
doc = {'decimal': 'not_a_decimal'}
v = Validator(schema, None)
self.assertFalse(v.validate(doc))
self.assertTrue('decimal' in v.errors)
self.assertTrue('decimal' in v.errors['decimal'])

def test_decimal_success(self):
schema = {'decimal': {'type': 'decimal'}}
doc = {'decimal': decimal128.Decimal128('123.123')}
v = Validator(schema, None)
self.assertTrue(v.validate(doc))

def test_objectid_fail(self):
schema = {'id': {'type': 'objectid'}}
doc = {'id': 'not_an_object_id'}
Expand Down
12 changes: 9 additions & 3 deletions eve/tests/methods/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime

import simplejson as json
from bson import ObjectId
from bson import ObjectId, decimal128
from bson.dbref import DBRef

from eve.methods.common import serialize, normalize_dotted_fields
Expand Down Expand Up @@ -44,7 +44,9 @@ def test_mongo_serializes(self):
'dict_valueschema': {
'valueschema': {'type': 'objectid'}
},
'refobj': {'type': 'dbref'}
'refobj': {'type': 'dbref'},
'decobjstring': {'type': 'decimal'},
'decobjnumber': {'type': 'decimal'}
}
with self.app.app_context():
# Success
Expand All @@ -61,7 +63,9 @@ def test_mongo_serializes(self):
'refobj': {
'$id': '50656e4538345b39dd0414f0',
'$col': 'SomeCollection'
}
},
'decobjstring': "200.0",
'decobjnumber': 200.0
},
schema=schema
)
Expand All @@ -74,6 +78,8 @@ def test_mongo_serializes(self):
self.assertTrue(isinstance(ks['foo1'], ObjectId))
self.assertTrue(isinstance(ks['foo2'], ObjectId))
self.assertTrue(isinstance(res['refobj'], DBRef))
self.assertTrue(isinstance(res['decobjstring'], decimal128.Decimal128))
self.assertTrue(isinstance(res['decobjnumber'], decimal128.Decimal128))

def test_non_blocking_on_simple_field_serialization_exception(self):
schema = {
Expand Down
19 changes: 18 additions & 1 deletion eve/tests/methods/post.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from base64 import b64decode
from bson import ObjectId
from bson import ObjectId, decimal128

import simplejson as json

Expand All @@ -10,6 +10,7 @@
from eve import STATUS_OK, LAST_UPDATED, DATE_CREATED, ISSUES, STATUS, ETAG
from eve.methods.post import post
from eve.methods.post import post_internal
from eve.utils import str_type

from io import BytesIO

Expand Down Expand Up @@ -343,6 +344,22 @@ def test_post_auto_create_lists(self):
r, status = self.parse_response(resp)
self.assert201(status)

def test_post_decimal_number_success(self):
data = {"decimal_number": 100}
r, status = self.post('/invoices/', data=data)
self.assert201(status)
self.assertPostResponse(r)
id_field = self.domain['invoices']['id_field']
unique_id = r[id_field]
r, status = self.get('invoices/%s' % unique_id)
self.assert200(status)
assert isinstance(r["decimal_number"], str_type)

def test_post_decimal_number_fail(self):
data = {"decimal_number": "100.0.0"}
r, status = self.post('/invoices/', data=data)
self.assert422(status)

def test_post_referential_integrity(self):
data = {"person": self.unknown_item_id}
r, status = self.post('/invoices/', data=data)
Expand Down
1 change: 1 addition & 0 deletions eve/tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
'type': 'dbref',
'data_relation': {'resource': 'contacts'}
},
'decimal_number': {'type': 'decimal'},
}
}

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Flask==0.12
itsdangerous==0.24
Jinja2==2.9.4
MarkupSafe==0.23
pymongo==3.4.0
pymongo==3.5.0
simplejson==3.8.2
Werkzeug==0.11.15
backport_collections==0.1
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
'jinja2>=2.8,<3.0',
'itsdangerous>=0.24,<1.0',
'flask>=0.10.1,<=0.12',
'pymongo>=3.4',
'pymongo>=3.5',
'backport_collections>=0.1',
]

Expand Down

0 comments on commit cee2f7e

Please sign in to comment.