Repository: simphony/tornado-webapi

Issue:
Support multiple registries
The current global registry is convenient for a single application, but it's a global object. This introduces major problems when testing. We need to be able to handle multiple registers, each one providing its own api.


Discussion:

Patch:
diff --git a/tornadowebapi/__init__.py b/tornadowebapi/__init__.py
index 12f7bda..9cf510d 100644
--- a/tornadowebapi/__init__.py
+++ b/tornadowebapi/__init__.py
@@ -1,5 +1,4 @@
-from .handler import ResourceHandler, CollectionHandler
-from .utils import url_path_join, with_end_slash
+from . import registry
 
 MAJOR = 0
 MINOR = 1
@@ -30,13 +29,4 @@ def api_handlers(base_urlpath, version="v1"):
     The current implementation does not support multiple API versions yet.
     The version option is only provided for futureproofing.
     """
-    return [
-        (with_end_slash(
-            url_path_join(base_urlpath, "api", version, "(.*)", "(.*)")),
-         ResourceHandler
-         ),
-        (with_end_slash(
-            url_path_join(base_urlpath, "api", version, "(.*)")),
-         CollectionHandler
-         ),
-        ]
+    return registry.registry.api_handlers(base_urlpath, version)
diff --git a/tornadowebapi/handler.py b/tornadowebapi/handler.py
index f9c8956..609a1c5 100644
--- a/tornadowebapi/handler.py
+++ b/tornadowebapi/handler.py
@@ -4,11 +4,14 @@ from tornado.log import app_log
 from . import exceptions
 from .http import httpstatus
 from .http.payloaded_http_error import PayloadedHTTPError
-from .registry import registry
 from .utils import url_path_join, with_end_slash
 
 
 class BaseHandler(web.RequestHandler):
+    def initialize(self, registry):
+        """Initialization method for when the class is instantiated."""
+        self._registry = registry
+
     @gen.coroutine
     def prepare(self):
         """Runs before any specific handler. """
@@ -17,8 +20,8 @@ class BaseHandler(web.RequestHandler):
 
     @property
     def registry(self):
-        """Returns the global class vs Resource registry"""
-        return registry
+        """Returns the class vs Resource registry"""
+        return self._registry
 
     @property
     def log(self):
diff --git a/tornadowebapi/registry.py b/tornadowebapi/registry.py
index c5bac97..ee6bfe0 100644
--- a/tornadowebapi/registry.py
+++ b/tornadowebapi/registry.py
@@ -1,3 +1,5 @@
+from .handler import ResourceHandler, CollectionHandler
+from .utils import url_path_join, with_end_slash
 from .resource import Resource
 from .authenticator import NullAuthenticator
 
@@ -63,5 +65,36 @@ class Registry:
         """If the registry contains the given item"""
         return item in self._registered_types
 
+    def api_handlers(self, base_urlpath, version="v1"):
+        """Returns the API handlers for the interface.
+        Add these handlers to your application to provide an
+        interface to your Resources.
+
+
+        Parameters
+        ----------
+        base_urlpath: str
+            The base url path to serve
+        version: str
+            A string identifying the version of the API.
+
+        Notes
+        -----
+        The current implementation does not support multiple API versions yet.
+        The version option is only provided for futureproofing.
+        """
+        return [
+            (with_end_slash(
+                url_path_join(base_urlpath, "api", version, "(.*)", "(.*)")),
+             ResourceHandler,
+             dict(registry=self)
+             ),
+            (with_end_slash(
+                url_path_join(base_urlpath, "api", version, "(.*)")),
+             CollectionHandler,
+             dict(registry=self)
+             ),
+        ]
+
 #: global registry for registration of the classes.
 registry = Registry()


Test patch:
diff --git a/tornadowebapi/tests/test_registry.py b/tornadowebapi/tests/test_registry.py
index 1dc5433..0b793df 100644
--- a/tornadowebapi/tests/test_registry.py
+++ b/tornadowebapi/tests/test_registry.py
@@ -42,3 +42,11 @@ class TestRegistry(unittest.TestCase):
         reg = Registry()
 
         self.assertIsNotNone(reg.authenticator)
+
+    def test_api_handlers(self):
+        reg = Registry()
+        api_handlers = reg.api_handlers("/foo")
+        self.assertEqual(len(api_handlers), 2)
+
+        self.assertEqual(api_handlers[0][2]["registry"], reg)
+        self.assertEqual(api_handlers[1][2]["registry"], reg)
diff --git a/tornadowebapi/tests/test_webapi.py b/tornadowebapi/tests/test_webapi.py
index d756ece..3a3c8e5 100644
--- a/tornadowebapi/tests/test_webapi.py
+++ b/tornadowebapi/tests/test_webapi.py
@@ -6,6 +6,7 @@ from unittest import mock
 import tornadowebapi
 from tornadowebapi import registry, exceptions
 from tornadowebapi.http import httpstatus
+from tornadowebapi.registry import Registry
 from tornadowebapi.resource import Resource
 from tornadowebapi.handler import ResourceHandler, CollectionHandler
 from tornadowebapi.tests.utils import AsyncHTTPTestCase
@@ -56,6 +57,16 @@ class Student(Resource):
         return list(self.collection.keys())
 
 
+class Teacher(Resource):
+    @gen.coroutine
+    def retrieve(self, identifier):
+        return {}
+
+    @gen.coroutine
+    def items(self):
+        return []
+
+
 class UnsupportAll(Resource):
     pass
 
@@ -399,3 +410,23 @@ class TestRESTFunctions(unittest.TestCase):
         self.assertEqual(handlers[0][1], ResourceHandler)
         self.assertEqual(handlers[1][0], "/foo/api/v1/(.*)/")
         self.assertEqual(handlers[1][1], CollectionHandler)
+
+
+class TestNonGlobalRegistry(AsyncHTTPTestCase):
+    def setUp(self):
+        super().setUp()
+        Student.collection = OrderedDict()
+        Student.id = 0
+
+    def get_app(self):
+        self.registry = Registry()
+        self.registry.register(Teacher)
+        handlers = self.registry.api_handlers('/')
+        app = web.Application(handlers=handlers)
+        return app
+
+    def test_non_global_registry(self):
+        res = self.fetch("/api/v1/teachers/")
+        self.assertEqual(res.code, httpstatus.OK)
+        self.assertEqual(escape.json_decode(res.body),
+                         {"items": []})