Skip to content

Commit

Permalink
Merge pull request #15 from Wiston999/development
Browse files Browse the repository at this point in the history
Merge of development into master
  • Loading branch information
Wiston999 committed Apr 7, 2016
2 parents bc53283 + 31df256 commit 931cb6e
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 17 deletions.
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,65 @@
# json2py

## Description
Convert JSON/dict to python object and viceversa
Convert JSON/dict to python object and vice versa

## Installation
```
pip install json2py
```

## Example
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)
```

Output:
```javascript
[
{
"integer": 1000,
"floating": 10.5,
"hi": "world"
},
{
// Notice how "integer" value has changed
"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)

Expand Down
4 changes: 3 additions & 1 deletion doc/Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
Changelog
===================
* **0.1**: First version, base implementation done. Added docs and tests.
* **0.2**: Added DateField, added optional fields, fixed some bugs.
* **0.2**: Added DateField, added optional fields, fixed some bugs.
* **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.
6 changes: 3 additions & 3 deletions doc/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
Expand Down
2 changes: 1 addition & 1 deletion json2py/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
25 changes: 23 additions & 2 deletions json2py/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -82,6 +89,7 @@ def __str__(self):
def __repr__(self):
return self.__str__()


class TextField(BaseField):
"""
Class representing a string field in JSON.
Expand All @@ -104,6 +112,7 @@ def __str__(self):
def __repr__(self):
return self.__str__()


class NumberField(BaseField):
"""
Abstract class for representing JSON numbers.
Expand All @@ -118,6 +127,7 @@ def __str__(self):
def __repr__(self):
return self.__str__()


class IntegerField(NumberField):
"""
Class representing an integer field in JSON.
Expand Down Expand Up @@ -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)

def __init__(self, value = None, name = None, required = True):
super(NestedField, self).__setattr__('value', {})
super(NestedField, self).__setattr__('name', name)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
34 changes: 27 additions & 7 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 931cb6e

Please sign in to comment.