Skip to content

Commit

Permalink
Merge ac0c092 into b85f59c
Browse files Browse the repository at this point in the history
  • Loading branch information
den4uk committed Jul 7, 2019
2 parents b85f59c + ac0c092 commit 7ec2da8
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 28 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ jsonextra
[![Build Status](https://travis-ci.org/den4uk/jsonextra.svg?branch=master)](https://travis-ci.org/den4uk/jsonextra)
[![Codecov](https://codecov.io/gh/den4uk/jsonextra/branch/master/graph/badge.svg)](https://codecov.io/gh/den4uk/jsonextra)
[![PyPI Version](http://img.shields.io/pypi/v/jsonextra.svg)](https://pypi.python.org/pypi/jsonextra)
[![License](http://img.shields.io/pypi/l/jsonextra.svg)](https://pypi.python.org/pypi/jsonextra)
[![License](https://img.shields.io/github/license/den4uk/jsonextra.svg)](https://pypi.python.org/pypi/jsonextra)

_is same as `json` library but with extra support for `uuid` and `datetime` data classes_
_same as `json` library, but with extra support for `bytes`, `uuid` and `datetime` data classes_

## Installation

Expand Down Expand Up @@ -50,3 +50,4 @@ If this behaviour is undesired, please use the built-in `json.loads` method inst
- `datetime.date`
- `datetime.datetime`
- `uuid.UUID`
- `bytes`
14 changes: 14 additions & 0 deletions jsonextra/json_extra.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import re
import json
import uuid
import base64
import contextlib
import dateutil.parser

uuid_rex = re.compile(r'^[0-9a-f]{8}\-?[0-9a-f]{4}\-?4[0-9a-f]{3}\-?[89ab][0-9a-f]{3}\-?[0-9a-f]{12}$', re.I)
datetime_rex = re.compile(r'^\d{4}\-[01]\d\-[0-3]\d[\sT][0-2]\d\:[0-5]\d\:[0-5]\d')
date_rex = re.compile(r'^\d{4}\-[01]\d\-[0-3]\d$')
bytes_prefix = 'base64:'
bytes_rex = re.compile(r'^base64:([\w\d+/]*?\={,2}?)$')


class ExtraEncoder(json.JSONEncoder):

@staticmethod
def bytes_to_b64(data: bytes) -> str:
return base64.b64encode(data).decode()

def default(self, obj):
try:
return super().default(obj)
except TypeError:
if isinstance(obj, bytes):
return (bytes_prefix + self.bytes_to_b64(obj))
return str(obj)


Expand All @@ -30,6 +40,10 @@ def _apply_extras(value: str):
return dateutil.parser.parse(value).date()
elif datetime_rex.match(value):
return dateutil.parser.parse(value)
else:
try_bytes = bytes_rex.match(value)
if try_bytes:
return base64.b64decode(try_bytes.groups()[0])
return value

def object_hook(self, obj):
Expand Down
4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-r requirements.txt
pytest
coverage
-e .
wheel
pytest-cov
coveralls
codecov
Expand Down
27 changes: 16 additions & 11 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
import re
import os.path
from setuptools import setup
from jsonextra import __version__

req = os.path.join(os.path.dirname(__file__), 'requirements.txt')
with open(req, 'rt', encoding='utf-8') as f:
install_requires = [dep for dep in f.read().splitlines() if not dep.startswith('#')]

def readfile(fls: list):
with open(os.path.join(os.path.dirname(__file__), *fls), 'rt', encoding='utf-8') as fh:
return fh.read()

reme = os.path.join(os.path.dirname(__file__), 'README.md')
with open(reme, 'rt', encoding='utf-8') as f:
long_description = f.read()

_version = re.search('__version__ = [\'"](.+)[\'"]', readfile(['jsonextra', '__init__.py'])).groups()[0]


install_requires = [
dep for dep in readfile(['requirements.txt']).splitlines()
if not dep.startswith('#')
]


setup(
name='jsonextra',
version=__version__,
description='JSON Extra | JSON that gives you extra datetime and uuid data types',
version=_version,
description='JSON Extra | JSON that gives you extra datetime, uuid and bytes data types',
author='Denis Sazonov',
author_email='den@saz.lt',
packages=['jsonextra'],
license='MIT License',
keywords="json uuid datetime date".split(),
keywords="json uuid datetime date bytes".split(),
install_requires=install_requires,
include_package_data=True,
long_description=long_description,
long_description=readfile(['README.md']),
long_description_content_type="text/markdown",
url="https://github.com/den4uk/jsonextra",
classifiers=[
Expand Down
74 changes: 61 additions & 13 deletions tests/test_jsonextra.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,41 @@
import jsonextra


dict_example = {
dict_flat = {
'id': uuid.UUID('d05c6319-0944-4dd6-819f-3a2dc6f7a3c2'),
'town': 'Hill Valley',
'episode': 2,
'date': datetime.date(2015, 10, 21),
'time': datetime.datetime(2019, 6, 14, 20, 43, 53, 207572),
'secret': b'\xd6aO\x1d\xd71Y\x05',
'anone': None,
'watch_again': True,
}

json_example = '{"id": "d05c6319-0944-4dd6-819f-3a2dc6f7a3c2", "town": "Hill Valley", "episode": 2, "date": "2015-10-21", "time": "2019-06-14 20:43:53.207572", "anone": null, "watch_again": true}'
json_flat = '''{
"id": "d05c6319-0944-4dd6-819f-3a2dc6f7a3c2",
"town": "Hill Valley",
"episode": 2,
"date": "2015-10-21",
"time": "2019-06-14 20:43:53.207572",
"secret": "base64:1mFPHdcxWQU=",
"anone": null,
"watch_again": true
}'''


dict_nested = {
'events': {
'admin': uuid.UUID('c05c6319-0944-4dd6-819f-3a2dc6f7a3c2'),
'dates': [datetime.date(2016, 11, 22), datetime.date(2017, 12, 23)],
'dates': [
datetime.date(2016, 11, 22),
datetime.date(2017, 12, 23)
],
'uids': {
'other': [uuid.UUID('b05c6319-0944-4dd6-819f-3a2dc6f7a3c2'), uuid.UUID('a05c6319-0944-4dd6-819f-3a2dc6f7a3c2')],
'other': [
uuid.UUID('b05c6319-0944-4dd6-819f-3a2dc6f7a3c2'),
uuid.UUID('a05c6319-0944-4dd6-819f-3a2dc6f7a3c2')
],
'more': [
{
'a': datetime.date(2018, 1, 24)
Expand All @@ -36,23 +52,52 @@
}
}

json_nested = '{"events": {"admin": "c05c6319-0944-4dd6-819f-3a2dc6f7a3c2", "dates": ["2016-11-22", "2017-12-23"], "uids": {"other": ["b05c6319-0944-4dd6-819f-3a2dc6f7a3c2", "a05c6319-0944-4dd6-819f-3a2dc6f7a3c2"], "more": [{"a": "2018-01-24"}, {"b": "2019-02-25"}]}}}'

json_nested = '''{
"events": {
"admin": "c05c6319-0944-4dd6-819f-3a2dc6f7a3c2",
"dates": [
"2016-11-22",
"2017-12-23"
],
"uids": {
"other": [
"b05c6319-0944-4dd6-819f-3a2dc6f7a3c2",
"a05c6319-0944-4dd6-819f-3a2dc6f7a3c2"
],
"more": [
{
"a": "2018-01-24"
},
{
"b": "2019-02-25"
}
]
}
}
}'''


def test_load():
i = io.StringIO(json_example)
assert jsonextra.load(i) == dict_example
@pytest.mark.parametrize('py_obj, json_obj', [
(dict_flat, json_flat),
(dict_nested, json_nested),
])
def test_load(py_obj, json_obj):
i = io.StringIO(json_obj)
assert jsonextra.load(i) == py_obj


def test_dump():
@pytest.mark.parametrize('py_obj, json_obj', [
(dict_flat, json_flat),
(dict_nested, json_nested),
])
def test_dump(py_obj, json_obj):
i = io.StringIO()
jsonextra.dump(dict_example, i)
assert i.getvalue() == json_example
jsonextra.dump(py_obj, i, indent=2)
assert i.getvalue() == json_obj


many_list = [
(dict_example, json_example),
(dict_nested, json_nested),
({'x': 'foo'}, '{"x": "foo"}'),
({'x': 2}, '{"x": 2}'),
({'x': True}, '{"x": true}'),
Expand All @@ -61,6 +106,8 @@ def test_dump():
({'x': datetime.date(1991, 2, 16)}, '{"x": "1991-02-16"}'),
({'x': datetime.datetime(2001, 12, 1, 14, 58, 17)}, '{"x": "2001-12-01 14:58:17"}'),
({'x': datetime.datetime(2001, 12, 1, 14, 58, 17, 123456)}, '{"x": "2001-12-01 14:58:17.123456"}'),
({'x': b'hello'}, '{"x": "base64:aGVsbG8="}'),
({'x': b''}, '{"x": "base64:"}'),
]


Expand All @@ -84,6 +131,7 @@ def test_loads_many(py_obj, json_obj):
({'x': uuid.UUID('98f395f2-6ecb-46d8-98e4-926b8dfdd070')}, '{"x": "98f395f26ecb46d898e4926b8dfdd070"}'), # No dashes, but is valid
({'x': uuid.UUID('98f395f2-6ecb-46d8-98e4-926b8dfdd070')}, '{"x": "98F395F26ECB46D898E4926B8DFDD070"}'), # As above, uppercase
({'x': uuid.UUID('98f395f2-6ecb-46d8-98e4-926b8dfdd070')}, '{"x": "98F395F26ecb46d898e4926b8DFDD070"}'), # As above, mixed case
({'x': 'base64:Oly==='}, '{"x": "base64:Oly==="}'), # invalid base64 string (too much padding)
]


Expand Down

0 comments on commit 7ec2da8

Please sign in to comment.