Add sanic as an entry point command (#1866)
* Add sanic as an entry point command * Fix linting issue in imports Co-authored-by: 7 <yunxu1992@gmail.com>
This commit is contained in:
parent
fa4f85eb32
commit
496e87e4ba
|
@ -1,3 +1,6 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
@ -6,7 +9,7 @@ from sanic.app import Sanic
|
||||||
from sanic.log import logger
|
from sanic.log import logger
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def main():
|
||||||
parser = ArgumentParser(prog="sanic")
|
parser = ArgumentParser(prog="sanic")
|
||||||
parser.add_argument("--host", dest="host", type=str, default="127.0.0.1")
|
parser.add_argument("--host", dest="host", type=str, default="127.0.0.1")
|
||||||
parser.add_argument("--port", dest="port", type=int, default=8000)
|
parser.add_argument("--port", dest="port", type=int, default=8000)
|
||||||
|
@ -22,6 +25,10 @@ if __name__ == "__main__":
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
module_path = os.path.abspath(os.getcwd())
|
||||||
|
if module_path not in sys.path:
|
||||||
|
sys.path.append(module_path)
|
||||||
|
|
||||||
module_parts = args.module.split(".")
|
module_parts = args.module.split(".")
|
||||||
module_name = ".".join(module_parts[:-1])
|
module_name = ".".join(module_parts[:-1])
|
||||||
app_name = module_parts[-1]
|
app_name = module_parts[-1]
|
||||||
|
@ -58,3 +65,7 @@ if __name__ == "__main__":
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.exception("Failed to run app")
|
logger.exception("Failed to run app")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
10
setup.py
10
setup.py
|
@ -5,7 +5,6 @@ import codecs
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distutils.util import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
@ -39,9 +38,7 @@ def open_local(paths, mode="r", encoding="utf8"):
|
||||||
|
|
||||||
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
|
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
|
||||||
try:
|
try:
|
||||||
version = re.findall(
|
version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0]
|
||||||
r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M
|
|
||||||
)[0]
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise RuntimeError("Unable to determine version.")
|
raise RuntimeError("Unable to determine version.")
|
||||||
|
|
||||||
|
@ -70,11 +67,10 @@ setup_kwargs = {
|
||||||
"Programming Language :: Python :: 3.7",
|
"Programming Language :: Python :: 3.7",
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
],
|
],
|
||||||
|
"entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
env_dependency = (
|
env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"'
|
||||||
'; sys_platform != "win32" ' 'and implementation_name == "cpython"'
|
|
||||||
)
|
|
||||||
ujson = "ujson>=1.35" + env_dependency
|
ujson = "ujson>=1.35" + env_dependency
|
||||||
uvloop = "uvloop>=0.5.3" + env_dependency
|
uvloop = "uvloop>=0.5.3" + env_dependency
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ def test_asyncio_server_no_start_serving(app):
|
||||||
srv = loop.run_until_complete(asyncio_srv_coro)
|
srv = loop.run_until_complete(asyncio_srv_coro)
|
||||||
assert srv.is_serving() is False
|
assert srv.is_serving() is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
sys.version_info < (3, 7), reason="requires python3.7 or higher"
|
sys.version_info < (3, 7), reason="requires python3.7 or higher"
|
||||||
)
|
)
|
||||||
|
@ -75,6 +76,7 @@ def test_asyncio_server_start_serving(app):
|
||||||
loop.run_until_complete(wait_close)
|
loop.run_until_complete(wait_close)
|
||||||
# Looks like we can't easily test `serve_forever()`
|
# Looks like we can't easily test `serve_forever()`
|
||||||
|
|
||||||
|
|
||||||
def test_app_loop_not_running(app):
|
def test_app_loop_not_running(app):
|
||||||
with pytest.raises(SanicException) as excinfo:
|
with pytest.raises(SanicException) as excinfo:
|
||||||
app.loop
|
app.loop
|
||||||
|
@ -125,7 +127,10 @@ def test_app_handle_request_handler_is_none(app, monkeypatch):
|
||||||
|
|
||||||
request, response = app.test_client.get("/test")
|
request, response = app.test_client.get("/test")
|
||||||
|
|
||||||
assert "'None' was returned while requesting a handler from the router" in response.text
|
assert (
|
||||||
|
"'None' was returned while requesting a handler from the router"
|
||||||
|
in response.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("websocket_enabled", [True, False])
|
@pytest.mark.parametrize("websocket_enabled", [True, False])
|
||||||
|
|
|
@ -84,8 +84,8 @@ def test_listeners_triggered(app):
|
||||||
|
|
||||||
all_tasks = (
|
all_tasks = (
|
||||||
asyncio.Task.all_tasks()
|
asyncio.Task.all_tasks()
|
||||||
if sys.version_info < (3, 7) else
|
if sys.version_info < (3, 7)
|
||||||
asyncio.all_tasks(asyncio.get_event_loop())
|
else asyncio.all_tasks(asyncio.get_event_loop())
|
||||||
)
|
)
|
||||||
for task in all_tasks:
|
for task in all_tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
@ -134,8 +134,8 @@ def test_listeners_triggered_async(app):
|
||||||
|
|
||||||
all_tasks = (
|
all_tasks = (
|
||||||
asyncio.Task.all_tasks()
|
asyncio.Task.all_tasks()
|
||||||
if sys.version_info < (3, 7) else
|
if sys.version_info < (3, 7)
|
||||||
asyncio.all_tasks(asyncio.get_event_loop())
|
else asyncio.all_tasks(asyncio.get_event_loop())
|
||||||
)
|
)
|
||||||
for task in all_tasks:
|
for task in all_tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
|
@ -270,24 +270,31 @@ def test_bp_middleware(app):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == "FAIL"
|
assert response.text == "FAIL"
|
||||||
|
|
||||||
|
|
||||||
def test_bp_middleware_order(app):
|
def test_bp_middleware_order(app):
|
||||||
blueprint = Blueprint("test_bp_middleware_order")
|
blueprint = Blueprint("test_bp_middleware_order")
|
||||||
order = list()
|
order = list()
|
||||||
|
|
||||||
@blueprint.middleware("request")
|
@blueprint.middleware("request")
|
||||||
def mw_1(request):
|
def mw_1(request):
|
||||||
order.append(1)
|
order.append(1)
|
||||||
|
|
||||||
@blueprint.middleware("request")
|
@blueprint.middleware("request")
|
||||||
def mw_2(request):
|
def mw_2(request):
|
||||||
order.append(2)
|
order.append(2)
|
||||||
|
|
||||||
@blueprint.middleware("request")
|
@blueprint.middleware("request")
|
||||||
def mw_3(request):
|
def mw_3(request):
|
||||||
order.append(3)
|
order.append(3)
|
||||||
|
|
||||||
@blueprint.middleware("response")
|
@blueprint.middleware("response")
|
||||||
def mw_4(request, response):
|
def mw_4(request, response):
|
||||||
order.append(6)
|
order.append(6)
|
||||||
|
|
||||||
@blueprint.middleware("response")
|
@blueprint.middleware("response")
|
||||||
def mw_5(request, response):
|
def mw_5(request, response):
|
||||||
order.append(5)
|
order.append(5)
|
||||||
|
|
||||||
@blueprint.middleware("response")
|
@blueprint.middleware("response")
|
||||||
def mw_6(request, response):
|
def mw_6(request, response):
|
||||||
order.append(4)
|
order.append(4)
|
||||||
|
@ -303,6 +310,7 @@ def test_bp_middleware_order(app):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert order == [1, 2, 3, 4, 5, 6]
|
assert order == [1, 2, 3, 4, 5, 6]
|
||||||
|
|
||||||
|
|
||||||
def test_bp_exception_handler(app):
|
def test_bp_exception_handler(app):
|
||||||
blueprint = Blueprint("test_middleware")
|
blueprint = Blueprint("test_middleware")
|
||||||
|
|
||||||
|
@ -585,9 +593,7 @@ def test_bp_group_with_default_url_prefix(app):
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
resource_id = str(uuid4())
|
resource_id = str(uuid4())
|
||||||
request, response = app.test_client.get(
|
request, response = app.test_client.get(f"/api/v1/resources/{resource_id}")
|
||||||
f"/api/v1/resources/{resource_id}"
|
|
||||||
)
|
|
||||||
assert response.json == {"resource_id": resource_id}
|
assert response.json == {"resource_id": resource_id}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from sanic import Sanic, server
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.testing import HOST, SanicTestClient
|
from sanic.testing import HOST, SanicTestClient
|
||||||
|
|
||||||
|
|
||||||
CONFIG_FOR_TESTS = {"KEEP_ALIVE_TIMEOUT": 2, "KEEP_ALIVE": True}
|
CONFIG_FOR_TESTS = {"KEEP_ALIVE_TIMEOUT": 2, "KEEP_ALIVE": True}
|
||||||
|
|
||||||
old_conn = None
|
old_conn = None
|
||||||
|
@ -46,7 +47,7 @@ class ReusableSanicConnectionPool(
|
||||||
cert=self.cert,
|
cert=self.cert,
|
||||||
verify=self.verify,
|
verify=self.verify,
|
||||||
trust_env=self.trust_env,
|
trust_env=self.trust_env,
|
||||||
http2=self.http2
|
http2=self.http2,
|
||||||
)
|
)
|
||||||
connection = httpx.dispatch.connection.HTTPConnection(
|
connection = httpx.dispatch.connection.HTTPConnection(
|
||||||
origin,
|
origin,
|
||||||
|
@ -166,9 +167,7 @@ class ReuseableSanicTestClient(SanicTestClient):
|
||||||
try:
|
try:
|
||||||
return results[-1]
|
return results[-1]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError(
|
raise ValueError(f"Request object expected, got ({results})")
|
||||||
f"Request object expected, got ({results})"
|
|
||||||
)
|
|
||||||
|
|
||||||
def kill_server(self):
|
def kill_server(self):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -614,6 +614,7 @@ def test_request_stream(app):
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == data
|
assert response.text == data
|
||||||
|
|
||||||
|
|
||||||
def test_streaming_new_api(app):
|
def test_streaming_new_api(app):
|
||||||
@app.post("/non-stream")
|
@app.post("/non-stream")
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
|
|
|
@ -17,7 +17,7 @@ class DelayableHTTPConnection(httpx.dispatch.connection.HTTPConnection):
|
||||||
async def send(self, request, timeout=None):
|
async def send(self, request, timeout=None):
|
||||||
|
|
||||||
if self.connection is None:
|
if self.connection is None:
|
||||||
self.connection = (await self.connect(timeout=timeout))
|
self.connection = await self.connect(timeout=timeout)
|
||||||
|
|
||||||
if self._request_delay:
|
if self._request_delay:
|
||||||
await asyncio.sleep(self._request_delay)
|
await asyncio.sleep(self._request_delay)
|
||||||
|
|
|
@ -242,7 +242,7 @@ def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
|
||||||
|
|
||||||
|
|
||||||
def test_non_chunked_streaming_returns_correct_content(
|
def test_non_chunked_streaming_returns_correct_content(
|
||||||
non_chunked_streaming_app
|
non_chunked_streaming_app,
|
||||||
):
|
):
|
||||||
request, response = non_chunked_streaming_app.test_client.get("/")
|
request, response = non_chunked_streaming_app.test_client.get("/")
|
||||||
assert response.text == "foo,bar"
|
assert response.text == "foo,bar"
|
||||||
|
@ -257,7 +257,7 @@ def test_stream_response_status_returns_correct_headers(status):
|
||||||
|
|
||||||
@pytest.mark.parametrize("keep_alive_timeout", [10, 20, 30])
|
@pytest.mark.parametrize("keep_alive_timeout", [10, 20, 30])
|
||||||
def test_stream_response_keep_alive_returns_correct_headers(
|
def test_stream_response_keep_alive_returns_correct_headers(
|
||||||
keep_alive_timeout
|
keep_alive_timeout,
|
||||||
):
|
):
|
||||||
response = StreamingHTTPResponse(sample_streaming_fn)
|
response = StreamingHTTPResponse(sample_streaming_fn)
|
||||||
headers = response.get_headers(
|
headers = response.get_headers(
|
||||||
|
@ -286,7 +286,7 @@ def test_stream_response_does_not_include_chunked_header_if_disabled():
|
||||||
|
|
||||||
|
|
||||||
def test_stream_response_writes_correct_content_to_transport_when_chunked(
|
def test_stream_response_writes_correct_content_to_transport_when_chunked(
|
||||||
streaming_app
|
streaming_app,
|
||||||
):
|
):
|
||||||
response = StreamingHTTPResponse(sample_streaming_fn)
|
response = StreamingHTTPResponse(sample_streaming_fn)
|
||||||
response.protocol = MagicMock(HttpProtocol)
|
response.protocol = MagicMock(HttpProtocol)
|
||||||
|
@ -434,9 +434,10 @@ def test_file_response_custom_filename(
|
||||||
request, response = app.test_client.get(f"/files/{source}")
|
request, response = app.test_client.get(f"/files/{source}")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(static_file_directory, source)
|
assert response.body == get_file_content(static_file_directory, source)
|
||||||
assert response.headers[
|
assert (
|
||||||
"Content-Disposition"
|
response.headers["Content-Disposition"]
|
||||||
] == f'attachment; filename="{dest}"'
|
== f'attachment; filename="{dest}"'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
|
@ -510,9 +511,10 @@ def test_file_stream_response_custom_filename(
|
||||||
request, response = app.test_client.get(f"/files/{source}")
|
request, response = app.test_client.get(f"/files/{source}")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(static_file_directory, source)
|
assert response.body == get_file_content(static_file_directory, source)
|
||||||
assert response.headers[
|
assert (
|
||||||
"Content-Disposition"
|
response.headers["Content-Disposition"]
|
||||||
] == f'attachment; filename="{dest}"'
|
== f'attachment; filename="{dest}"'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
|
@ -581,7 +583,10 @@ def test_file_stream_response_range(
|
||||||
request, response = app.test_client.get(f"/files/{file_name}")
|
request, response = app.test_client.get(f"/files/{file_name}")
|
||||||
assert response.status == 206
|
assert response.status == 206
|
||||||
assert "Content-Range" in response.headers
|
assert "Content-Range" in response.headers
|
||||||
assert response.headers["Content-Range"] == f"bytes {range.start}-{range.end}/{range.total}"
|
assert (
|
||||||
|
response.headers["Content-Range"]
|
||||||
|
== f"bytes {range.start}-{range.end}/{range.total}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_raw_response(app):
|
def test_raw_response(app):
|
||||||
|
|
|
@ -580,7 +580,7 @@ async def test_websocket_route_asgi(app):
|
||||||
ev.clear()
|
ev.clear()
|
||||||
request, response = await app.asgi_client.websocket("/test/1")
|
request, response = await app.asgi_client.websocket("/test/1")
|
||||||
second_set = ev.is_set()
|
second_set = ev.is_set()
|
||||||
assert(first_set and second_set)
|
assert first_set and second_set
|
||||||
|
|
||||||
|
|
||||||
def test_method_not_allowed(app):
|
def test_method_not_allowed(app):
|
||||||
|
|
|
@ -33,9 +33,7 @@ def after(app, loop):
|
||||||
calledq.put(mock.called)
|
calledq.put(mock.called)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(os.name == "nt", reason="May hang CI on py38/windows")
|
||||||
os.name == "nt", reason="May hang CI on py38/windows"
|
|
||||||
)
|
|
||||||
def test_register_system_signals(app):
|
def test_register_system_signals(app):
|
||||||
"""Test if sanic register system signals"""
|
"""Test if sanic register system signals"""
|
||||||
|
|
||||||
|
@ -51,9 +49,7 @@ def test_register_system_signals(app):
|
||||||
assert calledq.get() is True
|
assert calledq.get() is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(os.name == "nt", reason="May hang CI on py38/windows")
|
||||||
os.name == "nt", reason="May hang CI on py38/windows"
|
|
||||||
)
|
|
||||||
def test_dont_register_system_signals(app):
|
def test_dont_register_system_signals(app):
|
||||||
"""Test if sanic don't register system signals"""
|
"""Test if sanic don't register system signals"""
|
||||||
|
|
||||||
|
@ -69,9 +65,7 @@ def test_dont_register_system_signals(app):
|
||||||
assert calledq.get() is False
|
assert calledq.get() is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(os.name == "nt", reason="windows cannot SIGINT processes")
|
||||||
os.name == "nt", reason="windows cannot SIGINT processes"
|
|
||||||
)
|
|
||||||
def test_windows_workaround():
|
def test_windows_workaround():
|
||||||
"""Test Windows workaround (on any other OS)"""
|
"""Test Windows workaround (on any other OS)"""
|
||||||
# At least some code coverage, even though this test doesn't work on
|
# At least some code coverage, even though this test doesn't work on
|
||||||
|
|
|
@ -97,9 +97,7 @@ def test_static_file_content_type(app, static_file_directory, file_name):
|
||||||
def test_static_directory(app, file_name, base_uri, static_file_directory):
|
def test_static_directory(app, file_name, base_uri, static_file_directory):
|
||||||
app.static(base_uri, static_file_directory)
|
app.static(base_uri, static_file_directory)
|
||||||
|
|
||||||
request, response = app.test_client.get(
|
request, response = app.test_client.get(uri=f"{base_uri}/{file_name}")
|
||||||
uri=f"{base_uri}/{file_name}"
|
|
||||||
)
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(static_file_directory, file_name)
|
assert response.body == get_file_content(static_file_directory, file_name)
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,7 @@ def test_routes_with_host(app):
|
||||||
assert app.url_for("hostindex") == "/"
|
assert app.url_for("hostindex") == "/"
|
||||||
assert app.url_for("hostpath") == "/path"
|
assert app.url_for("hostpath") == "/path"
|
||||||
assert app.url_for("hostindex", _external=True) == "http://example.com/"
|
assert app.url_for("hostindex", _external=True) == "http://example.com/"
|
||||||
assert app.url_for("hostpath", _external=True) == "http://path.example.com/path"
|
assert (
|
||||||
|
app.url_for("hostpath", _external=True)
|
||||||
|
== "http://path.example.com/path"
|
||||||
|
)
|
||||||
|
|
|
@ -151,8 +151,7 @@ def test_with_custom_class_methods(app):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
self._iternal_method()
|
self._iternal_method()
|
||||||
return text(
|
return text(
|
||||||
f"I am get method and global var "
|
f"I am get method and global var " f"is {self.global_var}"
|
||||||
f"is {self.global_var}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_route(DummyView.as_view(), "/")
|
app.add_route(DummyView.as_view(), "/")
|
||||||
|
|
|
@ -128,9 +128,11 @@ def test_handle_quit(worker):
|
||||||
assert not worker.alive
|
assert not worker.alive
|
||||||
assert worker.exit_code == 0
|
assert worker.exit_code == 0
|
||||||
|
|
||||||
|
|
||||||
async def _a_noop(*a, **kw):
|
async def _a_noop(*a, **kw):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_run_max_requests_exceeded(worker):
|
def test_run_max_requests_exceeded(worker):
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
worker.ppid = 1
|
worker.ppid = 1
|
||||||
|
|
Loading…
Reference in New Issue
Block a user