forked from hans/Nominatim
Merge pull request #3093 from lonvia/remove-sanic
Remove support for Sanic
This commit is contained in:
2
.github/workflows/ci-tests.yml
vendored
2
.github/workflows/ci-tests.yml
vendored
@@ -113,7 +113,7 @@ jobs:
|
||||
if: matrix.flavour == 'oldstuff'
|
||||
|
||||
- name: Install Python webservers
|
||||
run: pip3 install falcon sanic sanic-testing sanic-cors starlette
|
||||
run: pip3 install falcon starlette
|
||||
|
||||
- name: Install latest pylint
|
||||
run: pip3 install -U pylint asgi_lifespan
|
||||
|
||||
@@ -67,9 +67,8 @@ For running the experimental Python frontend:
|
||||
|
||||
* one of the following web frameworks:
|
||||
* [falcon](https://falconframework.org/) (3.0+)
|
||||
* [sanic](https://sanic.dev) and (optionally) [sanic-cors](https://github.com/ashleysommer/sanic-cors)
|
||||
* [starlette](https://www.starlette.io/)
|
||||
* [uvicorn](https://www.uvicorn.org/) (only with falcon and starlette framworks)
|
||||
* [uvicorn](https://www.uvicorn.org/)
|
||||
|
||||
For dependencies for running tests and building documentation, see
|
||||
the [Development section](../develop/Development-Environment.md).
|
||||
|
||||
@@ -41,7 +41,6 @@ It has the following additional requirements:
|
||||
For testing the Python search frontend, you need to install extra dependencies
|
||||
depending on your choice of webserver framework:
|
||||
|
||||
* [sanic-testing](https://sanic.dev/en/plugins/sanic-testing/getting-started.html) (sanic only)
|
||||
* [httpx](https://www.python-httpx.org/) (starlette only)
|
||||
* [asgi-lifespan](https://github.com/florimondmanca/asgi-lifespan) (starlette only)
|
||||
|
||||
@@ -66,7 +65,7 @@ sudo apt install php-cgi phpunit php-codesniffer \
|
||||
pip3 install --user behave mkdocs mkdocstrings pytest pytest-asyncio pylint \
|
||||
mypy types-PyYAML types-jinja2 types-psycopg2 types-psutil \
|
||||
types-ujson types-requests types-Pygments typing-extensions\
|
||||
sanic-testing httpx asgi-lifespan
|
||||
httpx asgi-lifespan
|
||||
```
|
||||
|
||||
The `mkdocs` executable will be located in `.local/bin`. You may have to add
|
||||
|
||||
@@ -248,12 +248,11 @@ class ICUQueryAnalyzer(AbstractQueryAnalyzer):
|
||||
and (repl.ttype != qmod.TokenType.HOUSENUMBER
|
||||
or len(tlist.tokens[0].lookup_word) > 4):
|
||||
repl.add_penalty(0.39)
|
||||
elif tlist.ttype == qmod.TokenType.HOUSENUMBER:
|
||||
elif tlist.ttype == qmod.TokenType.HOUSENUMBER \
|
||||
and len(tlist.tokens[0].lookup_word) <= 3:
|
||||
if any(c.isdigit() for c in tlist.tokens[0].lookup_word):
|
||||
for repl in node.starting:
|
||||
if repl.end == tlist.end and repl.ttype != qmod.TokenType.HOUSENUMBER \
|
||||
and (repl.ttype != qmod.TokenType.HOUSENUMBER
|
||||
or len(tlist.tokens[0].lookup_word) <= 3):
|
||||
if repl.end == tlist.end and repl.ttype != qmod.TokenType.HOUSENUMBER:
|
||||
repl.add_penalty(0.5 - tlist.tokens[0].penalty)
|
||||
elif tlist.ttype not in (qmod.TokenType.COUNTRY, qmod.TokenType.PARTIAL):
|
||||
norm = parts[i].normalized
|
||||
|
||||
@@ -233,12 +233,11 @@ class LegacyQueryAnalyzer(AbstractQueryAnalyzer):
|
||||
and (repl.ttype != qmod.TokenType.HOUSENUMBER
|
||||
or len(tlist.tokens[0].lookup_word) > 4):
|
||||
repl.add_penalty(0.39)
|
||||
elif tlist.ttype == qmod.TokenType.HOUSENUMBER:
|
||||
elif tlist.ttype == qmod.TokenType.HOUSENUMBER \
|
||||
and len(tlist.tokens[0].lookup_word) <= 3:
|
||||
if any(c.isdigit() for c in tlist.tokens[0].lookup_word):
|
||||
for repl in node.starting:
|
||||
if repl.end == tlist.end and repl.ttype != qmod.TokenType.HOUSENUMBER \
|
||||
and (repl.ttype != qmod.TokenType.HOUSENUMBER
|
||||
or len(tlist.tokens[0].lookup_word) <= 3):
|
||||
if repl.end == tlist.end and repl.ttype != qmod.TokenType.HOUSENUMBER:
|
||||
repl.add_penalty(0.5 - tlist.tokens[0].penalty)
|
||||
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ def extend_query_parts(queryparts: Dict[str, Any], details: Dict[str, Any],
|
||||
"""
|
||||
parsed = SearchDetails.from_kwargs(details)
|
||||
if parsed.geometry_output != GeometryFormat.NONE:
|
||||
if parsed.geometry_output & GeometryFormat.GEOJSON:
|
||||
if GeometryFormat.GEOJSON in parsed.geometry_output:
|
||||
queryparts['polygon_geojson'] = '1'
|
||||
if parsed.geometry_output & GeometryFormat.KML:
|
||||
if GeometryFormat.KML in parsed.geometry_output:
|
||||
queryparts['polygon_kml'] = '1'
|
||||
if parsed.geometry_output & GeometryFormat.SVG:
|
||||
if GeometryFormat.SVG in parsed.geometry_output:
|
||||
queryparts['polygon_svg'] = '1'
|
||||
if parsed.geometry_output & GeometryFormat.TEXT:
|
||||
if GeometryFormat.TEXT in parsed.geometry_output:
|
||||
queryparts['polygon_text'] = '1'
|
||||
if parsed.address_details:
|
||||
queryparts['addressdetails'] = '1'
|
||||
|
||||
@@ -215,7 +215,7 @@ class AdminServe:
|
||||
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', 'starlette'),
|
||||
choices=('php', 'falcon', 'starlette'),
|
||||
help='Webserver framework to run. (default: php)')
|
||||
|
||||
|
||||
@@ -223,6 +223,7 @@ class AdminServe:
|
||||
if args.engine == 'php':
|
||||
run_php_server(args.server, args.project_dir / 'website')
|
||||
else:
|
||||
import uvicorn # pylint: disable=import-outside-toplevel
|
||||
server_info = args.server.split(':', 1)
|
||||
host = server_info[0]
|
||||
if len(server_info) > 1:
|
||||
@@ -232,21 +233,10 @@ class AdminServe:
|
||||
else:
|
||||
port = 8088
|
||||
|
||||
if args.engine == 'sanic':
|
||||
server_module = importlib.import_module('nominatim.server.sanic.server')
|
||||
server_module = importlib.import_module(f'nominatim.server.{args.engine}.server')
|
||||
|
||||
app = server_module.get_application(args.project_dir)
|
||||
app.run(host=host, port=port, debug=True, single_process=True)
|
||||
else:
|
||||
import uvicorn # pylint: disable=import-outside-toplevel
|
||||
|
||||
if args.engine == 'falcon':
|
||||
server_module = importlib.import_module('nominatim.server.falcon.server')
|
||||
elif args.engine == 'starlette':
|
||||
server_module = importlib.import_module('nominatim.server.starlette.server')
|
||||
|
||||
app = server_module.get_application(args.project_dir)
|
||||
uvicorn.run(app, host=host, port=port)
|
||||
app = server_module.get_application(args.project_dir)
|
||||
uvicorn.run(app, host=host, port=port)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# This file is part of Nominatim. (https://nominatim.org)
|
||||
#
|
||||
# Copyright (C) 2023 by the Nominatim developer community.
|
||||
# For a full list of authors see the git log.
|
||||
"""
|
||||
Server implementation using the sanic webserver framework.
|
||||
"""
|
||||
from typing import Any, Optional, Mapping, Callable, cast, Coroutine
|
||||
from pathlib import Path
|
||||
|
||||
from sanic import Request, HTTPResponse, Sanic
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.response import text as TextResponse
|
||||
|
||||
from nominatim.api import NominatimAPIAsync
|
||||
import nominatim.api.v1 as api_impl
|
||||
from nominatim.config import Configuration
|
||||
|
||||
class ParamWrapper(api_impl.ASGIAdaptor):
|
||||
""" Adaptor class for server glue to Sanic framework.
|
||||
"""
|
||||
|
||||
def __init__(self, request: Request) -> None:
|
||||
self.request = request
|
||||
|
||||
|
||||
def get(self, name: str, default: Optional[str] = None) -> Optional[str]:
|
||||
return cast(Optional[str], self.request.args.get(name, default))
|
||||
|
||||
|
||||
def get_header(self, name: str, default: Optional[str] = None) -> Optional[str]:
|
||||
return cast(Optional[str], self.request.headers.get(name, default))
|
||||
|
||||
|
||||
def error(self, msg: str, status: int = 400) -> SanicException:
|
||||
exception = SanicException(msg, status_code=status)
|
||||
|
||||
return exception
|
||||
|
||||
|
||||
def create_response(self, status: int, output: str) -> HTTPResponse:
|
||||
return TextResponse(output, status=status, content_type=self.content_type)
|
||||
|
||||
|
||||
def config(self) -> Configuration:
|
||||
return cast(Configuration, self.request.app.ctx.api.config)
|
||||
|
||||
|
||||
def _wrap_endpoint(func: api_impl.EndpointFunc)\
|
||||
-> Callable[[Request], Coroutine[Any, Any, HTTPResponse]]:
|
||||
async def _callback(request: Request) -> HTTPResponse:
|
||||
return cast(HTTPResponse, await func(request.app.ctx.api, ParamWrapper(request)))
|
||||
|
||||
return _callback
|
||||
|
||||
|
||||
def get_application(project_dir: Path,
|
||||
environ: Optional[Mapping[str, str]] = None) -> Sanic:
|
||||
""" Create a Nominatim sanic ASGI application.
|
||||
"""
|
||||
app = Sanic("NominatimInstance")
|
||||
|
||||
app.ctx.api = NominatimAPIAsync(project_dir, environ)
|
||||
|
||||
if app.ctx.api.config.get_bool('CORS_NOACCESSCONTROL'):
|
||||
from sanic_cors import CORS # pylint: disable=import-outside-toplevel
|
||||
CORS(app)
|
||||
|
||||
legacy_urls = app.ctx.api.config.get_bool('SERVE_LEGACY_URLS')
|
||||
for name, func in api_impl.ROUTES:
|
||||
endpoint = _wrap_endpoint(func)
|
||||
app.add_route(endpoint, f"/{name}", name=f"v1_{name}_simple")
|
||||
if legacy_urls:
|
||||
app.add_route(endpoint, f"/{name}.php", name=f"v1_{name}_legacy")
|
||||
|
||||
return app
|
||||
@@ -350,20 +350,6 @@ class NominatimEnvironment:
|
||||
return _request
|
||||
|
||||
|
||||
def create_api_request_func_sanic(self):
|
||||
import nominatim.server.sanic.server
|
||||
|
||||
async def _request(endpoint, params, project_dir, environ, http_headers):
|
||||
app = nominatim.server.sanic.server.get_application(project_dir, environ)
|
||||
|
||||
_, response = await app.asgi_client.get(f"/{endpoint}", params=params,
|
||||
headers=http_headers)
|
||||
|
||||
return response.text, response.status_code
|
||||
|
||||
return _request
|
||||
|
||||
|
||||
def create_api_request_func_falcon(self):
|
||||
import nominatim.server.falcon.server
|
||||
import falcon.testing
|
||||
|
||||
@@ -118,10 +118,10 @@ async def test_penalty_postcodes_and_housenumbers(conn, term, order):
|
||||
|
||||
assert query.num_token_slots() == 1
|
||||
|
||||
torder = [(tl.tokens[0].penalty, tl.ttype) for tl in query.nodes[0].starting]
|
||||
torder = [(tl.tokens[0].penalty, tl.ttype.name) for tl in query.nodes[0].starting]
|
||||
torder.sort()
|
||||
|
||||
assert [t[1] for t in torder] == [TokenType[o] for o in order]
|
||||
assert [t[1] for t in torder] == order
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_category_words_only_at_beginning(conn):
|
||||
|
||||
@@ -195,11 +195,10 @@ async def test_penalty_postcodes_and_housenumbers(conn, term, order):
|
||||
|
||||
assert query.num_token_slots() == 1
|
||||
|
||||
torder = [(tl.tokens[0].penalty, tl.ttype) for tl in query.nodes[0].starting]
|
||||
print(query.nodes[0].starting)
|
||||
torder = [(tl.tokens[0].penalty, tl.ttype.name) for tl in query.nodes[0].starting]
|
||||
torder.sort()
|
||||
|
||||
assert [t[1] for t in torder] == [TokenType[o] for o in order]
|
||||
assert [t[1] for t in torder] == order
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -68,15 +68,6 @@ def test_cli_serve_php(cli_call, mock_func_factory):
|
||||
assert func.called == 1
|
||||
|
||||
|
||||
def test_cli_serve_sanic(cli_call, mock_func_factory):
|
||||
mod = pytest.importorskip("sanic")
|
||||
func = mock_func_factory(mod.Sanic, "run")
|
||||
|
||||
cli_call('serve', '--engine', 'sanic') == 0
|
||||
|
||||
assert func.called == 1
|
||||
|
||||
|
||||
def test_cli_serve_starlette_custom_server(cli_call, mock_func_factory):
|
||||
pytest.importorskip("starlette")
|
||||
mod = pytest.importorskip("uvicorn")
|
||||
|
||||
Reference in New Issue
Block a user