Repository: ai4os/DEEPaaS (language: python)

Issue:
Ability to define "basePath"
### Description

Sometimes, especially when behind the reverse proxy, it might be useful to have "basePath" for the API not as `/` but as something else, e.g `/api`. It would be nice, if a user could (re-)define this "basePath"

In the case of OAS3, in swagger.yaml this would correspond to:

```
servers:
  - url: /api
```

Ref: https://swagger.io/docs/specification/api-host-and-base-path/




Discussion / hints:

Fix patch:
diff --git a/deepaas/cmd/run.py b/deepaas/cmd/run.py
index b5532224..8f953d27 100644
--- a/deepaas/cmd/run.py
+++ b/deepaas/cmd/run.py
@@ -45,6 +45,17 @@
 
 The DEEPaaS API service listens on this port number for incoming
 requests.
+""",
+    ),
+    # Allow to define base path for the API
+    cfg.StrOpt(
+        "base-path",
+        default="",
+        help="""
+Base path for the API. This is useful when the API is served behind a
+reverse proxy that is not at the root of the domain. For example, if
+the API is served at https://example.com/deepaas, then the base path
+should be set to /deepaas. Defaults to the root of the domain.
 """,
     ),
 ]
@@ -95,6 +106,7 @@ def main():
         enable_doc=CONF.doc_endpoint,
         enable_train=CONF.train_endpoint,
         enable_predict=CONF.predict_endpoint,
+        base_path=CONF.base_path,
     )
     web.run_app(
         app,


Test patch:
diff --git a/deepaas/tests/test_v2_api.py b/deepaas/tests/test_v2_api.py
index 7ad89df4..b6cad651 100644
--- a/deepaas/tests/test_v2_api.py
+++ b/deepaas/tests/test_v2_api.py
@@ -164,6 +164,59 @@ async def test_not_found(self):
         self.assertEqual(404, ret.status)
 
 
+class TestApiV2CusomBasePath(base.TestCase):
+    async def get_application(self):
+        app = web.Application(debug=True)
+        app.middlewares.append(web.normalize_path_middleware())
+
+        deepaas.model.v2.register_models(app)
+
+        v2app = v2.get_app(base_path="/custom")
+        app.add_subapp("/v2", v2app)
+
+        return app
+
+    def setUp(self):
+        super(TestApiV2CusomBasePath, self).setUp()
+
+        self.maxDiff = None
+
+        self.flags(debug=True)
+
+    def assert_ok(self, response):
+        self.assertIn(response.status, [200, 201])
+
+    async def test_predict_data(self):
+        f = six.BytesIO(b"foo")
+        ret = await self.client.post(
+            "/custom/v2/models/deepaas-test/predict/",
+            data={"data": (f, "foo.txt"), "parameter": 1},
+        )
+        json = await ret.json()
+        self.assertEqual(200, ret.status)
+        self.assertDictEqual(fake_responses.deepaas_test_predict, json)
+
+    async def test_train(self):
+        ret = await self.client.post(
+            "/custom/v2/models/deepaas-test/train/", data={"sleep": 0}
+        )
+        self.assertEqual(200, ret.status)
+        json = await ret.json()
+        json.pop("date")
+        self.assertDictEqual(fake_responses.deepaas_test_train, json)
+
+    async def test_get_metadata(self):
+        meta = fake_responses.models_meta
+
+        ret = await self.client.get("/custom/v2/models/")
+        self.assert_ok(ret)
+        self.assertDictEqual(meta, await ret.json())
+
+        ret = await self.client.get("/custom/v2/models/deepaas-test/")
+        self.assert_ok(ret)
+        self.assertDictEqual(meta["models"][0], await ret.json())
+
+
 class TestApiV2(base.TestCase):
     async def get_application(self):
         app = web.Application(debug=True)