Skip to content

Commit

Permalink
add new feature and test cases, bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
0xGosu committed Oct 18, 2019
1 parent 1e742cb commit 9b70ee9
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 9 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,26 @@ SERIALIZERS:
```
This will accept both of the `msgpack` and `django_msgpackpickle` but only output of result portfolio using `msgpack`
Once all service migrated, then switch to the first configuration

## Features
### This serializer will automatically encode and decode:
- DateTime, Date, Time, Duration:
object will be converted to string representation compatible with django.utils.dateparse
and convert back using django.utils.dateparse()
- Decimal:
object will be converted to byte string and then recover back to Decimal
- Django ORM instance:
object will be pickled using python cPickle/pickle library and depickled back to ORM Model instance
- Django ORM queryset:
object will be deform to Model + Query then pickled to avoid sending a list of instance

### String evaluation
This serializer can evaluate string that is compatible with `django.utils.dateparse` format
and auto convert the string to either `DateTime`, `Date`, `Time`, `Duration` object.

Also it can evaluate string with format like this:
`"<app_name.model_name.ID>"` this will be converted to an ORM instance: using `Model.objects.get(pk=ID)`
For example: `<auth.User.1>`

`"(app_name.model_name: RAW_QUERY_WITHOUT_SELECT_FROM)"` this will be converted to an ORM queryset
For example: `(auth.User: id >= 1 and date_joined > '2018-11-22 00:47:14.263837')`
2 changes: 1 addition & 1 deletion nameko_django/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.2
1.1.0
20 changes: 19 additions & 1 deletion nameko_django/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ def decode_list_object(list_obj):
r'|[-+]?P\d*D?T\d*H?\d*M?\d*S?' # duration ISO_8601
)

django_orm_re = re.compile(r"<([\w]+\.[\w]+)\.(\d+)>")
django_orm_queryset_re = re.compile(r"\s*\(([\w]+\.[\w]+):\s*(.+)\)", re.MULTILINE)

from django.apps import apps


def decode_single_object(obj):
if obj is None:
Expand All @@ -146,6 +151,15 @@ def decode_single_object(obj):
# if there is a datetime_obj can be decoded from string then return it
if datetime_obj is not None:
return datetime_obj
# check django orm evaluation from string
m = django_orm_re.match(obj)
if m:
return apps.get_model(m.group(1)).objects.get(pk=m.group(2))
m2 = django_orm_queryset_re.match(obj)
if m2:
model = apps.get_model(m2.group(1))
raw_id_qs = "SELECT id FROM {} WHERE {}".format(model._meta.db_table, m2.group(2).strip())
return model.objects.filter(id__in=[o.id for o in model.objects.raw(raw_id_qs)])
return obj


Expand All @@ -157,7 +171,11 @@ def dumps(o):
def loads(s):
if not isinstance(s, string_types):
s = bytes(s)
return unpackb(s, ext_hook=django_ext_hook, object_hook=decode_dict_object, list_hook=decode_list_object, raw=False)
r = unpackb(s, ext_hook=django_ext_hook, object_hook=decode_dict_object, list_hook=decode_list_object, raw=False)
if isinstance(r, string_types):
return decode_single_object(r)
else:
return r


register_args = (dumps, loads, 'application/x-django-msgpackpickle', 'binary')
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ flake8==3.7.8
nose==1.3.7
pytest==4.6.6
pytest-django==3.6.0
pytest-runner==5.1
9 changes: 7 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Created by vincenttran on 2019-09-02.
# Copyright (c) 2019 bentodatabase. All rights reserved.
#
import os
import os, sys

from os import path

Expand All @@ -20,6 +20,9 @@

from setuptools import setup

needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
pytest_runner = ['pytest-runner==5.1'] if needs_pytest else []

setup(
name='nameko-django',
version=__version__,
Expand All @@ -40,8 +43,10 @@
'msgpack>=0.5.0',
'aenum>=2.1.0'
],
setup_requires=[
] + pytest_runner,
test_suite='nose.collector',
tests_require=['nose'],
tests_require=['nose==1.3.7', 'pytest==4.6.6', 'pytest-django==3.6.0'],
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
Expand Down
66 changes: 62 additions & 4 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@

logger = logging.getLogger(__name__)

import django
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.test.utils import override_settings
from mock import call, patch

import pytest
from nameko_django.serializer import dumps, loads
from datetime import datetime, date, time, timedelta
from decimal import Decimal
from aenum import Enum, IntEnum, Constant
from collections import namedtuple
from django.utils.timezone import FixedOffset
from nose import tools
from django.db.models import ObjectDoesNotExist


def test_simple_list():
Expand Down Expand Up @@ -190,9 +191,8 @@ def test_decimal():
DEFAULT_INDEX_TABLESPACE='indexes',
LOGGING={},
)
settings.configure(**DJANGO_DEFAULT_SETTING)

django.setup()
settings.configure(**DJANGO_DEFAULT_SETTING)


def test_django_orm():
Expand All @@ -215,3 +215,61 @@ def test_django_orm_queryset():
logger.debug("new_qs=%s", qs2)
assert qs1.model._meta.db_table == qs2.model._meta.db_table
assert str(qs1) == str(qs2)


@pytest.mark.django_db
def test_django_orm_with_db(admin_user):
enc_data = dumps(admin_user)
dec_data = loads(enc_data)
logger.debug("admin_user=%s", admin_user)
assert dec_data.username == admin_user.username
assert dec_data.email == admin_user.email


@pytest.mark.django_db
def test_django_orm_eval_with_db(admin_user):
enc_data = dumps(['<auth.User.{}>'.format(admin_user.id), admin_user])
dec_data = loads(enc_data)
u, u1 = dec_data
logger.debug("admin_user.id=%s", admin_user.id)
assert u.username == u1.username == admin_user.username
assert u.email == u1.email == admin_user.email


@pytest.mark.django_db
def test_django_orm_eval_with_db2():
enc_data = dumps('<auth.User.2>')
with tools.assert_raises(ObjectDoesNotExist):
loads(enc_data)


@pytest.mark.django_db
def test_django_orm_queryset_with_db():
from django.contrib.auth.models import User
test_user_qs = User.objects.all().filter(last_login__isnull=False, id__gt=1000, date_joined__gt=datetime.now())
enc_data = dumps(test_user_qs)
dec_data = loads(enc_data)
qs1 = test_user_qs.query
qs2 = dec_data.query
assert qs1.model._meta.db_table == qs2.model._meta.db_table
assert str(qs1) == str(qs2)


@pytest.mark.django_db
def test_django_orm_queryset_eval_with_db(admin_user):
from django.contrib.auth.models import User
test_user_qs = User.objects.all().filter(id__gte=1, # last_login__isnull=False,
date_joined__gt='2018-11-22 00:47:14.263837')
qs_string = '(auth.User: {})'.format("id >= 1 and date_joined > '2018-11-22 00:47:14.263837'")
logger.debug(qs_string)
enc_data = dumps(qs_string)
dec_data = loads(enc_data)
qs1 = test_user_qs.query
qs2 = dec_data.query
logger.debug("qs1=%s", qs1)
logger.debug("qs2=%s", qs2)
assert len(test_user_qs) == 1 == dec_data.count()
assert list(test_user_qs) == list(dec_data)
u = dec_data.first()
assert u.username == admin_user.username
assert u.email == admin_user.email
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ toxworkdir = {toxinidir}/.tox

[testenv]
commands =
coverage run --source=nameko_django setup.py test
coverage run --source=nameko_django setup.py pytest
coverage report -m
deps =
coverage
Expand All @@ -48,3 +48,4 @@ commands = flake8 nameko_django
[pytest]
log_cli = true
log_level = INFO
addopts = --ignore=env

0 comments on commit 9b70ee9

Please sign in to comment.