Merge branch 'master' into 594
This commit is contained in:
commit
9b3bda8d36
6
Dockerfile
Normal file
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
FROM python:3.6
|
||||||
|
|
||||||
|
ADD . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN pip install tox
|
4
Makefile
Normal file
4
Makefile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
test:
|
||||||
|
find . -name "*.pyc" -delete
|
||||||
|
docker build -t sanic/test-image .
|
||||||
|
docker run -t sanic/test-image tox
|
|
@ -29,6 +29,14 @@ In general the convention is to only have UPPERCASE configuration parameters. Th
|
||||||
|
|
||||||
There are several ways how to load configuration.
|
There are several ways how to load configuration.
|
||||||
|
|
||||||
|
### From environment variables.
|
||||||
|
|
||||||
|
Any variables defined with the `SANIC_` prefix will be applied to the sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically. You can pass the `load_vars` boolean to the Sanic constructor to override that:
|
||||||
|
|
||||||
|
```python
|
||||||
|
app = Sanic(load_vars=False)
|
||||||
|
```
|
||||||
|
|
||||||
### From an Object
|
### From an Object
|
||||||
|
|
||||||
If there are a lot of configuration values and they have sensible defaults it might be helpful to put them into a module:
|
If there are a lot of configuration values and they have sensible defaults it might be helpful to put them into a module:
|
||||||
|
|
|
@ -18,3 +18,4 @@ A list of Sanic extensions created by the community.
|
||||||
`Babel` library
|
`Babel` library
|
||||||
- [Dispatch](https://github.com/ashleysommer/sanic-dispatcher): A dispatcher inspired by `DispatcherMiddleware` in werkzeug. Can act as a Sanic-to-WSGI adapter.
|
- [Dispatch](https://github.com/ashleysommer/sanic-dispatcher): A dispatcher inspired by `DispatcherMiddleware` in werkzeug. Can act as a Sanic-to-WSGI adapter.
|
||||||
- [Sanic-OAuth](https://github.com/Sniedes722/Sanic-OAuth): OAuth Library for connecting to & creating your own token providers.
|
- [Sanic-OAuth](https://github.com/Sniedes722/Sanic-OAuth): OAuth Library for connecting to & creating your own token providers.
|
||||||
|
- [Sanic-nginx-docker-example](https://github.com/itielshwartz/sanic-nginx-docker-example): Simple and easy to use example of Sanic behined nginx using docker-compose.
|
||||||
|
|
|
@ -55,8 +55,8 @@ from sanic import response
|
||||||
@app.route("/streaming")
|
@app.route("/streaming")
|
||||||
async def index(request):
|
async def index(request):
|
||||||
async def streaming_fn(response):
|
async def streaming_fn(response):
|
||||||
await response.write('foo')
|
response.write('foo')
|
||||||
await response.write('bar')
|
response.write('bar')
|
||||||
return response.stream(streaming_fn, content_type='text/plain')
|
return response.stream(streaming_fn, content_type='text/plain')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
0
examples/asyncorm/__init__.py
Normal file
0
examples/asyncorm/__init__.py
Normal file
140
examples/asyncorm/__main__.py
Normal file
140
examples/asyncorm/__main__.py
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
from sanic import Sanic
|
||||||
|
from sanic.exceptions import NotFound
|
||||||
|
from sanic.response import json
|
||||||
|
from sanic.views import HTTPMethodView
|
||||||
|
|
||||||
|
from asyncorm import configure_orm
|
||||||
|
from asyncorm.exceptions import QuerysetError
|
||||||
|
|
||||||
|
from library.models import Book
|
||||||
|
from library.serializer import BookSerializer
|
||||||
|
|
||||||
|
app = Sanic(name=__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('before_server_start')
|
||||||
|
def orm_configure(sanic, loop):
|
||||||
|
db_config = {'database': 'sanic_example',
|
||||||
|
'host': 'localhost',
|
||||||
|
'user': 'sanicdbuser',
|
||||||
|
'password': 'sanicDbPass',
|
||||||
|
}
|
||||||
|
|
||||||
|
# configure_orm needs a dictionary with:
|
||||||
|
# * the database configuration
|
||||||
|
# * the application/s where the models are defined
|
||||||
|
orm_app = configure_orm({'loop': loop, # always use the sanic loop!
|
||||||
|
'db_config': db_config,
|
||||||
|
'modules': ['library', ], # list of apps
|
||||||
|
})
|
||||||
|
|
||||||
|
# orm_app is the object that orchestrates the whole ORM
|
||||||
|
# sync_db should be run only once, better do that as external command
|
||||||
|
# it creates the tables in the database!!!!
|
||||||
|
# orm_app.sync_db()
|
||||||
|
|
||||||
|
|
||||||
|
# for all the 404 lets handle the exceptions
|
||||||
|
@app.exception(NotFound)
|
||||||
|
def ignore_404s(request, exception):
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': exception.status_code,
|
||||||
|
'error': exception.args[0],
|
||||||
|
'results': None,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# now the propper sanic workflow
|
||||||
|
class BooksView(HTTPMethodView):
|
||||||
|
def arg_parser(self, request):
|
||||||
|
parsed_args = {}
|
||||||
|
for k, v in request.args.items():
|
||||||
|
parsed_args[k] = v[0]
|
||||||
|
return parsed_args
|
||||||
|
|
||||||
|
async def get(self, request):
|
||||||
|
filtered_by = self.arg_parser(request)
|
||||||
|
|
||||||
|
if filtered_by:
|
||||||
|
q_books = await Book.objects.filter(**filtered_by)
|
||||||
|
else:
|
||||||
|
q_books = await Book.objects.all()
|
||||||
|
|
||||||
|
books = [BookSerializer.serialize(book) for book in q_books]
|
||||||
|
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': 200,
|
||||||
|
'results': books or None,
|
||||||
|
'count': len(books),
|
||||||
|
})
|
||||||
|
|
||||||
|
async def post(self, request):
|
||||||
|
# populate the book with the data in the request
|
||||||
|
book = Book(**request.json)
|
||||||
|
|
||||||
|
# and await on save
|
||||||
|
await book.save()
|
||||||
|
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': 201,
|
||||||
|
'results': BookSerializer.serialize(book),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class BookView(HTTPMethodView):
|
||||||
|
async def get_object(self, request, book_id):
|
||||||
|
try:
|
||||||
|
# await on database consults
|
||||||
|
book = await Book.objects.get(**{'id': book_id})
|
||||||
|
except QuerysetError as e:
|
||||||
|
raise NotFound(e.args[0])
|
||||||
|
return book
|
||||||
|
|
||||||
|
async def get(self, request, book_id):
|
||||||
|
# await on database consults
|
||||||
|
book = await self.get_object(request, book_id)
|
||||||
|
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': 200,
|
||||||
|
'results': BookSerializer.serialize(book),
|
||||||
|
})
|
||||||
|
|
||||||
|
async def put(self, request, book_id):
|
||||||
|
# await on database consults
|
||||||
|
book = await self.get_object(request, book_id)
|
||||||
|
# await on save
|
||||||
|
await book.save(**request.json)
|
||||||
|
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': 200,
|
||||||
|
'results': BookSerializer.serialize(book),
|
||||||
|
})
|
||||||
|
|
||||||
|
async def patch(self, request, book_id):
|
||||||
|
# await on database consults
|
||||||
|
book = await self.get_object(request, book_id)
|
||||||
|
# await on save
|
||||||
|
await book.save(**request.json)
|
||||||
|
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': 200,
|
||||||
|
'results': BookSerializer.serialize(book),
|
||||||
|
})
|
||||||
|
|
||||||
|
async def delete(self, request, book_id):
|
||||||
|
# await on database consults
|
||||||
|
book = await self.get_object(request, book_id)
|
||||||
|
# await on its deletion
|
||||||
|
await book.delete()
|
||||||
|
|
||||||
|
return json({'method': request.method,
|
||||||
|
'status': 200,
|
||||||
|
'results': None
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
app.add_route(BooksView.as_view(), '/books/')
|
||||||
|
app.add_route(BookView.as_view(), '/books/<book_id:int>/')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run()
|
0
examples/asyncorm/library/__init__.py
Normal file
0
examples/asyncorm/library/__init__.py
Normal file
21
examples/asyncorm/library/models.py
Normal file
21
examples/asyncorm/library/models.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from asyncorm.model import Model
|
||||||
|
from asyncorm.fields import CharField, IntegerField, DateField
|
||||||
|
|
||||||
|
|
||||||
|
BOOK_CHOICES = (
|
||||||
|
('hard cover', 'hard cover book'),
|
||||||
|
('paperback', 'paperback book')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# This is a simple model definition
|
||||||
|
class Book(Model):
|
||||||
|
name = CharField(max_length=50)
|
||||||
|
synopsis = CharField(max_length=255)
|
||||||
|
book_type = CharField(max_length=15, null=True, choices=BOOK_CHOICES)
|
||||||
|
pages = IntegerField(null=True)
|
||||||
|
date_created = DateField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta():
|
||||||
|
ordering = ['name', ]
|
||||||
|
unique_together = ['name', 'synopsis']
|
15
examples/asyncorm/library/serializer.py
Normal file
15
examples/asyncorm/library/serializer.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from asyncorm.model import ModelSerializer, SerializerMethod
|
||||||
|
from library.models import Book
|
||||||
|
|
||||||
|
|
||||||
|
class BookSerializer(ModelSerializer):
|
||||||
|
book_type = SerializerMethod()
|
||||||
|
|
||||||
|
def get_book_type(self, instance):
|
||||||
|
return instance.book_type_display()
|
||||||
|
|
||||||
|
class Meta():
|
||||||
|
model = Book
|
||||||
|
fields = [
|
||||||
|
'id', 'name', 'synopsis', 'book_type', 'pages', 'date_created'
|
||||||
|
]
|
2
examples/asyncorm/requirements.txt
Normal file
2
examples/asyncorm/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
asyncorm==0.0.7
|
||||||
|
sanic==0.4.1
|
136
examples/detailed_example.py
Normal file
136
examples/detailed_example.py
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
# This demo requires aioredis and environmental variables established in ENV_VARS
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import aioredis
|
||||||
|
|
||||||
|
import sanic
|
||||||
|
from sanic import Sanic
|
||||||
|
|
||||||
|
|
||||||
|
ENV_VARS = ["REDIS_HOST", "REDIS_PORT",
|
||||||
|
"REDIS_MINPOOL", "REDIS_MAXPOOL",
|
||||||
|
"REDIS_PASS", "APP_LOGFILE"]
|
||||||
|
|
||||||
|
app = Sanic(name=__name__)
|
||||||
|
|
||||||
|
logger = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.middleware("request")
|
||||||
|
async def log_uri(request):
|
||||||
|
# Simple middleware to log the URI endpoint that was called
|
||||||
|
logger.info("URI called: {0}".format(request.url))
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('before_server_start')
|
||||||
|
async def before_server_start(app, loop):
|
||||||
|
logger.info("Starting redis pool")
|
||||||
|
app.redis_pool = await aioredis.create_pool(
|
||||||
|
(app.config.REDIS_HOST, int(app.config.REDIS_PORT)),
|
||||||
|
minsize=int(app.config.REDIS_MINPOOL),
|
||||||
|
maxsize=int(app.config.REDIS_MAXPOOL),
|
||||||
|
password=app.config.REDIS_PASS)
|
||||||
|
|
||||||
|
|
||||||
|
@app.listener('after_server_stop')
|
||||||
|
async def after_server_stop(app, loop):
|
||||||
|
logger.info("Closing redis pool")
|
||||||
|
app.redis_pool.close()
|
||||||
|
await app.redis_pool.wait_closed()
|
||||||
|
|
||||||
|
|
||||||
|
@app.middleware("request")
|
||||||
|
async def attach_db_connectors(request):
|
||||||
|
# Just put the db objects in the request for easier access
|
||||||
|
logger.info("Passing redis pool to request object")
|
||||||
|
request["redis"] = request.app.redis_pool
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/state/<user_id>", methods=["GET"])
|
||||||
|
async def access_state(request, user_id):
|
||||||
|
try:
|
||||||
|
# Check to see if the value is in cache, if so lets return that
|
||||||
|
with await request["redis"] as redis_conn:
|
||||||
|
state = await redis_conn.get(user_id, encoding="utf-8")
|
||||||
|
if state:
|
||||||
|
return sanic.response.json({"msg": "Success",
|
||||||
|
"status": 200,
|
||||||
|
"success": True,
|
||||||
|
"data": json.loads(state),
|
||||||
|
"finished_at": datetime.now().isoformat()})
|
||||||
|
# Then state object is not in redis
|
||||||
|
logger.critical("Unable to find user_data in cache.")
|
||||||
|
return sanic.response.HTTPResponse({"msg": "User state not found",
|
||||||
|
"success": False,
|
||||||
|
"status": 404,
|
||||||
|
"finished_at": datetime.now().isoformat()}, status=404)
|
||||||
|
except aioredis.ProtocolError:
|
||||||
|
logger.critical("Unable to connect to state cache")
|
||||||
|
return sanic.response.HTTPResponse({"msg": "Internal Server Error",
|
||||||
|
"status": 500,
|
||||||
|
"success": False,
|
||||||
|
"finished_at": datetime.now().isoformat()}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/state/<user_id>/push", methods=["POST"])
|
||||||
|
async def set_state(request, user_id):
|
||||||
|
try:
|
||||||
|
# Pull a connection from the pool
|
||||||
|
with await request["redis"] as redis_conn:
|
||||||
|
# Set the value in cache to your new value
|
||||||
|
await redis_conn.set(user_id, json.dumps(request.json), expire=1800)
|
||||||
|
logger.info("Successfully pushed state to cache")
|
||||||
|
return sanic.response.HTTPResponse({"msg": "Successfully pushed state to cache",
|
||||||
|
"success": True,
|
||||||
|
"status": 200,
|
||||||
|
"finished_at": datetime.now().isoformat()})
|
||||||
|
except aioredis.ProtocolError:
|
||||||
|
logger.critical("Unable to connect to state cache")
|
||||||
|
return sanic.response.HTTPResponse({"msg": "Internal Server Error",
|
||||||
|
"status": 500,
|
||||||
|
"success": False,
|
||||||
|
"finished_at": datetime.now().isoformat()}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
def configure():
|
||||||
|
# Setup environment variables
|
||||||
|
env_vars = [os.environ.get(v, None) for v in ENV_VARS]
|
||||||
|
if not all(env_vars):
|
||||||
|
# Send back environment variables that were not set
|
||||||
|
return False, ", ".join([ENV_VARS[i] for i, flag in env_vars if not flag])
|
||||||
|
else:
|
||||||
|
# Add all the env vars to our app config
|
||||||
|
app.config.update({k: v for k, v in zip(ENV_VARS, env_vars)})
|
||||||
|
setup_logging()
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
logging_format = "[%(asctime)s] %(process)d-%(levelname)s "
|
||||||
|
logging_format += "%(module)s::%(funcName)s():l%(lineno)d: "
|
||||||
|
logging_format += "%(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=app.config.APP_LOGFILE,
|
||||||
|
format=logging_format,
|
||||||
|
level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
def main(result, missing):
|
||||||
|
if result:
|
||||||
|
try:
|
||||||
|
app.run(host="0.0.0.0", port=8080, debug=True)
|
||||||
|
except:
|
||||||
|
logging.critical("User killed server. Closing")
|
||||||
|
else:
|
||||||
|
logging.critical("Unable to start. Missing environment variables [{0}]".format(missing))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
result, missing = configure()
|
||||||
|
logger = logging.getLogger()
|
||||||
|
main(result, missing)
|
|
@ -1,18 +1,10 @@
|
||||||
aiocache
|
|
||||||
aiofiles
|
aiofiles
|
||||||
aiohttp
|
aiohttp==1.3.5
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
bottle
|
|
||||||
coverage
|
coverage
|
||||||
falcon
|
|
||||||
gunicorn
|
|
||||||
httptools
|
httptools
|
||||||
kyoukai
|
flake8
|
||||||
pytest
|
pytest
|
||||||
recommonmark
|
|
||||||
sphinx
|
|
||||||
sphinx_rtd_theme
|
|
||||||
tornado
|
|
||||||
tox
|
tox
|
||||||
ujson
|
ujson
|
||||||
uvloop
|
uvloop
|
||||||
|
|
28
sanic/app.py
28
sanic/app.py
|
@ -25,7 +25,8 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
|
|
||||||
def __init__(self, name=None, router=None, error_handler=None):
|
def __init__(self, name=None, router=None, error_handler=None,
|
||||||
|
load_env=True):
|
||||||
# Only set up a default log handler if the
|
# Only set up a default log handler if the
|
||||||
# end-user application didn't set anything up.
|
# end-user application didn't set anything up.
|
||||||
if not logging.root.handlers and log.level == logging.NOTSET:
|
if not logging.root.handlers and log.level == logging.NOTSET:
|
||||||
|
@ -44,7 +45,7 @@ class Sanic:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.router = router or Router()
|
self.router = router or Router()
|
||||||
self.error_handler = error_handler or ErrorHandler()
|
self.error_handler = error_handler or ErrorHandler()
|
||||||
self.config = Config()
|
self.config = Config(load_env=load_env)
|
||||||
self.request_middleware = deque()
|
self.request_middleware = deque()
|
||||||
self.response_middleware = deque()
|
self.response_middleware = deque()
|
||||||
self.blueprints = {}
|
self.blueprints = {}
|
||||||
|
@ -554,19 +555,24 @@ class Sanic:
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = (WebSocketProtocol if self.websocket_enabled
|
protocol = (WebSocketProtocol if self.websocket_enabled
|
||||||
else HttpProtocol)
|
else HttpProtocol)
|
||||||
|
if stop_event is not None:
|
||||||
|
if debug:
|
||||||
|
warnings.simplefilter('default')
|
||||||
|
warnings.warn("stop_event will be removed from future versions.",
|
||||||
|
DeprecationWarning)
|
||||||
server_settings = self._helper(
|
server_settings = self._helper(
|
||||||
host=host, port=port, debug=debug, before_start=before_start,
|
host=host, port=port, debug=debug, before_start=before_start,
|
||||||
after_start=after_start, before_stop=before_stop,
|
after_start=after_start, before_stop=before_stop,
|
||||||
after_stop=after_stop, ssl=ssl, sock=sock, workers=workers,
|
after_stop=after_stop, ssl=ssl, sock=sock, workers=workers,
|
||||||
loop=loop, protocol=protocol, backlog=backlog,
|
loop=loop, protocol=protocol, backlog=backlog,
|
||||||
stop_event=stop_event, register_sys_signals=register_sys_signals)
|
register_sys_signals=register_sys_signals)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
if workers == 1:
|
if workers == 1:
|
||||||
serve(**server_settings)
|
serve(**server_settings)
|
||||||
else:
|
else:
|
||||||
serve_multiple(server_settings, workers, stop_event)
|
serve_multiple(server_settings, workers)
|
||||||
except:
|
except:
|
||||||
log.exception(
|
log.exception(
|
||||||
'Experienced exception while trying to serve')
|
'Experienced exception while trying to serve')
|
||||||
|
@ -595,13 +601,17 @@ class Sanic:
|
||||||
if protocol is None:
|
if protocol is None:
|
||||||
protocol = (WebSocketProtocol if self.websocket_enabled
|
protocol = (WebSocketProtocol if self.websocket_enabled
|
||||||
else HttpProtocol)
|
else HttpProtocol)
|
||||||
|
if stop_event is not None:
|
||||||
|
if debug:
|
||||||
|
warnings.simplefilter('default')
|
||||||
|
warnings.warn("stop_event will be removed from future versions.",
|
||||||
|
DeprecationWarning)
|
||||||
server_settings = self._helper(
|
server_settings = self._helper(
|
||||||
host=host, port=port, debug=debug, before_start=before_start,
|
host=host, port=port, debug=debug, before_start=before_start,
|
||||||
after_start=after_start, before_stop=before_stop,
|
after_start=after_start, before_stop=before_stop,
|
||||||
after_stop=after_stop, ssl=ssl, sock=sock,
|
after_stop=after_stop, ssl=ssl, sock=sock,
|
||||||
loop=loop or get_event_loop(), protocol=protocol,
|
loop=loop or get_event_loop(), protocol=protocol,
|
||||||
backlog=backlog, stop_event=stop_event,
|
backlog=backlog, run_async=True)
|
||||||
run_async=True)
|
|
||||||
|
|
||||||
return await serve(**server_settings)
|
return await serve(**server_settings)
|
||||||
|
|
||||||
|
@ -621,7 +631,11 @@ class Sanic:
|
||||||
context = create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
|
context = create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
|
||||||
context.load_cert_chain(cert, keyfile=key)
|
context.load_cert_chain(cert, keyfile=key)
|
||||||
ssl = context
|
ssl = context
|
||||||
|
if stop_event is not None:
|
||||||
|
if debug:
|
||||||
|
warnings.simplefilter('default')
|
||||||
|
warnings.warn("stop_event will be removed from future versions.",
|
||||||
|
DeprecationWarning)
|
||||||
if loop is not None:
|
if loop is not None:
|
||||||
if debug:
|
if debug:
|
||||||
warnings.simplefilter('default')
|
warnings.simplefilter('default')
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import os
|
import os
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
SANIC_PREFIX = 'SANIC_'
|
||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
class Config(dict):
|
||||||
def __init__(self, defaults=None):
|
def __init__(self, defaults=None, load_env=True):
|
||||||
super().__init__(defaults or {})
|
super().__init__(defaults or {})
|
||||||
self.LOGO = """
|
self.LOGO = """
|
||||||
▄▄▄▄▄
|
▄▄▄▄▄
|
||||||
|
@ -29,6 +31,9 @@ class Config(dict):
|
||||||
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
|
self.REQUEST_MAX_SIZE = 100000000 # 100 megababies
|
||||||
self.REQUEST_TIMEOUT = 60 # 60 seconds
|
self.REQUEST_TIMEOUT = 60 # 60 seconds
|
||||||
|
|
||||||
|
if load_env:
|
||||||
|
self.load_environment_vars()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
try:
|
try:
|
||||||
return self[attr]
|
return self[attr]
|
||||||
|
@ -90,3 +95,13 @@ class Config(dict):
|
||||||
for key in dir(obj):
|
for key in dir(obj):
|
||||||
if key.isupper():
|
if key.isupper():
|
||||||
self[key] = getattr(obj, key)
|
self[key] = getattr(obj, key)
|
||||||
|
|
||||||
|
def load_environment_vars(self):
|
||||||
|
for k, v in os.environ.items():
|
||||||
|
"""
|
||||||
|
Looks for any SANIC_ prefixed environment variables and applies
|
||||||
|
them to the configuration if present.
|
||||||
|
"""
|
||||||
|
if k.startswith(SANIC_PREFIX):
|
||||||
|
_, config_key = k.split(SANIC_PREFIX, 1)
|
||||||
|
self[config_key] = v
|
||||||
|
|
|
@ -132,8 +132,8 @@ class StreamingHTTPResponse(BaseHTTPResponse):
|
||||||
|
|
||||||
async def stream(
|
async def stream(
|
||||||
self, version="1.1", keep_alive=False, keep_alive_timeout=None):
|
self, version="1.1", keep_alive=False, keep_alive_timeout=None):
|
||||||
"""Streams headers, runs the `streaming_fn` callback that writes content
|
"""Streams headers, runs the `streaming_fn` callback that writes
|
||||||
to the response body, then finalizes the response body.
|
content to the response body, then finalizes the response body.
|
||||||
"""
|
"""
|
||||||
headers = self.get_headers(
|
headers = self.get_headers(
|
||||||
version, keep_alive=keep_alive,
|
version, keep_alive=keep_alive,
|
||||||
|
@ -331,7 +331,11 @@ def stream(
|
||||||
:param headers: Custom Headers.
|
:param headers: Custom Headers.
|
||||||
"""
|
"""
|
||||||
return StreamingHTTPResponse(
|
return StreamingHTTPResponse(
|
||||||
streaming_fn, headers=headers, content_type=content_type, status=status)
|
streaming_fn,
|
||||||
|
headers=headers,
|
||||||
|
content_type=content_type,
|
||||||
|
status=status
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def redirect(to, headers=None, status=302,
|
def redirect(to, headers=None, status=302,
|
||||||
|
|
|
@ -4,10 +4,13 @@ import traceback
|
||||||
import warnings
|
import warnings
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from multiprocessing import Process, Event
|
from multiprocessing import Process
|
||||||
from os import set_inheritable
|
from os import set_inheritable
|
||||||
from signal import SIGTERM, SIGINT
|
from signal import (
|
||||||
from signal import signal as signal_func
|
SIGTERM, SIGINT,
|
||||||
|
signal as signal_func,
|
||||||
|
Signals
|
||||||
|
)
|
||||||
from socket import socket, SOL_SOCKET, SO_REUSEADDR
|
from socket import socket, SOL_SOCKET, SO_REUSEADDR
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
@ -421,7 +424,7 @@ def serve(host, port, request_handler, error_handler, before_start=None,
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
|
|
||||||
def serve_multiple(server_settings, workers, stop_event=None):
|
def serve_multiple(server_settings, workers):
|
||||||
"""Start multiple server processes simultaneously. Stop on interrupt
|
"""Start multiple server processes simultaneously. Stop on interrupt
|
||||||
and terminate signals, and drain connections when complete.
|
and terminate signals, and drain connections when complete.
|
||||||
|
|
||||||
|
@ -448,11 +451,13 @@ def serve_multiple(server_settings, workers, stop_event=None):
|
||||||
server_settings['host'] = None
|
server_settings['host'] = None
|
||||||
server_settings['port'] = None
|
server_settings['port'] = None
|
||||||
|
|
||||||
if stop_event is None:
|
def sig_handler(signal, frame):
|
||||||
stop_event = Event()
|
log.info("Received signal {}. Shutting down.".format(
|
||||||
|
Signals(signal).name))
|
||||||
|
loop.close()
|
||||||
|
|
||||||
signal_func(SIGINT, lambda s, f: loop.close())
|
signal_func(SIGINT, lambda s, f: sig_handler(s, f))
|
||||||
signal_func(SIGTERM, lambda s, f: loop.close())
|
signal_func(SIGTERM, lambda s, f: sig_handler(s, f))
|
||||||
|
|
||||||
processes = []
|
processes = []
|
||||||
for _ in range(workers):
|
for _ in range(workers):
|
||||||
|
|
|
@ -16,6 +16,17 @@ def test_load_from_object():
|
||||||
assert app.config.CONFIG_VALUE == 'should be used'
|
assert app.config.CONFIG_VALUE == 'should be used'
|
||||||
assert 'not_for_config' not in app.config
|
assert 'not_for_config' not in app.config
|
||||||
|
|
||||||
|
def test_auto_load_env():
|
||||||
|
environ["SANIC_TEST_ANSWER"] = "42"
|
||||||
|
app = Sanic()
|
||||||
|
assert app.config.TEST_ANSWER == "42"
|
||||||
|
del environ["SANIC_TEST_ANSWER"]
|
||||||
|
|
||||||
|
def test_auto_load_env():
|
||||||
|
environ["SANIC_TEST_ANSWER"] = "42"
|
||||||
|
app = Sanic(load_env=False)
|
||||||
|
assert getattr(app.config, 'TEST_ANSWER', None) == None
|
||||||
|
del environ["SANIC_TEST_ANSWER"]
|
||||||
|
|
||||||
def test_load_from_file():
|
def test_load_from_file():
|
||||||
app = Sanic('test_load_from_file')
|
app = Sanic('test_load_from_file')
|
||||||
|
|
|
@ -2,48 +2,46 @@ from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.exceptions import PayloadTooLarge
|
from sanic.exceptions import PayloadTooLarge
|
||||||
|
|
||||||
data_received_app = Sanic('data_received')
|
|
||||||
data_received_app.config.REQUEST_MAX_SIZE = 1
|
|
||||||
data_received_default_app = Sanic('data_received_default')
|
|
||||||
data_received_default_app.config.REQUEST_MAX_SIZE = 1
|
|
||||||
on_header_default_app = Sanic('on_header')
|
|
||||||
on_header_default_app.config.REQUEST_MAX_SIZE = 500
|
|
||||||
|
|
||||||
|
|
||||||
@data_received_app.route('/1')
|
|
||||||
async def handler1(request):
|
|
||||||
return text('OK')
|
|
||||||
|
|
||||||
|
|
||||||
@data_received_app.exception(PayloadTooLarge)
|
|
||||||
def handler_exception(request, exception):
|
|
||||||
return text('Payload Too Large from error_handler.', 413)
|
|
||||||
|
|
||||||
|
|
||||||
def test_payload_too_large_from_error_handler():
|
def test_payload_too_large_from_error_handler():
|
||||||
|
data_received_app = Sanic('data_received')
|
||||||
|
data_received_app.config.REQUEST_MAX_SIZE = 1
|
||||||
|
|
||||||
|
@data_received_app.route('/1')
|
||||||
|
async def handler1(request):
|
||||||
|
return text('OK')
|
||||||
|
|
||||||
|
@data_received_app.exception(PayloadTooLarge)
|
||||||
|
def handler_exception(request, exception):
|
||||||
|
return text('Payload Too Large from error_handler.', 413)
|
||||||
|
|
||||||
response = data_received_app.test_client.get('/1', gather_request=False)
|
response = data_received_app.test_client.get('/1', gather_request=False)
|
||||||
assert response.status == 413
|
assert response.status == 413
|
||||||
assert response.text == 'Payload Too Large from error_handler.'
|
assert response.text == 'Payload Too Large from error_handler.'
|
||||||
|
|
||||||
|
|
||||||
@data_received_default_app.route('/1')
|
def test_payload_too_large_at_data_received_default():
|
||||||
async def handler2(request):
|
data_received_default_app = Sanic('data_received_default')
|
||||||
|
data_received_default_app.config.REQUEST_MAX_SIZE = 1
|
||||||
|
|
||||||
|
@data_received_default_app.route('/1')
|
||||||
|
async def handler2(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
|
||||||
def test_payload_too_large_at_data_received_default():
|
|
||||||
response = data_received_default_app.test_client.get(
|
response = data_received_default_app.test_client.get(
|
||||||
'/1', gather_request=False)
|
'/1', gather_request=False)
|
||||||
assert response.status == 413
|
assert response.status == 413
|
||||||
assert response.text == 'Error: Payload Too Large'
|
assert response.text == 'Error: Payload Too Large'
|
||||||
|
|
||||||
|
|
||||||
@on_header_default_app.route('/1')
|
def test_payload_too_large_at_on_header_default():
|
||||||
async def handler3(request):
|
on_header_default_app = Sanic('on_header')
|
||||||
|
on_header_default_app.config.REQUEST_MAX_SIZE = 500
|
||||||
|
|
||||||
|
@on_header_default_app.post('/1')
|
||||||
|
async def handler3(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
|
|
||||||
def test_payload_too_large_at_on_header_default():
|
|
||||||
data = 'a' * 1000
|
data = 'a' * 1000
|
||||||
response = on_header_default_app.test_client.post(
|
response = on_header_default_app.test_client.post(
|
||||||
'/1', gather_request=False, data=data)
|
'/1', gather_request=False, data=data)
|
||||||
|
|
|
@ -88,4 +88,7 @@ def test_chained_redirect(redirect_app):
|
||||||
assert request.url.endswith('/1')
|
assert request.url.endswith('/1')
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == 'OK'
|
assert response.text == 'OK'
|
||||||
|
try:
|
||||||
|
assert response.url.endswith('/3')
|
||||||
|
except AttributeError:
|
||||||
assert response.url.path.endswith('/3')
|
assert response.url.path.endswith('/3')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user