Skip to content

Commit

Permalink
Performance improvements (#13)
Browse files Browse the repository at this point in the history
* Compute argspec for serialisation functions only once

* mocked_get_context -> mocked_inspect_getargspec

* Bump version to 1.3.2
  • Loading branch information
youtux authored and olegpidsadnyi committed Mar 6, 2017
1 parent 7dfaa74 commit 8b2a71e
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 14 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

1.3.2
-----

* Improve serialization performance (youtux)


1.3.1
-----

Expand Down
2 changes: 1 addition & 1 deletion halogen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""halogen public API."""

__version__ = '1.3.1'
__version__ = '1.3.2'

try:
from halogen.schema import Schema, attr, Attr, Link, Curie, Embedded, Accessor
Expand Down
17 changes: 12 additions & 5 deletions halogen/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ def BYPASS(value):
return value


def _get_context(func, kwargs):
def _get_context(argspec, kwargs):
"""Prepare a context for the serialization.
:param func: Function which needs or does not need kwargs.
:param argspec: The argspec of the serialization function.
:param kwargs: Dict with context
:return: Keywords arguments that function can accept.
"""
argspec = inspect.getargspec(func)
if argspec.keywords is not None:
return kwargs
return dict((arg, kwargs[arg]) for arg in argspec.args if arg in kwargs)
Expand All @@ -48,6 +47,10 @@ def __init__(self, getter=None, setter=None):
self.getter = getter
self.setter = setter

@cached_property
def _getter_argspec(self):
return inspect.getargspec(self.getter)

def get(self, obj, **kwargs):
"""Get an attribute from a value.
Expand All @@ -56,7 +59,7 @@ def get(self, obj, **kwargs):
"""
assert self.getter is not None, "Getter accessor is not specified."
if callable(self.getter):
return self.getter(obj, **_get_context(self.getter, kwargs))
return self.getter(obj, **_get_context(self._getter_argspec, kwargs))

assert isinstance(self.getter, string_types), "Accessor must be a function or a dot-separated string."

Expand Down Expand Up @@ -153,6 +156,10 @@ def accessor(self):
attr = self.attr or self.name
return Accessor(getter=attr, setter=attr)

@cached_property
def _attr_type_serialize_argspec(self):
return inspect.getargspec(self.attr_type.serialize)

def serialize(self, value, **kwargs):
"""Serialize the attribute of the input data.
Expand All @@ -172,7 +179,7 @@ def serialize(self, value, **kwargs):
raise
value = self.default() if callable(self.default) else self.default

return self.attr_type.serialize(value, **_get_context(self.attr_type.serialize, kwargs))
return self.attr_type.serialize(value, **_get_context(self._attr_type_serialize_argspec, kwargs))

return self.attr_type

Expand Down
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ def run_tests(self):
"Topic :: Software Development :: Libraries",
"Topic :: Utilities",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3"
] + [("Programming Language :: Python :: %s" % x) for x in "2.7 3.4".split()],
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
],
cmdclass={"test": ToxTestCommand},
packages=["halogen"],
install_requires=install_requires,
Expand Down
12 changes: 9 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Test configuration."""
import inspect

import halogen
import mock
Expand All @@ -7,8 +8,13 @@


@pytest.fixture(scope="session")
def mocked_get_context():
def mocked_inspect_getargspec(request):
"""Mock halogen.schema._get_context for returning empty dict."""
patcher = mock.patch("halogen.schema._get_context")
def f():
return None

patcher = mock.patch("inspect.getargspec")
patcher.return_value = inspect.getargspec(f)

patcher.start()
patcher.return_value = {}
request.addfinalizer(patcher.stop)
2 changes: 1 addition & 1 deletion tests/unit/schema/accessor/test_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_get_object_callable(basic_object_value, basic_object):
assert acc.get(basic_object) == basic_object_value


def test_get_object_callable_called(mocked_get_context, basic_object):
def test_get_object_callable_called(mocked_inspect_getargspec, basic_object):
"""Test if Accessor.get() calls Accessor.getter() if getter is callable."""
acc = Accessor()
acc.getter = mock.Mock()
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/schema/attr/test_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from halogen.types import Type


def test_serialize_type(mocked_get_context):
def test_serialize_type(mocked_inspect_getargspec):
"""Test if serialization of a "type" works.
Test if serialization of an Attr with an attr_type that is a "type" correctly calls the serialization function.
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
distshare = {homedir}/.tox/distshare
envlist = py27,py34
envlist = py27,py34,py35,py36

[testenv]
commands = py.test --junitxml={envlogdir}/junit-{envname}.xml halogen tests
Expand Down

0 comments on commit 8b2a71e

Please sign in to comment.