Skip to content

Commit

Permalink
Merge branch 'main' of github.com:sanic-org/sanic into env-type-regis…
Browse files Browse the repository at this point in the history
…tration
  • Loading branch information
ahopkins committed Dec 16, 2021
2 parents 8769d46 + b5a00ac commit eb843c6
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 79 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ on:
- main
tags:
- "!*" # Do not execute on tags
paths:
- sanic/*
- tests/*
pull_request:
paths:
- "!*.MD"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
test:
Expand Down
15 changes: 13 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Sanic | Build fast. Run fast.
:stub-columns: 1

* - Build
- | |Py39Test| |Py38Test| |Py37Test|
- | |Py310Test| |Py39Test| |Py38Test| |Py37Test|
* - Docs
- | |UserGuide| |Documentation|
* - Package
Expand All @@ -27,6 +27,8 @@ Sanic | Build fast. Run fast.
:target: https://community.sanicframework.org/
.. |Discord| image:: https://img.shields.io/discord/812221182594121728?logo=discord
:target: https://discord.gg/FARQzAEMAA
.. |Py310Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python310.yml/badge.svg?branch=main
:target: https://github.com/sanic-org/sanic/actions/workflows/pr-python310.yml
.. |Py39Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml/badge.svg?branch=main
:target: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml
.. |Py38Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python38.yml/badge.svg?branch=main
Expand Down Expand Up @@ -75,7 +77,11 @@ The goal of the project is to provide a simple way to get up and running a highl
Sponsor
-------

Check out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.
Check out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.

Thanks to `Linode <https://www.linode.com>`_ for their contribution towards the development and community of Sanic.

|Linode|

Installation
------------
Expand Down Expand Up @@ -160,3 +166,8 @@ Contribution
------------

We are always happy to have new contributions. We have `marked issues good for anyone looking to get started <https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner>`_, and welcome `questions on the forums <https://community.sanicframework.org/>`_. Please take a look at our `Contribution guidelines <https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst>`_.

.. |Linode| image:: https://www.linode.com/wp-content/uploads/2021/01/Linode-Logo-Black.svg
:alt: Linode
:target: https://www.linode.com
:width: 200px
31 changes: 15 additions & 16 deletions examples/run_async.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import asyncio

from signal import SIGINT, signal

import uvloop

from sanic import Sanic, response
Expand All @@ -15,17 +13,18 @@ async def test(request):
return response.json({"answer": "42"})


asyncio.set_event_loop(uvloop.new_event_loop())
server = app.create_server(
host="0.0.0.0", port=8000, return_asyncio_server=True
)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server)
server = loop.run_until_complete(task)
loop.run_until_complete(server.startup())
signal(SIGINT, lambda s, f: loop.stop())

try:
loop.run_forever()
finally:
loop.stop()
async def main():
server = await app.create_server(
port=8000, host="0.0.0.0", return_asyncio_server=True
)

if server is None:
return

await server.startup()
await server.serve_forever()


if __name__ == "__main__":
asyncio.set_event_loop(uvloop.new_event_loop())
asyncio.run(main())
1 change: 1 addition & 0 deletions sanic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,7 @@ async def _startup(self):
self.error_handler, fallback=self.config.FALLBACK_ERROR_FORMAT
)
TouchUp.run(self)
self.state.is_started = True

async def _server_event(
self,
Expand Down
1 change: 1 addition & 0 deletions sanic/application/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class ApplicationState:
reload_dirs: Set[Path] = field(default_factory=set)
server: Server = field(default=Server.SANIC)
is_running: bool = field(default=False)
is_started: bool = field(default=False)
is_stopping: bool = field(default=False)
verbosity: int = field(default=0)
workers: int = field(default=0)
Expand Down
49 changes: 33 additions & 16 deletions sanic/server/async_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@

import asyncio

from typing import TYPE_CHECKING
from warnings import warn

from sanic.exceptions import SanicException


if TYPE_CHECKING:
from sanic import Sanic


class AsyncioServer:
"""
Wraps an asyncio server with functionality that might be useful to
a user who needs to manage the server lifecycle manually.
"""

__slots__ = ("app", "connections", "loop", "serve_coro", "server", "init")
__slots__ = ("app", "connections", "loop", "serve_coro", "server")

def __init__(
self,
app,
app: Sanic,
loop,
serve_coro,
connections,
Expand All @@ -27,13 +34,20 @@ def __init__(
self.loop = loop
self.serve_coro = serve_coro
self.server = None
self.init = False

@property
def init(self):
warn(
"AsyncioServer.init has been deprecated and will be removed "
"in v22.6. Use Sanic.state.is_started instead.",
DeprecationWarning,
)
return self.app.state.is_started

def startup(self):
"""
Trigger "before_server_start" events
"""
self.init = True
return self.app._startup()

def before_start(self):
Expand Down Expand Up @@ -77,30 +91,33 @@ def close(self):
return task

def start_serving(self):
if self.server:
try:
return self.server.start_serving()
except AttributeError:
raise NotImplementedError(
"server.start_serving not available in this version "
"of asyncio or uvloop."
)
return self._serve(self.server.start_serving)

def serve_forever(self):
return self._serve(self.server.serve_forever)

def _serve(self, serve_func):
if self.server:
if not self.app.state.is_started:
raise SanicException(
"Cannot run Sanic server without first running "
"await server.startup()"
)

try:
return self.server.serve_forever()
return serve_func()
except AttributeError:
name = serve_func.__name__
raise NotImplementedError(
"server.serve_forever not available in this version "
f"server.{name} not available in this version "
"of asyncio or uvloop."
)

def _server_event(self, concern: str, action: str):
if not self.init:
if not self.app.state.is_started:
raise SanicException(
"Cannot dispatch server event without "
"first running server.startup()"
"first running await server.startup()"
)
return self.app._server_event(concern, action, loop=self.loop)

Expand Down
86 changes: 55 additions & 31 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
import logging
import re

from email import message
from inspect import isawaitable
from os import environ
from unittest.mock import Mock, patch

import py
import pytest

from sanic import Sanic
Expand Down Expand Up @@ -41,41 +39,39 @@ async def handler(request):


def test_create_asyncio_server(app):
if not uvloop_installed():
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(return_asyncio_server=True)
assert isawaitable(asyncio_srv_coro)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is True
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(return_asyncio_server=True)
assert isawaitable(asyncio_srv_coro)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is True


def test_asyncio_server_no_start_serving(app):
if not uvloop_installed():
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
port=43123,
return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False),
)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is False
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
port=43123,
return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False),
)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is False


def test_asyncio_server_start_serving(app):
if not uvloop_installed():
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
port=43124,
return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False),
)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is False
loop.run_until_complete(srv.start_serving())
assert srv.is_serving() is True
wait_close = srv.close()
loop.run_until_complete(wait_close)
# Looks like we can't easily test `serve_forever()`
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
port=43124,
return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False),
)
srv = loop.run_until_complete(asyncio_srv_coro)
assert srv.is_serving() is False
loop.run_until_complete(srv.startup())
loop.run_until_complete(srv.start_serving())
assert srv.is_serving() is True
wait_close = srv.close()
loop.run_until_complete(wait_close)
# Looks like we can't easily test `serve_forever()`


def test_create_server_main(app, caplog):
Expand All @@ -92,6 +88,21 @@ def test_create_server_main(app, caplog):
) in caplog.record_tuples


def test_create_server_no_startup(app):
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(
port=43124,
return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False),
)
srv = loop.run_until_complete(asyncio_srv_coro)
message = (
"Cannot run Sanic server without first running await server.startup()"
)
with pytest.raises(SanicException, match=message):
loop.run_until_complete(srv.start_serving())


def test_create_server_main_convenience(app, caplog):
app.main_process_start(lambda *_: ...)
loop = asyncio.get_event_loop()
Expand All @@ -106,6 +117,19 @@ def test_create_server_main_convenience(app, caplog):
) in caplog.record_tuples


def test_create_server_init(app, caplog):
loop = asyncio.get_event_loop()
asyncio_srv_coro = app.create_server(return_asyncio_server=True)
server = loop.run_until_complete(asyncio_srv_coro)

message = (
"AsyncioServer.init has been deprecated and will be removed in v22.6. "
"Use Sanic.state.is_started instead."
)
with pytest.warns(DeprecationWarning, match=message):
server.init


def test_app_loop_not_running(app):
with pytest.raises(SanicException) as excinfo:
app.loop
Expand Down

0 comments on commit eb843c6

Please sign in to comment.