Merge branch 'main' of github.com:sanic-org/sanic into feat/optional-uvloop-use

This commit is contained in:
prryplatypus 2021-12-06 19:34:26 +01:00
commit 796892de52
No known key found for this signature in database
GPG Key ID: 6687E128FB70819B
50 changed files with 356 additions and 258 deletions

View File

@ -5,11 +5,13 @@ on:
branches: [ main ] branches: [ main ]
pull_request: pull_request:
branches: [ main ] branches: [ main ]
types: [opened, synchronize, reopened, ready_for_review]
schedule: schedule:
- cron: '25 16 * * 0' - cron: '25 16 * * 0'
jobs: jobs:
analyze: analyze:
if: github.event.pull_request.draft == false
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -1,19 +1,20 @@
name: Coverage check name: Coverage check
# on: on:
# push: push:
# branches: branches:
# - main - main
# tags: tags:
# - "!*" # Do not execute on tags - "!*" # Do not execute on tags
# paths: paths:
# - sanic/* - sanic/*
# - tests/* - tests/*
# pull_request: pull_request:
# paths: paths:
# - "!*.MD" - "!*.MD"
on: [push, pull_request] types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:
test: test:
if: github.event.pull_request.draft == false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:

View File

@ -3,9 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:
bandit: bandit:
if: github.event.pull_request.draft == false
name: type-check-${{ matrix.config.python-version }} name: type-check-${{ matrix.config.python-version }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,9 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:
docsLinter: docsLinter:
if: github.event.pull_request.draft == false
name: Lint Documentation name: Lint Documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:

View File

@ -3,9 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:
linter: linter:
if: github.event.pull_request.draft == false
name: lint name: lint
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,15 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
push: types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
paths:
- sanic/*
- tests/*
jobs: jobs:
testPy39: testPy310:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,15 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
push: types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
paths:
- sanic/*
- tests/*
jobs: jobs:
testPy37: testPy37:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,15 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
push: types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
paths:
- sanic/*
- tests/*
jobs: jobs:
testPy38: testPy38:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,15 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
push: types: [opened, synchronize, reopened, ready_for_review]
branches:
- main
paths:
- sanic/*
- tests/*
jobs: jobs:
testPy39: testPy39:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }} name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,9 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:
typeChecking: typeChecking:
if: github.event.pull_request.draft == false
name: type-check-${{ matrix.config.python-version }} name: type-check-${{ matrix.config.python-version }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:

View File

@ -3,9 +3,11 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
types: [opened, synchronize, reopened, ready_for_review]
jobs: jobs:
testsOnWindows: testsOnWindows:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }} name: ut-${{ matrix.config.tox-env }}
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:

View File

@ -657,7 +657,7 @@ Improved Documentation
Version 20.6.0 Version 20.6.0
--------------- ---------------
*Released, but unintentionally ommitting PR #1880, so was replaced by 20.6.1* *Released, but unintentionally omitting PR #1880, so was replaced by 20.6.1*
Version 20.3.0 Version 20.3.0
@ -1090,7 +1090,7 @@ Version 18.12
* Fix Range header handling for static files (#1402) * Fix Range header handling for static files (#1402)
* Fix the logger and make it work (#1397) * Fix the logger and make it work (#1397)
* Fix type pikcle->pickle in multiprocessing test * Fix type pikcle->pickle in multiprocessing test
* Fix pickling blueprints Change the string passed in the "name" section of the namedtuples in Blueprint to match the name of the Blueprint module attribute name. This allows blueprints to be pickled and unpickled, without errors, which is a requirment of running Sanic in multiprocessing mode in Windows. Added a test for pickling and unpickling blueprints Added a test for pickling and unpickling sanic itself Added a test for enabling multiprocessing on an app with a blueprint (only useful to catch this bug if the tests are run on Windows). * Fix pickling blueprints Change the string passed in the "name" section of the namedtuples in Blueprint to match the name of the Blueprint module attribute name. This allows blueprints to be pickled and unpickled, without errors, which is a requirement of running Sanic in multiprocessing mode in Windows. Added a test for pickling and unpickling blueprints Added a test for pickling and unpickling sanic itself Added a test for enabling multiprocessing on an app with a blueprint (only useful to catch this bug if the tests are run on Windows).
* Fix document for logging * Fix document for logging
Version 0.8 Version 0.8
@ -1129,7 +1129,7 @@ Version 0.8
* Content-length header on 204/304 responses (Arnulfo Solís) * Content-length header on 204/304 responses (Arnulfo Solís)
* Extend WebSocketProtocol arguments and add docs (Bob Olde Hampsink, yunstanford) * Extend WebSocketProtocol arguments and add docs (Bob Olde Hampsink, yunstanford)
* Update development status from pre-alpha to beta (Maksim Anisenkov) * Update development status from pre-alpha to beta (Maksim Anisenkov)
* KeepAlive Timout log level changed to debug (Arnulfo Solís) * KeepAlive Timeout log level changed to debug (Arnulfo Solís)
* Pin pytest to 3.3.2 because of pytest-dev/pytest#3170 (Maksim Aniskenov) * Pin pytest to 3.3.2 because of pytest-dev/pytest#3170 (Maksim Aniskenov)
* Install Python 3.5 and 3.6 on docker container for tests (Shahin Azad) * Install Python 3.5 and 3.6 on docker container for tests (Shahin Azad)
* Add support for blueprint groups and nesting (Elias Tarhini) * Add support for blueprint groups and nesting (Elias Tarhini)

View File

@ -11,7 +11,7 @@ Sanic | Build fast. Run fast.
:stub-columns: 1 :stub-columns: 1
* - Build * - Build
- | |Py39Test| |Py38Test| |Py37Test| |Codecov| - | |Py39Test| |Py38Test| |Py37Test|
* - Docs * - Docs
- | |UserGuide| |Documentation| - | |UserGuide| |Documentation|
* - Package * - Package
@ -27,8 +27,6 @@ Sanic | Build fast. Run fast.
:target: https://community.sanicframework.org/ :target: https://community.sanicframework.org/
.. |Discord| image:: https://img.shields.io/discord/812221182594121728?logo=discord .. |Discord| image:: https://img.shields.io/discord/812221182594121728?logo=discord
:target: https://discord.gg/FARQzAEMAA :target: https://discord.gg/FARQzAEMAA
.. |Codecov| image:: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg
:target: https://codecov.io/gh/sanic-org/sanic
.. |Py39Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml/badge.svg?branch=main .. |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 :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 .. |Py38Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python38.yml/badge.svg?branch=main

View File

@ -4,12 +4,14 @@ import asyncio
from sanic import Sanic from sanic import Sanic
app = Sanic()
app = Sanic(__name__)
async def notify_server_started_after_five_seconds(): async def notify_server_started_after_five_seconds():
await asyncio.sleep(5) await asyncio.sleep(5)
print('Server successfully started!') print("Server successfully started!")
app.add_task(notify_server_started_after_five_seconds()) app.add_task(notify_server_started_after_five_seconds())

View File

@ -1,30 +1,29 @@
from sanic import Sanic
from sanic.response import text
from random import randint from random import randint
app = Sanic() from sanic import Sanic
from sanic.response import text
@app.middleware('request') app = Sanic(__name__)
@app.middleware("request")
def append_request(request): def append_request(request):
# Add new key with random value request.ctx.num = randint(0, 100)
request['num'] = randint(0, 100)
@app.get('/pop') @app.get("/pop")
def pop_handler(request): def pop_handler(request):
# Pop key from request object return text(request.ctx.num)
num = request.pop('num')
return text(num)
@app.get('/key_exist') @app.get("/key_exist")
def key_exist_handler(request): def key_exist_handler(request):
# Check the key is exist or not # Check the key is exist or not
if 'num' in request: if hasattr(request.ctx, "num"):
return text('num exist in request') return text("num exist in request")
return text('num does not exist in reqeust') return text("num does not exist in request")
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,10 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from sanic import Sanic
from functools import wraps from functools import wraps
from sanic import Sanic
from sanic.response import json from sanic.response import json
app = Sanic()
app = Sanic(__name__)
def check_request_for_authorization_status(request): def check_request_for_authorization_status(request):
@ -27,14 +29,16 @@ def authorized(f):
return response return response
else: else:
# the user is not authorized. # the user is not authorized.
return json({'status': 'not_authorized'}, 403) return json({"status": "not_authorized"}, 403)
return decorated_function return decorated_function
@app.route("/") @app.route("/")
@authorized @authorized
async def test(request): async def test(request):
return json({'status': 'authorized'}) return json({"status": "authorized"})
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -1,43 +1,53 @@
from sanic import Sanic, Blueprint from sanic import Blueprint, Sanic
from sanic.response import text from sanic.response import text
'''
Demonstrates that blueprint request middleware are executed in the order they
"""
Demonstrates that blueprint request middleware are executed in the order they
are added. And blueprint response middleware are executed in _reverse_ order. are added. And blueprint response middleware are executed in _reverse_ order.
On a valid request, it should print "1 2 3 6 5 4" to terminal On a valid request, it should print "1 2 3 6 5 4" to terminal
''' """
app = Sanic(__name__) app = Sanic(__name__)
bp = Blueprint("bp_"+__name__) bp = Blueprint("bp_" + __name__)
@bp.middleware('request')
@bp.on_request
def request_middleware_1(request): def request_middleware_1(request):
print('1') print("1")
@bp.middleware('request')
@bp.on_request
def request_middleware_2(request): def request_middleware_2(request):
print('2') print("2")
@bp.middleware('request')
@bp.on_request
def request_middleware_3(request): def request_middleware_3(request):
print('3') print("3")
@bp.middleware('response')
@bp.on_response
def resp_middleware_4(request, response): def resp_middleware_4(request, response):
print('4') print("4")
@bp.middleware('response')
@bp.on_response
def resp_middleware_5(request, response): def resp_middleware_5(request, response):
print('5') print("5")
@bp.middleware('response')
@bp.on_response
def resp_middleware_6(request, response): def resp_middleware_6(request, response):
print('6') print("6")
@bp.route('/')
@bp.route("/")
def pop_handler(request): def pop_handler(request):
return text('hello world') return text("hello world")
app.blueprint(bp, url_prefix='/bp')
app.blueprint(bp, url_prefix="/bp")
app.run(host="0.0.0.0", port=8000, debug=True, auto_reload=False) app.run(host="0.0.0.0", port=8000, debug=True, auto_reload=False)

View File

@ -1,6 +1,7 @@
from sanic import Blueprint, Sanic from sanic import Blueprint, Sanic
from sanic.response import file, json from sanic.response import file, json
app = Sanic(__name__) app = Sanic(__name__)
blueprint = Blueprint("name", url_prefix="/my_blueprint") blueprint = Blueprint("name", url_prefix="/my_blueprint")
blueprint2 = Blueprint("name2", url_prefix="/my_blueprint2") blueprint2 = Blueprint("name2", url_prefix="/my_blueprint2")

View File

@ -2,17 +2,20 @@ from asyncio import sleep
from sanic import Sanic, response from sanic import Sanic, response
app = Sanic(__name__, strict_slashes=True) app = Sanic(__name__, strict_slashes=True)
@app.get("/") @app.get("/")
async def handler(request): async def handler(request):
return response.redirect("/sleep/3") return response.redirect("/sleep/3")
@app.get("/sleep/<t:number>") @app.get("/sleep/<t:number>")
async def handler2(request, t=0.3): async def handler2(request, t=0.3):
await sleep(t) await sleep(t)
return response.text(f"Slept {t:.1f} seconds.\n") return response.text(f"Slept {t:.1f} seconds.\n")
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -7,8 +7,10 @@ and pass in an instance of it when we create our Sanic instance. Inside this
class' default handler, we can do anything including sending exceptions to class' default handler, we can do anything including sending exceptions to
an external service. an external service.
""" """
from sanic.handlers import ErrorHandler
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from sanic.handlers import ErrorHandler
""" """
Imports and code relevant for our CustomHandler class Imports and code relevant for our CustomHandler class
(Ordinarily this would be in a separate file) (Ordinarily this would be in a separate file)
@ -16,7 +18,6 @@ Imports and code relevant for our CustomHandler class
class CustomHandler(ErrorHandler): class CustomHandler(ErrorHandler):
def default(self, request, exception): def default(self, request, exception):
# Here, we have access to the exception object # Here, we have access to the exception object
# and can do anything with it (log, send to external service, etc) # and can do anything with it (log, send to external service, etc)
@ -38,17 +39,17 @@ server's error_handler to an instance of our CustomHandler
from sanic import Sanic from sanic import Sanic
app = Sanic(__name__)
handler = CustomHandler() handler = CustomHandler()
app.error_handler = handler app = Sanic(__name__, error_handler=handler)
@app.route("/") @app.route("/")
async def test(request): async def test(request):
# Here, something occurs which causes an unexpected exception # Here, something occurs which causes an unexpected exception
# This exception will flow to our custom handler. # This exception will flow to our custom handler.
raise SanicException('You Broke It!') raise SanicException("You Broke It!")
if __name__ == '__main__':
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,4 +1,6 @@
from sanic import Sanic, response, text from sanic import Sanic, response, text
from sanic.handlers import ErrorHandler
from sanic.server.async_server import AsyncioServer
HTTP_PORT = 9999 HTTP_PORT = 9999
@ -32,20 +34,40 @@ def proxy(request, path):
return response.redirect(url) return response.redirect(url)
@https.listener("main_process_start") @https.main_process_start
async def start(app, _): async def start(app, _):
global http http_server = await http.create_server(
app.http_server = await http.create_server(
port=HTTP_PORT, return_asyncio_server=True port=HTTP_PORT, return_asyncio_server=True
) )
app.http_server.after_start() app.add_task(runner(http, http_server))
app.ctx.http_server = http_server
app.ctx.http = http
@https.listener("main_process_stop") @https.main_process_stop
async def stop(app, _): async def stop(app, _):
app.http_server.before_stop() await app.ctx.http_server.before_stop()
await app.http_server.close() await app.ctx.http_server.close()
app.http_server.after_stop() for connection in app.ctx.http_server.connections:
connection.close_if_idle()
await app.ctx.http_server.after_stop()
app.ctx.http = False
async def runner(app: Sanic, app_server: AsyncioServer):
app.is_running = True
try:
app.signalize()
app.finalize()
ErrorHandler.finalize(app.error_handler)
app_server.init = True
await app_server.before_start()
await app_server.after_start()
await app_server.serve_forever()
finally:
app.is_running = False
app.is_stopping = True
https.run(port=HTTPS_PORT, debug=True) https.run(port=HTTPS_PORT, debug=True)

View File

@ -1,26 +1,30 @@
import asyncio
import httpx
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic.response import json
import asyncio
import aiohttp
app = Sanic(__name__) app = Sanic(__name__)
sem = None sem = None
@app.listener('before_server_start') @app.before_server_start
def init(sanic, loop): def init(sanic, _):
global sem global sem
concurrency_per_worker = 4 concurrency_per_worker = 4
sem = asyncio.Semaphore(concurrency_per_worker, loop=loop) sem = asyncio.Semaphore(concurrency_per_worker)
async def bounded_fetch(session, url): async def bounded_fetch(session, url):
""" """
Use session object to perform 'get' request on url Use session object to perform 'get' request on url
""" """
async with sem, session.get(url) as response: async with sem:
return await response.json() response = await session.get(url)
return response.json()
@app.route("/") @app.route("/")
@ -28,9 +32,9 @@ async def test(request):
""" """
Download and serve example JSON Download and serve example JSON
""" """
url = "https://api.github.com/repos/channelcat/sanic" url = "https://api.github.com/repos/sanic-org/sanic"
async with aiohttp.ClientSession() as session: async with httpx.AsyncClient() as session:
response = await bounded_fetch(session, url) response = await bounded_fetch(session, url)
return json(response) return json(response)

View File

@ -1,6 +1,6 @@
import logging import logging
import aiotask_context as context from contextvars import ContextVar
from sanic import Sanic, response from sanic import Sanic, response
@ -11,8 +11,8 @@ log = logging.getLogger(__name__)
class RequestIdFilter(logging.Filter): class RequestIdFilter(logging.Filter):
def filter(self, record): def filter(self, record):
try: try:
record.request_id = context.get("X-Request-ID") record.request_id = app.ctx.request_id.get(None) or "n/a"
except ValueError: except AttributeError:
record.request_id = "n/a" record.request_id = "n/a"
return True return True
@ -49,8 +49,7 @@ app = Sanic(__name__, log_config=LOG_SETTINGS)
@app.on_request @app.on_request
async def set_request_id(request): async def set_request_id(request):
request_id = request.id request.app.ctx.request_id.set(request.id)
context.set("X-Request-ID", request_id)
log.info(f"Setting {request.id=}") log.info(f"Setting {request.id=}")
@ -61,14 +60,14 @@ async def set_request_header(request, response):
@app.route("/") @app.route("/")
async def test(request): async def test(request):
log.debug("X-Request-ID: %s", context.get("X-Request-ID")) log.debug("X-Request-ID: %s", request.id)
log.info("Hello from test!") log.info("Hello from test!")
return response.json({"test": True}) return response.json({"test": True})
@app.before_server_start @app.before_server_start
def setup(app, loop): def setup(app, loop):
loop.set_task_factory(context.task_factory) app.ctx.request_id = ContextVar("request_id")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,5 +1,6 @@
import logging import logging
import socket import socket
from os import getenv from os import getenv
from platform import node from platform import node
from uuid import getnode as get_mac from uuid import getnode as get_mac
@ -7,10 +8,11 @@ from uuid import getnode as get_mac
from logdna import LogDNAHandler from logdna import LogDNAHandler
from sanic import Sanic from sanic import Sanic
from sanic.response import json
from sanic.request import Request from sanic.request import Request
from sanic.response import json
log = logging.getLogger('logdna')
log = logging.getLogger("logdna")
log.setLevel(logging.INFO) log.setLevel(logging.INFO)
@ -30,10 +32,12 @@ logdna_options = {
"index_meta": True, "index_meta": True,
"hostname": node(), "hostname": node(),
"ip": get_my_ip_address(), "ip": get_my_ip_address(),
"mac": get_mac_address() "mac": get_mac_address(),
} }
logdna_handler = LogDNAHandler(getenv("LOGDNA_API_KEY"), options=logdna_options) logdna_handler = LogDNAHandler(
getenv("LOGDNA_API_KEY"), options=logdna_options
)
logdna = logging.getLogger(__name__) logdna = logging.getLogger(__name__)
logdna.setLevel(logging.INFO) logdna.setLevel(logging.INFO)
@ -49,13 +53,8 @@ def log_request(request: Request):
@app.route("/") @app.route("/")
def default(request): def default(request):
return json({ return json({"response": "I was here"})
"response": "I was here"
})
if __name__ == "__main__": if __name__ == "__main__":
app.run( app.run(host="0.0.0.0", port=getenv("PORT", 8080))
host="0.0.0.0",
port=getenv("PORT", 8080)
)

View File

@ -59,31 +59,31 @@ async def handler_stream(request):
return response.stream(body) return response.stream(body)
@app.listener("before_server_start") @app.before_server_start
async def listener_before_server_start(*args, **kwargs): async def listener_before_server_start(*args, **kwargs):
print("before_server_start") print("before_server_start")
@app.listener("after_server_start") @app.after_server_start
async def listener_after_server_start(*args, **kwargs): async def listener_after_server_start(*args, **kwargs):
print("after_server_start") print("after_server_start")
@app.listener("before_server_stop") @app.before_server_stop
async def listener_before_server_stop(*args, **kwargs): async def listener_before_server_stop(*args, **kwargs):
print("before_server_stop") print("before_server_stop")
@app.listener("after_server_stop") @app.after_server_stop
async def listener_after_server_stop(*args, **kwargs): async def listener_after_server_stop(*args, **kwargs):
print("after_server_stop") print("after_server_stop")
@app.middleware("request") @app.on_request
async def print_on_request(request): async def print_on_request(request):
print("print_on_request") print("print_on_request")
@app.middleware("response") @app.on_response
async def print_on_response(request, response): async def print_on_response(request, response):
print("print_on_response") print("print_on_response")

View File

@ -1,9 +1,12 @@
from sanic import Sanic
from sanic import response
from signal import signal, SIGINT
import asyncio import asyncio
from signal import SIGINT, signal
import uvloop import uvloop
from sanic import Sanic, response
app = Sanic(__name__) app = Sanic(__name__)
@ -11,12 +14,18 @@ app = Sanic(__name__)
async def test(request): async def test(request):
return response.json({"answer": "42"}) return response.json({"answer": "42"})
asyncio.set_event_loop(uvloop.new_event_loop()) asyncio.set_event_loop(uvloop.new_event_loop())
server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True) server = app.create_server(
host="0.0.0.0", port=8000, return_asyncio_server=True
)
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
task = asyncio.ensure_future(server) task = asyncio.ensure_future(server)
server = loop.run_until_complete(task)
loop.run_until_complete(server.startup())
signal(SIGINT, lambda s, f: loop.stop()) signal(SIGINT, lambda s, f: loop.stop())
try: try:
loop.run_forever() loop.run_forever()
except: finally:
loop.stop() loop.stop()

View File

@ -11,9 +11,24 @@ from sanic.server import AsyncioServer
app = Sanic(__name__) app = Sanic(__name__)
@app.listener("after_server_start") @app.before_server_start
async def after_start_test(app, loop): async def before_server_start(app, loop):
print("Async Server Started!") print("Async Server starting")
@app.after_server_start
async def after_server_start(app, loop):
print("Async Server started")
@app.before_server_stop
async def before_server_stop(app, loop):
print("Async Server stopping")
@app.after_server_stop
async def after_server_stop(app, loop):
print("Async Server stopped")
@app.route("/") @app.route("/")
@ -28,20 +43,20 @@ serv_coro = app.create_server(
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
serv_task = asyncio.ensure_future(serv_coro, loop=loop) serv_task = asyncio.ensure_future(serv_coro, loop=loop)
signal(SIGINT, lambda s, f: loop.stop()) signal(SIGINT, lambda s, f: loop.stop())
server: AsyncioServer = loop.run_until_complete(serv_task) # type: ignore server: AsyncioServer = loop.run_until_complete(serv_task)
server.startup() loop.run_until_complete(server.startup())
# When using app.run(), this actually triggers before the serv_coro. # When using app.run(), this actually triggers before the serv_coro.
# But, in this example, we are using the convenience method, even if it is # But, in this example, we are using the convenience method, even if it is
# out of order. # out of order.
server.before_start() loop.run_until_complete(server.before_start())
server.after_start() loop.run_until_complete(server.after_start())
try: try:
loop.run_forever() loop.run_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
loop.stop() loop.stop()
finally: finally:
server.before_stop() loop.run_until_complete(server.before_stop())
# Wait for server to close # Wait for server to close
close_task = server.close() close_task = server.close()
@ -50,4 +65,4 @@ finally:
# Complete all tasks on the loop # Complete all tasks on the loop
for connection in server.connections: for connection in server.connections:
connection.close_if_idle() connection.close_if_idle()
server.after_stop() loop.run_until_complete(server.after_stop())

View File

@ -1,42 +1,41 @@
from sanic import Sanic from sanic import Sanic
from sanic.views import HTTPMethodView
from sanic.response import text from sanic.response import text
from sanic.views import HTTPMethodView
app = Sanic('some_name')
app = Sanic("some_name")
class SimpleView(HTTPMethodView): class SimpleView(HTTPMethodView):
def get(self, request): def get(self, request):
return text('I am get method') return text("I am get method")
def post(self, request): def post(self, request):
return text('I am post method') return text("I am post method")
def put(self, request): def put(self, request):
return text('I am put method') return text("I am put method")
def patch(self, request): def patch(self, request):
return text('I am patch method') return text("I am patch method")
def delete(self, request): def delete(self, request):
return text('I am delete method') return text("I am delete method")
class SimpleAsyncView(HTTPMethodView): class SimpleAsyncView(HTTPMethodView):
async def get(self, request): async def get(self, request):
return text('I am async get method') return text("I am async get method")
async def post(self, request): async def post(self, request):
return text('I am async post method') return text("I am async post method")
async def put(self, request): async def put(self, request):
return text('I am async put method') return text("I am async put method")
app.add_route(SimpleView.as_view(), '/') app.add_route(SimpleView.as_view(), "/")
app.add_route(SimpleAsyncView.as_view(), '/async') app.add_route(SimpleAsyncView.as_view(), "/async")
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,9 +1,9 @@
import os import os
from sanic import Sanic from sanic import Sanic, response
from sanic.log import logger as log
from sanic import response
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.log import logger as log
app = Sanic(__name__) app = Sanic(__name__)
@ -13,7 +13,7 @@ async def test_async(request):
return response.json({"test": True}) return response.json({"test": True})
@app.route("/sync", methods=['GET', 'POST']) @app.route("/sync", methods=["GET", "POST"])
def test_sync(request): def test_sync(request):
return response.json({"test": True}) return response.json({"test": True})
@ -31,6 +31,7 @@ def exception(request):
@app.route("/await") @app.route("/await")
async def test_await(request): async def test_await(request):
import asyncio import asyncio
await asyncio.sleep(5) await asyncio.sleep(5)
return response.text("I'm feeling sleepy") return response.text("I'm feeling sleepy")
@ -42,8 +43,10 @@ async def test_file(request):
@app.route("/file_stream") @app.route("/file_stream")
async def test_file_stream(request): async def test_file_stream(request):
return await response.file_stream(os.path.abspath("setup.py"), return await response.file_stream(
chunk_size=1024) os.path.abspath("setup.py"), chunk_size=1024
)
# ----------------------------------------------- # # ----------------------------------------------- #
# Exceptions # Exceptions
@ -52,14 +55,17 @@ async def test_file_stream(request):
@app.exception(ServerError) @app.exception(ServerError)
async def test(request, exception): async def test(request, exception):
return response.json({"exception": "{}".format(exception), "status": exception.status_code}, return response.json(
status=exception.status_code) {"exception": str(exception), "status": exception.status_code},
status=exception.status_code,
)
# ----------------------------------------------- # # ----------------------------------------------- #
# Read from request # Read from request
# ----------------------------------------------- # # ----------------------------------------------- #
@app.route("/json") @app.route("/json")
def post_json(request): def post_json(request):
return response.json({"received": True, "message": request.json}) return response.json({"received": True, "message": request.json})
@ -67,38 +73,51 @@ def post_json(request):
@app.route("/form") @app.route("/form")
def post_form_json(request): def post_form_json(request):
return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')}) return response.json(
{
"received": True,
"form_data": request.form,
"test": request.form.get("test"),
}
)
@app.route("/query_string") @app.route("/query_string")
def query_string(request): def query_string(request):
return response.json({"parsed": True, "args": request.args, "url": request.url, return response.json(
"query_string": request.query_string}) {
"parsed": True,
"args": request.args,
"url": request.url,
"query_string": request.query_string,
}
)
# ----------------------------------------------- # # ----------------------------------------------- #
# Run Server # Run Server
# ----------------------------------------------- # # ----------------------------------------------- #
@app.listener('before_server_start')
@app.before_server_start
def before_start(app, loop): def before_start(app, loop):
log.info("SERVER STARTING") log.info("SERVER STARTING")
@app.listener('after_server_start') @app.after_server_start
def after_start(app, loop): def after_start(app, loop):
log.info("OH OH OH OH OHHHHHHHH") log.info("OH OH OH OH OHHHHHHHH")
@app.listener('before_server_stop') @app.before_server_stop
def before_stop(app, loop): def before_stop(app, loop):
log.info("SERVER STOPPING") log.info("SERVER STOPPING")
@app.listener('after_server_stop') @app.after_server_stop
def after_stop(app, loop): def after_stop(app, loop):
log.info("TRIED EVERYTHING") log.info("TRIED EVERYTHING")
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -1,7 +1,8 @@
from sanic import Sanic
from sanic import response
import socket
import os import os
import socket
from sanic import Sanic, response
app = Sanic(__name__) app = Sanic(__name__)
@ -10,14 +11,15 @@ app = Sanic(__name__)
async def test(request): async def test(request):
return response.text("OK") return response.text("OK")
if __name__ == '__main__':
server_address = './uds_socket' if __name__ == "__main__":
server_address = "./uds_socket"
# Make sure the socket does not already exist # Make sure the socket does not already exist
try: try:
os.unlink(server_address) os.unlink(server_address)
except OSError: except OSError:
if os.path.exists(server_address): if os.path.exists(server_address):
raise raise
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.bind(server_address) sock.bind(server_address)
app.run(sock=sock) app.run(sock=sock)

View File

@ -1,20 +1,21 @@
from sanic import Sanic from sanic import Sanic, response
from sanic import response
app = Sanic(__name__) app = Sanic(__name__)
@app.route('/') @app.route("/")
async def index(request): async def index(request):
# generate a URL for the endpoint `post_handler` # generate a URL for the endpoint `post_handler`
url = app.url_for('post_handler', post_id=5) url = app.url_for("post_handler", post_id=5)
# the URL is `/posts/5`, redirect to it # the URL is `/posts/5`, redirect to it
return response.redirect(url) return response.redirect(url)
@app.route('/posts/<post_id>') @app.route("/posts/<post_id>")
async def post_handler(request, post_id): async def post_handler(request, post_id):
return response.text('Post - {}'.format(post_id)) return response.text("Post - {}".format(post_id))
if __name__ == '__main__':
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -8,7 +8,9 @@ app = Sanic(name="blue-print-group-version-example")
bp1 = Blueprint(name="ultron", url_prefix="/ultron") bp1 = Blueprint(name="ultron", url_prefix="/ultron")
bp2 = Blueprint(name="vision", url_prefix="/vision", strict_slashes=None) bp2 = Blueprint(name="vision", url_prefix="/vision", strict_slashes=None)
bpg = Blueprint.group([bp1, bp2], url_prefix="/sentient/robot", version=1, strict_slashes=True) bpg = Blueprint.group(
bp1, bp2, url_prefix="/sentient/robot", version=1, strict_slashes=True
)
@bp1.get("/name") @bp1.get("/name")
@ -31,5 +33,5 @@ async def bp2_revised_name(request):
app.blueprint(bpg) app.blueprint(bpg)
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -1,25 +1,27 @@
from sanic import Sanic from sanic import Sanic
from sanic.response import redirect from sanic.response import redirect
app = Sanic(__name__) app = Sanic(__name__)
app.static('index.html', "websocket.html") app.static("index.html", "websocket.html")
@app.route('/')
@app.route("/")
def index(request): def index(request):
return redirect("index.html") return redirect("index.html")
@app.websocket('/feed')
@app.websocket("/feed")
async def feed(request, ws): async def feed(request, ws):
while True: while True:
data = 'hello!' data = "hello!"
print('Sending: ' + data) print("Sending: " + data)
await ws.send(data) await ws.send(data)
data = await ws.recv() data = await ws.recv()
print('Received: ' + data) print("Received: " + data)
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -337,7 +337,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
""" """
Method for attaching middleware to specific routes. This is mainly an Method for attaching middleware to specific routes. This is mainly an
internal tool for use by Blueprints to attach middleware to only its internal tool for use by Blueprints to attach middleware to only its
specfic routes. But, it could be used in a more generalized fashion. specific routes. But, it could be used in a more generalized fashion.
:param middleware: the middleware to execute :param middleware: the middleware to execute
:param route_names: a list of the names of the endpoints :param route_names: a list of the names of the endpoints
@ -775,6 +775,14 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
if request.stream: if request.stream:
response = request.stream.response response = request.stream.response
if isinstance(response, BaseHTTPResponse): if isinstance(response, BaseHTTPResponse):
await self.dispatch(
"http.lifecycle.response",
inline=True,
context={
"request": request,
"response": response,
},
)
await response.send(end_stream=True) await response.send(end_stream=True)
else: else:
raise ServerError( raise ServerError(

View File

@ -64,8 +64,10 @@ class MOTDTTY(MOTD):
self.set_variables() self.set_variables()
def set_variables(self): # no cov def set_variables(self): # no cov
fallback = (80, 24) fallback = (108, 24)
terminal_width = min(get_terminal_size(fallback=fallback).columns, 108) terminal_width = max(
get_terminal_size(fallback=fallback).columns, fallback[0]
)
self.max_value_width = terminal_width - fallback[0] + 36 self.max_value_width = terminal_width - fallback[0] + 36
self.key_width = 4 self.key_width = 4

View File

@ -79,7 +79,7 @@ class Blueprint(BaseSanic):
:param name: unique name of the blueprint :param name: unique name of the blueprint
:param url_prefix: URL to be prefixed before all route URLs :param url_prefix: URL to be prefixed before all route URLs
:param host: IP Address of FQDN for the sanic server to use. :param host: IP Address or FQDN for the sanic server to use.
:param version: Blueprint Version :param version: Blueprint Version
:param strict_slashes: Enforce the API urls are requested with a :param strict_slashes: Enforce the API urls are requested with a
trailing */* trailing */*
@ -112,7 +112,7 @@ class Blueprint(BaseSanic):
self, self,
name: str = None, name: str = None,
url_prefix: Optional[str] = None, url_prefix: Optional[str] = None,
host: Optional[str] = None, host: Optional[Union[List[str], str]] = None,
version: Optional[Union[int, str, float]] = None, version: Optional[Union[int, str, float]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
version_prefix: str = "/v", version_prefix: str = "/v",

View File

@ -176,11 +176,11 @@ class Config(dict):
def load_environment_vars(self, prefix=SANIC_PREFIX): def load_environment_vars(self, prefix=SANIC_PREFIX):
""" """
Looks for prefixed environment variables and applies Looks for prefixed environment variables and applies them to the
them to the configuration if present. This is called automatically when configuration if present. This is called automatically when Sanic
Sanic starts up to load environment variables into config. starts up to load environment variables into config.
It will automatically hyrdate the following types: It will automatically hydrate the following types:
- ``int`` - ``int``
- ``float`` - ``float``
@ -188,19 +188,18 @@ class Config(dict):
Anything else will be imported as a ``str``. Anything else will be imported as a ``str``.
""" """
for k, v in environ.items(): for key, value in environ.items():
if k.startswith(prefix): if not key.startswith(prefix):
_, config_key = k.split(prefix, 1) continue
_, config_key = key.split(prefix, 1)
for converter in (int, float, str_to_bool, str):
try: try:
self[config_key] = int(v) self[config_key] = converter(value)
break
except ValueError: except ValueError:
try: pass
self[config_key] = float(v)
except ValueError:
try:
self[config_key] = str_to_bool(v)
except ValueError:
self[config_key] = v
def update_config(self, config: Union[bytes, str, dict, Any]): def update_config(self, config: Union[bytes, str, dict, Any]):
""" """

View File

@ -28,7 +28,7 @@ _host_re = re.compile(
# RFC's quoted-pair escapes are mostly ignored by browsers. Chrome, Firefox and # RFC's quoted-pair escapes are mostly ignored by browsers. Chrome, Firefox and
# curl all have different escaping, that we try to handle as well as possible, # curl all have different escaping, that we try to handle as well as possible,
# even though no client espaces in a way that would allow perfect handling. # even though no client escapes in a way that would allow perfect handling.
# For more information, consult ../tests/test_requests.py # For more information, consult ../tests/test_requests.py

View File

@ -144,7 +144,7 @@ def import_string(module_name, package=None):
import a module or class by string path. import a module or class by string path.
:module_name: str with path of module or path to import and :module_name: str with path of module or path to import and
instanciate a class instantiate a class
:returns: a module object or one instance from class if :returns: a module object or one instance from class if
module_name is a valid path to class module_name is a valid path to class

View File

@ -52,7 +52,7 @@ class RouteMixin:
self, self,
uri: str, uri: str,
methods: Optional[Iterable[str]] = None, methods: Optional[Iterable[str]] = None,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
stream: bool = False, stream: bool = False,
version: Optional[Union[int, str, float]] = None, version: Optional[Union[int, str, float]] = None,
@ -189,9 +189,9 @@ class RouteMixin:
handler: RouteHandler, handler: RouteHandler,
uri: str, uri: str,
methods: Iterable[str] = frozenset({"GET"}), methods: Iterable[str] = frozenset({"GET"}),
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
stream: bool = False, stream: bool = False,
version_prefix: str = "/v", version_prefix: str = "/v",
@ -254,9 +254,9 @@ class RouteMixin:
def get( def get(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
ignore_body: bool = True, ignore_body: bool = True,
version_prefix: str = "/v", version_prefix: str = "/v",
@ -290,10 +290,10 @@ class RouteMixin:
def post( def post(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
stream: bool = False, stream: bool = False,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
version_prefix: str = "/v", version_prefix: str = "/v",
error_format: Optional[str] = None, error_format: Optional[str] = None,
@ -326,10 +326,10 @@ class RouteMixin:
def put( def put(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
stream: bool = False, stream: bool = False,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
version_prefix: str = "/v", version_prefix: str = "/v",
error_format: Optional[str] = None, error_format: Optional[str] = None,
@ -362,9 +362,9 @@ class RouteMixin:
def head( def head(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
ignore_body: bool = True, ignore_body: bool = True,
version_prefix: str = "/v", version_prefix: str = "/v",
@ -406,9 +406,9 @@ class RouteMixin:
def options( def options(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
ignore_body: bool = True, ignore_body: bool = True,
version_prefix: str = "/v", version_prefix: str = "/v",
@ -450,10 +450,10 @@ class RouteMixin:
def patch( def patch(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
stream=False, stream=False,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
version_prefix: str = "/v", version_prefix: str = "/v",
error_format: Optional[str] = None, error_format: Optional[str] = None,
@ -496,9 +496,9 @@ class RouteMixin:
def delete( def delete(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
ignore_body: bool = True, ignore_body: bool = True,
version_prefix: str = "/v", version_prefix: str = "/v",
@ -532,10 +532,10 @@ class RouteMixin:
def websocket( def websocket(
self, self,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
subprotocols: Optional[List[str]] = None, subprotocols: Optional[List[str]] = None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
apply: bool = True, apply: bool = True,
version_prefix: str = "/v", version_prefix: str = "/v",
@ -573,10 +573,10 @@ class RouteMixin:
self, self,
handler, handler,
uri: str, uri: str,
host: Optional[str] = None, host: Optional[Union[str, List[str]]] = None,
strict_slashes: Optional[bool] = None, strict_slashes: Optional[bool] = None,
subprotocols=None, subprotocols=None,
version: Optional[int] = None, version: Optional[Union[int, str, float]] = None,
name: Optional[str] = None, name: Optional[str] = None,
version_prefix: str = "/v", version_prefix: str = "/v",
error_format: Optional[str] = None, error_format: Optional[str] = None,

View File

@ -13,7 +13,7 @@ class FutureRoute(NamedTuple):
handler: str handler: str
uri: str uri: str
methods: Optional[Iterable[str]] methods: Optional[Iterable[str]]
host: str host: Union[str, List[str]]
strict_slashes: bool strict_slashes: bool
stream: bool stream: bool
version: Optional[int] version: Optional[int]

View File

@ -101,7 +101,7 @@ class BaseHTTPResponse:
async def send( async def send(
self, self,
data: Optional[Union[AnyStr]] = None, data: Optional[AnyStr] = None,
end_stream: Optional[bool] = None, end_stream: Optional[bool] = None,
) -> None: ) -> None:
""" """

View File

@ -54,7 +54,7 @@ class Router(BaseRouter):
self, path: str, method: str, host: Optional[str] self, path: str, method: str, host: Optional[str]
) -> Tuple[Route, RouteHandler, Dict[str, Any]]: ) -> Tuple[Route, RouteHandler, Dict[str, Any]]:
""" """
Retrieve a `Route` object containg the details about how to handle Retrieve a `Route` object containing the details about how to handle
a response for a given request a response for a given request
:param request: the incoming request object :param request: the incoming request object

View File

@ -175,7 +175,7 @@ def match_hostname(
def selector_sni_callback( def selector_sni_callback(
sslobj: ssl.SSLObject, server_name: str, ctx: CertSelector sslobj: ssl.SSLObject, server_name: str, ctx: CertSelector
) -> Optional[int]: ) -> Optional[int]:
"""Select a certificate mathing the SNI.""" """Select a certificate matching the SNI."""
# Call server_name_callback to store the SNI on sslobj # Call server_name_callback to store the SNI on sslobj
server_name_callback(sslobj, server_name, ctx) server_name_callback(sslobj, server_name, ctx)
# Find a new context matching the hostname # Find a new context matching the hostname

View File

@ -48,7 +48,7 @@ def load_module_from_file_location(
"""Returns loaded module provided as a file path. """Returns loaded module provided as a file path.
:param args: :param args:
Coresponds to importlib.util.spec_from_file_location location Corresponds to importlib.util.spec_from_file_location location
parameters,but with this differences: parameters,but with this differences:
- It has to be of a string or bytes type. - It has to be of a string or bytes type.
- You can also use here environment variables - You can also use here environment variables
@ -58,10 +58,10 @@ def load_module_from_file_location(
If location parameter is of a bytes type, then use this encoding If location parameter is of a bytes type, then use this encoding
to decode it into string. to decode it into string.
:param args: :param args:
Coresponds to the rest of importlib.util.spec_from_file_location Corresponds to the rest of importlib.util.spec_from_file_location
parameters. parameters.
:param kwargs: :param kwargs:
Coresponds to the rest of importlib.util.spec_from_file_location Corresponds to the rest of importlib.util.spec_from_file_location
parameters. parameters.
For example You can: For example You can:

View File

@ -310,7 +310,7 @@ if __name__ == "__main__":
cli.add_argument( cli.add_argument(
"--milestone", "--milestone",
"-ms", "-ms",
help="Git Release milestone information to include in relase note", help="Git Release milestone information to include in release note",
required=False, required=False,
) )
cli.add_argument( cli.add_argument(

View File

@ -121,6 +121,7 @@ docs_require = [
"docutils", "docutils",
"pygments", "pygments",
"m2r2", "m2r2",
"mistune<2.0.0",
] ]
dev_require = tests_require + [ dev_require = tests_require + [

View File

@ -17,7 +17,7 @@ def test_custom_context(app):
@app.route("/") @app.route("/")
def handler(request): def handler(request):
# Accessing non-existant key should fail with AttributeError # Accessing non-existent key should fail with AttributeError
try: try:
invalid = request.ctx.missing invalid = request.ctx.missing
except AttributeError as e: except AttributeError as e:

View File

@ -21,7 +21,7 @@ def gunicorn_worker():
"gunicorn " "gunicorn "
f"--bind 127.0.0.1:{PORT} " f"--bind 127.0.0.1:{PORT} "
"--worker-class sanic.worker.GunicornWorker " "--worker-class sanic.worker.GunicornWorker "
"examples.simple_server:app" "examples.hello_world:app"
) )
worker = subprocess.Popen(shlex.split(command)) worker = subprocess.Popen(shlex.split(command))
time.sleep(2) time.sleep(2)
@ -35,7 +35,7 @@ def gunicorn_worker_with_access_logs():
"gunicorn " "gunicorn "
f"--bind 127.0.0.1:{PORT + 1} " f"--bind 127.0.0.1:{PORT + 1} "
"--worker-class sanic.worker.GunicornWorker " "--worker-class sanic.worker.GunicornWorker "
"examples.simple_server:app" "examples.hello_world:app"
) )
worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
time.sleep(2) time.sleep(2)
@ -50,7 +50,7 @@ def gunicorn_worker_with_env_var():
f"--bind 127.0.0.1:{PORT + 2} " f"--bind 127.0.0.1:{PORT + 2} "
"--worker-class sanic.worker.GunicornWorker " "--worker-class sanic.worker.GunicornWorker "
"--log-level info " "--log-level info "
"examples.simple_server:app" "examples.hello_world:app"
) )
worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) worker = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
time.sleep(2) time.sleep(2)