]> git.openstreetmap.org Git - nominatim.git/blob - nominatim/server/sanic/server.py
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / nominatim / server / sanic / server.py
1 # SPDX-License-Identifier: GPL-2.0-only
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Server implementation using the sanic webserver framework.
9 """
10 from typing import Any, Optional, Mapping
11 from pathlib import Path
12
13 import sanic
14
15 from nominatim.api import NominatimAPIAsync
16 from nominatim.apicmd.status import StatusResult
17 import nominatim.result_formatter.v1 as formatting
18
19 api = sanic.Blueprint('NominatimAPI')
20
21 CONTENT_TYPE = {
22   'text': 'text/plain; charset=utf-8',
23   'xml': 'text/xml; charset=utf-8'
24 }
25
26 def usage_error(msg: str) -> sanic.HTTPResponse:
27     """ Format the response for an error with the query parameters.
28     """
29     return sanic.response.text(msg, status=400)
30
31
32 def api_response(request: sanic.Request, result: Any) -> sanic.HTTPResponse:
33     """ Render a response from the query results using the configured
34         formatter.
35     """
36     body = request.ctx.formatter.format(result, request.ctx.format)
37     return sanic.response.text(body,
38                                content_type=CONTENT_TYPE.get(request.ctx.format,
39                                                              'application/json'))
40
41
42 @api.on_request # type: ignore[misc]
43 async def extract_format(request: sanic.Request) -> Optional[sanic.HTTPResponse]:
44     """ Get and check the 'format' parameter and prepare the formatter.
45         `ctx.result_type` describes the expected return type and
46         `ctx.default_format` the format value to assume when no parameter
47         is present.
48     """
49     assert request.route is not None
50     request.ctx.formatter = request.app.ctx.formatters[request.route.ctx.result_type]
51
52     request.ctx.format = request.args.get('format', request.route.ctx.default_format)
53     if not request.ctx.formatter.supports_format(request.ctx.format):
54         return usage_error("Parameter 'format' must be one of: " +
55                            ', '.join(request.ctx.formatter.list_formats()))
56
57     return None
58
59
60 @api.get('/status', ctx_result_type=StatusResult, ctx_default_format='text')
61 async def status(request: sanic.Request) -> sanic.HTTPResponse:
62     """ Implementation of status endpoint.
63     """
64     result = await request.app.ctx.api.status()
65     response = api_response(request, result)
66
67     if request.ctx.format == 'text' and result.status:
68         response.status = 500
69
70     return response
71
72
73 def get_application(project_dir: Path,
74                     environ: Optional[Mapping[str, str]] = None) -> sanic.Sanic:
75     """ Create a Nominatim sanic ASGI application.
76     """
77     app = sanic.Sanic("NominatimInstance")
78
79     app.ctx.api = NominatimAPIAsync(project_dir, environ)
80     app.ctx.formatters = {}
81     for rtype in (StatusResult, ):
82         app.ctx.formatters[rtype] = formatting.create(rtype)
83
84     app.blueprint(api)
85
86     return app