From e66aaf75f1ff733001090c1c7694494d67666e07 Mon Sep 17 00:00:00 2001 From: Victor Cabezas Date: Mon, 28 Mar 2016 21:14:22 +0200 Subject: [PATCH 1/6] Change log for v0.3 --- doc/Changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Changelog.rst b/doc/Changelog.rst index 48c8aba..7b5ab38 100644 --- a/doc/Changelog.rst +++ b/doc/Changelog.rst @@ -3,4 +3,5 @@ Changelog =================== * **0.1**: First version, base implementation done. Added docs and tests. -* **0.2**: Added DateField, added optional fields, fixed some bugs. \ No newline at end of file +* **0.2**: Added DateField, added optional fields, fixed some bugs. +* **0.3**: Fixed Python's 3 compatibility. Added some examples. \ No newline at end of file From e14a75d10d6c2d50338baa085b089df3959fa36f Mon Sep 17 00:00:00 2001 From: Victor Cabezas Date: Wed, 30 Mar 2016 21:35:53 +0200 Subject: [PATCH 2/6] Fixed issue with json_encode, added test for forbidden attrs in NestedField, tests failing on Python3 --- json2py/encoder.py | 2 +- json2py/models.py | 25 +++++++++++++++++++++++-- tests.py | 34 +++++++++++++++++++++++++++------- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/json2py/encoder.py b/json2py/encoder.py index 9ef9e4f..9688b6b 100644 --- a/json2py/encoder.py +++ b/json2py/encoder.py @@ -7,7 +7,7 @@ class BaseEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, NestedField): - return dict([(k, self.default(v)) for k, v in obj.items()]) + return dict([(k if v.name is None else v.name, self.default(v)) for k, v in obj.items()]) elif isinstance(obj, ListField): return [self.default(v) for v in obj.value] elif isinstance(obj, DateField): diff --git a/json2py/models.py b/json2py/models.py index 0f6fa0f..2d903b3 100644 --- a/json2py/models.py +++ b/json2py/models.py @@ -16,6 +16,13 @@ class ParseException(Exception): pass +class InvalidAttribute(Exception): + """ + Exception raised when invalid attribute is set on :class:`.NestedField` + """ + pass + + class BaseField(object): """ Base Class holding and defining common features for all the other subclasses. @@ -82,6 +89,7 @@ def __str__(self): def __repr__(self): return self.__str__() + class TextField(BaseField): """ Class representing a string field in JSON. @@ -104,6 +112,7 @@ def __str__(self): def __repr__(self): return self.__str__() + class NumberField(BaseField): """ Abstract class for representing JSON numbers. @@ -118,6 +127,7 @@ def __str__(self): def __repr__(self): return self.__str__() + class IntegerField(NumberField): """ Class representing an integer field in JSON. @@ -160,9 +170,19 @@ class NestedField(BaseField): :arg name: It has the same meaning as in :class:`.BaseField` :arg required: It has the same meaning as in :class:`.BaseField` :raise `ParseException`: If ``value`` is not a dict nor None + :raise `InvalidAttribute`: If a reserved keyword is used as attribute + :note: Reserved keywords are: ``name``, ``value`` and ``required`` :note: For use cases and examples refer to :doc:`examples` """ + __forbiddenAttrs = ['name', 'value', 'required'] + + def __new__(cls, *args, **kwargs): + forbidden_intersection = set(dir(cls)).intersection(NestedField.__forbiddenAttrs) + if len(forbidden_intersection) > 0: + raise InvalidAttribute('%s cannot be used as attribute names, use name keyword for bypassing this limitation' %(', '.join(forbidden_intersection))) + return super(NestedField, cls).__new__(cls, *args, **kwargs) + def __init__(self, value = None, name = None, required = True): super(NestedField, self).__setattr__('value', {}) super(NestedField, self).__setattr__('name', name) @@ -190,9 +210,9 @@ def __init__(self, value = None, name = None, required = True): if key not in data and field.required: raise LookupError('%s was not found on data dict' % key) elif key in data: - field = field.__class__(data[key]) + field = field.__class__(value = data[key], name = field.name, required = field.required) else: - field = field.__class__(None) + field = field.__class__(value = None, name = field.name, required = field.required) super(NestedField, self).__getattribute__('value')[reverseLookUp[key]] = field # setattr(self, reverseLookUp[key], field) @@ -229,6 +249,7 @@ def parse(data, cls): def items(self): return super(NestedField, self).__getattribute__('value').items() + class ListField(BaseField): """ Class representing a list field in JSON. This class implements :mod:`list` interface so you diff --git a/tests.py b/tests.py index e1ed95b..db3ece4 100644 --- a/tests.py +++ b/tests.py @@ -7,6 +7,7 @@ from json2py.models import ListField from json2py.models import BooleanField from json2py.models import ParseException +from json2py.models import InvalidAttribute from json2py.models import DateField from datetime import datetime @@ -16,13 +17,25 @@ class NestedObjTest(NestedField): id = IntegerField() key = IntegerField(name = 'clave') - value = TextField() + valor = TextField(name = 'value') class ListObjTest(ListField): __model__ = NestedObjTest +class ForbiddenNameTest(NestedField): + name = TextField() + + +class ForbiddenValueTest(NestedField): + value = TextField() + + +class ForbiddenRequiredTest(NestedField): + required = TextField() + + class RequiredTest(unittest.TestCase): def test_required(self): @@ -148,7 +161,7 @@ def __init__(self, *args, **kwargs): def test_init(self): self.assertEqual(self.testObj.id.value, 1234) self.assertEqual(self.testObj.key.value, 1) # clave field - self.assertEqual(self.testObj.value.value, 'aValue') + self.assertEqual(self.testObj.valor.value, 'aValue') self.assertRaises(ParseException, NestedField.__init__, NestedField(), 10) # with self.assertRaises(ParseException): @@ -176,7 +189,14 @@ def test_init(self): def test_encode(self): self.assertEqual(NestedField({}).json_encode(), '{}') - self.assertEqual(json.loads(self.testObj.json_encode()), json.loads('{"id": 1234, "key": 1, "value": "aValue"}')) + self.assertEqual(json.loads(self.testObj.json_encode()), json.loads('{"id": 1234, "clave": 1, "value": "aValue"}')) + + def test_forbidden(self): + ## Tricky test + self.assertRaises(InvalidAttribute, ForbiddenNameTest.__new__, ForbiddenNameTest) + self.assertRaises(InvalidAttribute, ForbiddenValueTest.__new__, ForbiddenValueTest) + self.assertRaises(InvalidAttribute, ForbiddenRequiredTest.__new__, ForbiddenRequiredTest) + class ListTest(unittest.TestCase): def __init__(self, *args, **kwargs): @@ -191,8 +211,8 @@ def test_init(self): self.assertEqual(self.testObj[1].id.value, 4321) self.assertEqual(self.testObj[0].key.value, 1) # clave field self.assertEqual(self.testObj[1].key.value, 2) # clave field - self.assertEqual(self.testObj[0].value.value, 'aValue') - self.assertEqual(self.testObj[1].value.value, 'anotherValue') + self.assertEqual(self.testObj[0].valor.value, 'aValue') + self.assertEqual(self.testObj[1].valor.value, 'anotherValue') self.assertRaises(ParseException, ListField.__init__, ListObjTest(), 10) # with self.assertRaises(ParseException): @@ -224,11 +244,11 @@ def test_slice(self): self.assertEqual(len(self.testObj[1:]), 1) self.assertEqual(self.testObj[-1].id.value, 4321) self.assertEqual(self.testObj[-1].key.value, 2) # clave field - self.assertEqual(self.testObj[-1].value.value, 'anotherValue') + self.assertEqual(self.testObj[-1].valor.value, 'anotherValue') def test_encode(self): self.assertEqual(ListObjTest([]).json_encode(), '[]') - self.assertEqual(json.loads(self.testObj.json_encode()), json.loads('[{"id": 1234, "key": 1, "value": "aValue"},{"id": 4321, "key": 2, "value": "anotherValue"}]')) + self.assertEqual(json.loads(self.testObj.json_encode()), json.loads('[{"id": 1234, "clave": 1, "value": "aValue"},{"id": 4321, "clave": 2, "value": "anotherValue"}]')) class DateTest(unittest.TestCase): From cd2143d77de38735ffc5ec5505e59c1d4637d778 Mon Sep 17 00:00:00 2001 From: Victor Cabezas Date: Mon, 4 Apr 2016 21:29:26 +0200 Subject: [PATCH 3/6] Python 2 and 3 now passing, fixes #14 --- json2py/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json2py/models.py b/json2py/models.py index 2d903b3..4ba1798 100644 --- a/json2py/models.py +++ b/json2py/models.py @@ -181,7 +181,7 @@ def __new__(cls, *args, **kwargs): forbidden_intersection = set(dir(cls)).intersection(NestedField.__forbiddenAttrs) if len(forbidden_intersection) > 0: raise InvalidAttribute('%s cannot be used as attribute names, use name keyword for bypassing this limitation' %(', '.join(forbidden_intersection))) - return super(NestedField, cls).__new__(cls, *args, **kwargs) + return super(NestedField, cls).__new__(cls) def __init__(self, value = None, name = None, required = True): super(NestedField, self).__setattr__('value', {}) From 1c17ca731b2ba446ed7716f9ce6b25f8f06bdad0 Mon Sep 17 00:00:00 2001 From: Victor Cabezas Date: Thu, 7 Apr 2016 19:16:26 +0200 Subject: [PATCH 4/6] Updated models docs. Updated and expanded README.md, closes #11. --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ doc/models.rst | 6 +++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e38142..7b38579 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,51 @@ Convert JSON/dict to python object and viceversa pip install json2py ``` +## Example +Using json2py makes treating with Python dicts as simple as: +```python + from json2py.models import * + class Example(NestedField): + hello = TextField(name = 'hi') + integer = IntegerField() + floating = FloatField() + + class ExampleList(ListField): + __model__ = Example + + dict_var = {'hi': 'world', 'integer': 1000, 'floating': 10.5, 'ignored': "you won't see me"} + list_var = [dict_var] * 3 + + myMappedList = ExampleList(list_var) + + myMappedList[1].integer.value = 1234 + + print myMappedList.json_encode(indent = 4) +``` + +Output: +```javascript + [ + { + "integer": 1000, + "floating": 10.5, + "hi": "world" + }, + { + "integer": 1234, + "floating": 10.5, + "hi": "world" + }, + { + "integer": 1000, + "floating": 10.5, + "hi": "world" + } + ] +``` + +Please, refer to [Documentation examples](http://json2py.readthedocs.org/en/latest/examples.html) for more examples. + ## Build Status [![Build Status](https://travis-ci.org/Wiston999/json2py.svg?branch=master)](https://travis-ci.org/Wiston999/json2py) diff --git a/doc/models.rst b/doc/models.rst index 83b6592..973b9a0 100644 --- a/doc/models.rst +++ b/doc/models.rst @@ -38,17 +38,17 @@ Should return something like: { "integer": 1000, "floating": 10.5, - "hello": "world" + "hi": "world" }, { "integer": 1234, "floating": 10.5, - "hello": "world" + "hi": "world" }, { "integer": 1000, "floating": 10.5, - "hello": "world" + "hi": "world" } ] From 40671b0f5b4c004302e5a54e09dc194833b099c5 Mon Sep 17 00:00:00 2001 From: Victor Cabezas Date: Thu, 7 Apr 2016 19:20:26 +0200 Subject: [PATCH 5/6] Update README.md Solved typo with viceversa and added comments to example. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b38579..e79d973 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # json2py ## Description -Convert JSON/dict to python object and viceversa +Convert JSON/dict to python object and vice versa ## Installation ``` @@ -12,21 +12,27 @@ pip install json2py Using json2py makes treating with Python dicts as simple as: ```python from json2py.models import * + # Define basic object map class Example(NestedField): hello = TextField(name = 'hi') integer = IntegerField() floating = FloatField() + # Define list map class ExampleList(ListField): __model__ = Example + # Sample variables dict_var = {'hi': 'world', 'integer': 1000, 'floating': 10.5, 'ignored': "you won't see me"} list_var = [dict_var] * 3 + # Do the magic myMappedList = ExampleList(list_var) + # Modify integer field of second list element myMappedList[1].integer.value = 1234 + # Show the results print myMappedList.json_encode(indent = 4) ``` @@ -39,6 +45,7 @@ Output: "hi": "world" }, { + // Notice how "integer" value has changed "integer": 1234, "floating": 10.5, "hi": "world" From 31df2565e0cac17edbf7f490ed238d5d82267b22 Mon Sep 17 00:00:00 2001 From: Victor Cabezas Date: Thu, 7 Apr 2016 19:24:19 +0200 Subject: [PATCH 6/6] Preparing v0.4 --- doc/Changelog.rst | 3 ++- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/Changelog.rst b/doc/Changelog.rst index 7b5ab38..6d14d8c 100644 --- a/doc/Changelog.rst +++ b/doc/Changelog.rst @@ -4,4 +4,5 @@ Changelog =================== * **0.1**: First version, base implementation done. Added docs and tests. * **0.2**: Added DateField, added optional fields, fixed some bugs. -* **0.3**: Fixed Python's 3 compatibility. Added some examples. \ No newline at end of file +* **0.3**: Fixed Python's 3 compatibility. Added some examples. +* **0.4**: Check if reserved words are used inside NestedField definition. Added example to README.md. Some bug fixes. \ No newline at end of file diff --git a/setup.py b/setup.py index b82e020..a7a93c4 100644 --- a/setup.py +++ b/setup.py @@ -2,10 +2,10 @@ setup( name = 'json2py', - version = '0.3', + version = '0.4', packages = ['json2py'], url = 'https://github.com/Wiston999/json2py', - download_url = 'https://github.com/Wiston999/json2py/tarball/v0.3', + download_url = 'https://github.com/Wiston999/json2py/tarball/v0.4', license = 'MIT', author = 'Victor Cabezas', author_email = 'wiston666@gmail.com',