diff --git a/.editorconfig b/.editorconfig index 81752c6..2ae904c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,6 +21,7 @@ charset = utf-8 indent_size = 4 # isort plugin configuration known_first_party = jsonresolver +known_third_party = werkzeug multi_line_output = 2 default_section = THIRDPARTY diff --git a/AUTHORS.rst b/AUTHORS.rst index 81dcfae..8375d28 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -12,3 +12,4 @@ Authors JSON data resolver with support for plugins. - Jiri Kuncar +- Krzysztof Nowak diff --git a/jsonresolver/core.py b/jsonresolver/core.py index cbdc983..21fa66c 100644 --- a/jsonresolver/core.py +++ b/jsonresolver/core.py @@ -33,8 +33,7 @@ def __init__(self, plugins=None, entry_point_group=None): ) if entry_point_group: self.pm.load_setuptools_entrypoints(entry_point_group) - - self._build_url_map() + self.url_map = None def _build_url_map(self): """Build an URL map from registered plugins.""" @@ -43,6 +42,8 @@ def _build_url_map(self): def resolve(self, url): """Resolve given URL and use regitered loader.""" + if self.url_map is None: + self._build_url_map() parts = urlsplit(url) loader, args = self.url_map.bind(parts.hostname).match(parts.path) return loader(**args) diff --git a/jsonresolver/hookspec.py b/jsonresolver/hookspec.py index 4a7601d..5b0f687 100644 --- a/jsonresolver/hookspec.py +++ b/jsonresolver/hookspec.py @@ -3,9 +3,9 @@ # This file is part of jsonresolver # Copyright (C) 2015 CERN. # -# jsonresolver is free software; you can redistribute it and/or modify -# it under the terms of the Revised BSD License; see LICENSE file for -# more details. +# jsonresolver is free software; you can redistribute it and/or +# modify it under the terms of the Revised BSD License; see LICENSE +# file for more details. """Define hook specification.""" diff --git a/run-tests.sh b/run-tests.sh index ce114b5..307abb8 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -24,7 +24,7 @@ pep257 jsonresolver && \ -# FIXME isort -rc -c -df **/*.py && \ +isort -rc -c -df **/*.py && \ check-manifest --ignore ".travis-*" && \ sphinx-build -qnNW docs docs/_build/html && \ python setup.py test && \ diff --git a/tests/demo/internals.py b/tests/demo/internals.py index 858bff8..cb4da10 100755 --- a/tests/demo/internals.py +++ b/tests/demo/internals.py @@ -1,11 +1,23 @@ -import jsonresolver -import requests +# -*- coding: utf-8 -*- +# +# This file is part of jsonresolver +# Copyright (C) 2015 CERN. +# +# jsonresolver is free software; you can redistribute it and/or +# modify it under the terms of the Revised BSD License; see LICENSE +# file for more details. + +"""Test adding JSON resolving rule using ``werkzeug.routing.Rule`` object.""" +import requests from werkzeug.routing import Rule +import jsonresolver + @jsonresolver.hookimpl def jsonresolver_loader(url_map): + """Load the resolver plugin as a Rule to URL map.""" def endpoint(recid): return requests.get( 'https://cds.cern.ch/record/{recid}?of=recjson'.format(recid=recid) diff --git a/tests/demo/raising.py b/tests/demo/raising.py new file mode 100644 index 0000000..ff90555 --- /dev/null +++ b/tests/demo/raising.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# This file is part of jsonresolver +# Copyright (C) 2015 CERN. +# +# jsonresolver is free software; you can redistribute it and/or +# modify it under the terms of the Revised BSD License; see LICENSE +# file for more details. + +"""Test for detecting the lazy evaluation of decorated JSON resolver.""" + +import jsonresolver + + +class EndpointCallDetected(Exception): + """Raise this ``exception`` to detect when a plugin is called in test.""" + + +@jsonresolver.route('/test', host='http://localhost:4000') +def raising(): + """Raise an exception.""" + raise EndpointCallDetected diff --git a/tests/demo/raising_hook.py b/tests/demo/raising_hook.py new file mode 100644 index 0000000..c36d5eb --- /dev/null +++ b/tests/demo/raising_hook.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# This file is part of jsonresolver +# Copyright (C) 2015 CERN. +# +# jsonresolver is free software; you can redistribute it and/or +# modify it under the terms of the Revised BSD License; see LICENSE +# file for more details. + +"""Test for detecting the lazy evaluation of hook-registered JSON resolver.""" + +import jsonresolver + + +class HookRegistrationDetected(Exception): + """Raise this ``exception`` to detect when a hook is registered.""" + + +@jsonresolver.hookimpl +def jsonresolver_loader(url_map): + """Load the raising plugin as a Rule to URL map.""" + raise HookRegistrationDetected diff --git a/tests/demo/schema.py b/tests/demo/schema.py index b81cc83..fe250b3 100644 --- a/tests/demo/schema.py +++ b/tests/demo/schema.py @@ -7,10 +7,13 @@ # modify it under the terms of the Revised BSD License; see LICENSE # file for more details. +"""Test plugin for JSON schema resolving using decorator.""" + import jsonresolver @jsonresolver.route('/schema/', host='http://localhost:4000') def schema(name): + """Return a fixed JSON ``schema``.""" assert name == 'authors.json' return {'type': 'array'} diff --git a/tests/demo/simple.py b/tests/demo/simple.py index 0577cbf..0e583e9 100755 --- a/tests/demo/simple.py +++ b/tests/demo/simple.py @@ -7,9 +7,12 @@ # modify it under the terms of the Revised BSD License; see LICENSE # file for more details. +"""Test plugin for JSON resolving using ``jsonresolver.route`` decorator.""" + import jsonresolver @jsonresolver.route('/test', host='http://localhost:4000') def simple(): + """Return a fixed JSON.""" return {'test': 'test'} diff --git a/tests/test_core.py b/tests/test_core.py index 6693adf..b6c1511 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -14,6 +14,8 @@ import importlib import pytest +from demo.raising import EndpointCallDetected +from demo.raising_hook import HookRegistrationDetected from mock import patch from pkg_resources import EntryPoint @@ -21,7 +23,10 @@ class MockEntryPoint(EntryPoint): + """EntryPoint mock.""" + def load(self): + """Load module from the mocked entry point.""" if self.name == 'importfail': raise ImportError() else: @@ -29,8 +34,12 @@ def load(self): def _mock_entry_points(name): + """Mock the entry points.""" data = { 'espresso': [MockEntryPoint('demo.simple', 'demo.simple')], + 'raising': [MockEntryPoint('demo.raising', 'demo.raising')], + 'raising_hook': [MockEntryPoint('demo.raising_hook', + 'demo.raising_hook')], 'someotherstuff': [], 'doubletrouble': [MockEntryPoint('demo.simple', 'demo.simple'), MockEntryPoint('demo.simple', 'demo.simple')], @@ -45,10 +54,44 @@ def _mock_entry_points(name): @patch('pkg_resources.iter_entry_points', _mock_entry_points) def test_entry_point_group(): + """Test the entry point group loading.""" resolver = JSONResolver(entry_point_group='espresso') assert resolver.resolve('http://localhost:4000/test') == {'test': 'test'} def test_plugins(): + """Test the plugins loading.""" resolver = JSONResolver(plugins=['demo.simple']) assert resolver.resolve('http://localhost:4000/test') == {'test': 'test'} + + +@patch('pkg_resources.iter_entry_points', _mock_entry_points) +def test_plugin_lazy_execution(): + """Test the lazy evaluation of loaded plugin.""" + # Plugin code should be called (i.e. raise exception) on resolve method + resolver = JSONResolver(plugins=['demo.raising']) + with pytest.raises(EndpointCallDetected) as exc_info: + resolver.resolve('http://localhost:4000/test') + assert exc_info.type is EndpointCallDetected + + # Same as above but loaded using entry point + resolver = JSONResolver(entry_point_group='raising') + with pytest.raises(EndpointCallDetected) as exc_info: + resolver.resolve('http://localhost:4000/test') + assert exc_info.type is EndpointCallDetected + + +@patch('pkg_resources.iter_entry_points', _mock_entry_points) +def test_plugin_lazy_execution_hooks(): + """Test the lazy evaluation of loaded plugin through hooks.""" + # Plugin code should be called (i.e. raise exception) on resolve method + resolver = JSONResolver(plugins=['demo.raising_hook']) + with pytest.raises(HookRegistrationDetected) as exc_info: + resolver.resolve('http://localhost:4000/test') + assert exc_info.type is HookRegistrationDetected + + # Same as above but loaded using entry point + resolver = JSONResolver(entry_point_group='raising_hook') + with pytest.raises(HookRegistrationDetected) as exc_info: + resolver.resolve('http://localhost:4000/test') + assert exc_info.type is HookRegistrationDetected diff --git a/tests/test_jsonref.py b/tests/test_jsonref.py index ef10192..dde8de8 100644 --- a/tests/test_jsonref.py +++ b/tests/test_jsonref.py @@ -11,8 +11,8 @@ from __future__ import absolute_import -from jsonref import JsonRef, JsonRefError import pytest +from jsonref import JsonRef, JsonRefError from jsonresolver import JSONResolver from jsonresolver.contrib.jsonref import json_loader_factory diff --git a/tests/test_jsonschema.py b/tests/test_jsonschema.py index 3ff7d39..f1572f6 100644 --- a/tests/test_jsonschema.py +++ b/tests/test_jsonschema.py @@ -11,8 +11,8 @@ from __future__ import absolute_import -from jsonschema import RefResolutionError, validate import pytest +from jsonschema import RefResolutionError, validate from jsonresolver import JSONResolver from jsonresolver.contrib.jsonschema import ref_resolver_factory