]> git.openstreetmap.org Git - nominatim.git/commitdiff
add sanic development server implementation
authorSarah Hoffmann <lonvia@denofr.de>
Thu, 1 Dec 2022 16:58:22 +0000 (17:58 +0100)
committerSarah Hoffmann <lonvia@denofr.de>
Tue, 3 Jan 2023 09:02:53 +0000 (10:02 +0100)
nominatim/cli.py
nominatim/clicmd/args.py
nominatim/result_formatter/base.py
nominatim/result_formatter/v1.py
nominatim/server/__init__.py [new file with mode: 0644]
nominatim/server/sanic/__init__.py [new file with mode: 0644]
nominatim/server/sanic/server.py [new file with mode: 0644]

index 56ed6a078eb3a082f9430564b279b1470348c89e..c134ca1808c2a2fe7267fc26947c54931bb23ab7 100644 (file)
@@ -197,10 +197,15 @@ class AdminServe:
     """\
     Start a simple web server for serving the API.
 
-    This command starts the built-in PHP webserver to serve the website
+    This command starts a built-in webserver to serve the website
     from the current project directory. This webserver is only suitable
     for testing and development. Do not use it in production setups!
 
+    There are different webservers available. The default 'php' engine
+    runs the classic PHP frontend. 'sanic' and 'falcon' are Python servers
+    which run the new Python frontend code. This is highly experimental
+    at the moment and may not include the full API.
+
     By the default, the webserver can be accessed at: http://127.0.0.1:8088
     """
 
@@ -208,10 +213,31 @@ class AdminServe:
         group = parser.add_argument_group('Server arguments')
         group.add_argument('--server', default='127.0.0.1:8088',
                            help='The address the server will listen to.')
+        group.add_argument('--engine', default='php',
+                           choices=('php', 'sanic', 'falcon'),
+                           help='Webserver framework to run. (default: php)')
 
 
     def run(self, args: NominatimArgs) -> int:
-        run_php_server(args.server, args.project_dir / 'website')
+        if args.engine == 'php':
+            run_php_server(args.server, args.project_dir / 'website')
+        elif args.engine == 'sanic':
+            import nominatim.server.sanic.server
+
+            app = nominatim.server.sanic.server.get_application(args.project_dir)
+
+            server_info = args.server.split(':', 1)
+            host = server_info[0]
+            if len(server_info) > 1:
+                if not server_info[1].isdigit():
+                    raise UsageError('Invalid format for --server parameter. Use <host>:<port>')
+                port = int(server_info[1])
+            else:
+                port = 8088
+            app.run(host=host, port=port, debug=True)
+        elif args.engine == 'falcon':
+            raise NotImplementedError('Support for falcon not yet available.')
+
         return 0
 
 
index 15de72a5bc7c61c797b3a3fd8cd005a2cd1153f2..e47287b33dff17c7f62c335ad5dc9ed08b6236e8 100644 (file)
@@ -127,6 +127,7 @@ class NominatimArgs:
 
     # Arguments to 'serve'
     server: str
+    engine: str
 
     # Arguments to 'special-phrases
     import_from_wiki: bool
index 88f4d9183a3ff172fbe856741d88423d65312c2f..d77f4db883d33171d152570c461050f1438cbb4b 100644 (file)
@@ -28,6 +28,12 @@ class ResultFormatter(Generic[T]):
         return list(self.functions.keys())
 
 
+    def supports_format(self, fmt: str) -> bool:
+        """ Check if the given format is supported by this formatter.
+        """
+        return fmt in self.functions
+
+
     def format(self, result: T, fmt: str) -> str:
         """ Convert the given result into a string using the given format.
 
index d14e3f6c4105209faf387f0446a14bc2f4a981a2..081f451e5f01ad5b2ea74e5d5022eb2a76d2d37b 100644 (file)
@@ -28,7 +28,7 @@ def _format_status_json(result: StatusResult) -> str:
     out['status'] = result.status
     out['message'] = result.message
     if result.data_updated is not None:
-        out['data_updated'] = result.data_updated
+        out['data_updated'] = result.data_updated.isoformat()
     out['software_version'] = result.software_version
     if result.database_version is not None:
         out['database_version'] = result.database_version
diff --git a/nominatim/server/__init__.py b/nominatim/server/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/nominatim/server/sanic/__init__.py b/nominatim/server/sanic/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/nominatim/server/sanic/server.py b/nominatim/server/sanic/server.py
new file mode 100644 (file)
index 0000000..9c75327
--- /dev/null
@@ -0,0 +1,62 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# This file is part of Nominatim. (https://nominatim.org)
+#
+# Copyright (C) 2022 by the Nominatim developer community.
+# For a full list of authors see the git log.
+"""
+Server implementation using the sanic webserver framework.
+"""
+from pathlib import Path
+
+import sanic
+
+from nominatim.api import NominatimAPIAsync
+from nominatim.apicmd.status import StatusResult
+import nominatim.result_formatter.v1 as formatting
+
+api = sanic.Blueprint('NominatimAPI')
+
+CONTENT_TYPE = {
+  'text': 'text/plain; charset=utf-8',
+  'xml': 'text/xml; charset=utf-8'
+}
+
+def usage_error(msg):
+    return sanic.response.text(msg, status=400)
+
+
+def api_response(request, result):
+    body = request.ctx.formatter.format(result, request.ctx.format)
+    return sanic.response.text(body,
+                               content_type=CONTENT_TYPE.get(request.ctx.format, 'application/json'))
+
+
+@api.on_request
+async def extract_format(request):
+    request.ctx.formatter = request.app.ctx.formatters[request.route.ctx.result_type]
+
+    request.ctx.format = request.args.get('format', request.route.ctx.default_format)
+    if not request.ctx.formatter.supports_format(request.ctx.format):
+        return usage_error("Parameter 'format' must be one of: " +
+                           ', '.join(request.ctx.formatter.list_formats()))
+
+
+@api.get('/status', ctx_result_type=StatusResult, ctx_default_format='text')
+async def status(request):
+    return api_response(request,await request.app.ctx.api.status())
+
+
+def get_application(project_dir: Path) -> sanic.Sanic:
+    app = sanic.Sanic("NominatimInstance")
+
+    app.ctx.api = NominatimAPIAsync(project_dir)
+    app.ctx.formatters = {}
+    for rtype in (StatusResult, ):
+        app.ctx.formatters[rtype] = formatting.create(rtype)
+
+    app.blueprint(api)
+
+    return app
+
+