make release script black compliant and tweak documentation with indexing and format

Signed-off-by: Harsha Narayana <harsha2k4@gmail.com>
This commit is contained in:
Harsha Narayana 2018-12-15 16:44:19 +05:30
parent b7702bc3e8
commit 2d82b8951f
No known key found for this signature in database
GPG Key ID: 8AF211CB60D4B28C
11 changed files with 387 additions and 272 deletions

View File

@ -7,24 +7,24 @@ Guides
:maxdepth: 2 :maxdepth: 2
sanic/getting_started sanic/getting_started
sanic/routing sanic/config
sanic/logging
sanic/request_data sanic/request_data
sanic/response sanic/response
sanic/cookies
sanic/routing
sanic/blueprints
sanic/static_files sanic/static_files
sanic/versioning
sanic/exceptions sanic/exceptions
sanic/middleware sanic/middleware
sanic/blueprints
sanic/websocket sanic/websocket
sanic/config
sanic/cookies
sanic/decorators sanic/decorators
sanic/streaming sanic/streaming
sanic/class_based_views sanic/class_based_views
sanic/custom_protocol sanic/custom_protocol
sanic/sockets sanic/sockets
sanic/ssl sanic/ssl
sanic/logging
sanic/versioning
sanic/debug_mode sanic/debug_mode
sanic/testing sanic/testing
sanic/deploying sanic/deploying

View File

@ -118,9 +118,9 @@ app = Sanic(__name__)
app.blueprint(api) app.blueprint(api)
``` ```
## Using blueprints ## Using Blueprints
Blueprints have much the same functionality as an application instance. Blueprints have almost the same functionality as an application instance.
### WebSocket routes ### WebSocket routes
@ -201,7 +201,7 @@ async def close_connection(app, loop):
Blueprints can be very useful for API versioning, where one blueprint may point Blueprints can be very useful for API versioning, where one blueprint may point
at `/v1/<routes>`, and another pointing at `/v2/<routes>`. at `/v1/<routes>`, and another pointing at `/v2/<routes>`.
When a blueprint is initialised, it can take an optional `url_prefix` argument, When a blueprint is initialised, it can take an optional `version` argument,
which will be prepended to all routes defined on the blueprint. This feature which will be prepended to all routes defined on the blueprint. This feature
can be used to implement our API versioning scheme. can be used to implement our API versioning scheme.
@ -210,8 +210,8 @@ can be used to implement our API versioning scheme.
from sanic.response import text from sanic.response import text
from sanic import Blueprint from sanic import Blueprint
blueprint_v1 = Blueprint('v1', url_prefix='/v1') blueprint_v1 = Blueprint('v1', url_prefix='/api', version="v1")
blueprint_v2 = Blueprint('v2', url_prefix='/v2') blueprint_v2 = Blueprint('v2', url_prefix='/api', version="v2")
@blueprint_v1.route('/') @blueprint_v1.route('/')
async def api_v1_root(request): async def api_v1_root(request):
@ -222,7 +222,7 @@ async def api_v2_root(request):
return text('Welcome to version 2 of our documentation') return text('Welcome to version 2 of our documentation')
``` ```
When we register our blueprints on the app, the routes `/v1` and `/v2` will now When we register our blueprints on the app, the routes `/v1/api` and `/v2/api` will now
point to the individual blueprints, which allows the creation of *sub-sites* point to the individual blueprints, which allows the creation of *sub-sites*
for each API version. for each API version.
@ -232,8 +232,8 @@ from sanic import Sanic
from blueprints import blueprint_v1, blueprint_v2 from blueprints import blueprint_v1, blueprint_v2
app = Sanic(__name__) app = Sanic(__name__)
app.blueprint(blueprint_v1, url_prefix='/v1') app.blueprint(blueprint_v1)
app.blueprint(blueprint_v2, url_prefix='/v2') app.blueprint(blueprint_v2)
app.run(host='0.0.0.0', port=8000, debug=True) app.run(host='0.0.0.0', port=8000, debug=True)
``` ```
@ -246,7 +246,7 @@ takes the format `<blueprint_name>.<handler_name>`. For example:
```python ```python
@blueprint_v1.route('/') @blueprint_v1.route('/')
async def root(request): async def root(request):
url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5' url = request.app.url_for('v1.post_handler', post_id=5) # --> '/v1/api/post/5'
return redirect(url) return redirect(url)

View File

@ -86,29 +86,52 @@ DB_USER = 'appuser'
Out of the box there are just a few predefined values which can be overwritten when creating the application. Out of the box there are just a few predefined values which can be overwritten when creating the application.
| Variable | Default | Description | | Variable | Default | Description |
| ------------------------- | --------- | ------------------------------------------------------ | | ------------------------- | --------- | --------------------------------------------------------- |
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) | | REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes) |
| REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size | | REQUEST_BUFFER_QUEUE_SIZE | 100 | Request streaming buffer queue size |
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) | | REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) | | RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
| KEEP_ALIVE | True | Disables keep-alive when False | | KEEP_ALIVE | True | Disables keep-alive when False |
| KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) | | KEEP_ALIVE_TIMEOUT | 5 | How long to hold a TCP connection open (sec) |
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long take to force close non-idle connection (sec) | | GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
| ACCESS_LOG | True | Disable or enable access log | | ACCESS_LOG | True | Disable or enable access log |
### The different Timeout variables: ### The different Timeout variables:
A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the `REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates a HTTP 408 response and sends that to the client. Adjust this value higher if your clients routinely pass very large request payloads or upload requests very slowly. #### `REQUEST_TIMEOUT`
A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT` value (in seconds), this is considered a Server Error so Sanic generates a HTTP 503 response and sets that to the client. Adjust this value higher if your application is likely to have long-running process that delay the generation of a response. A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the
Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds the
`REQUEST_TIMEOUT` value (in seconds), this is considered a Client Error so Sanic generates an `HTTP 408` response
and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads
or upload requests very slowly.
### What is Keep Alive? And what does the Keep Alive Timeout value do? #### `RESPONSE_TIMEOUT`
Keep-Alive is a HTTP feature indroduced in HTTP 1.1. When sending a HTTP request, the client (usually a web browser application) can set a Keep-Alive header to indicate for the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server. A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the
Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the `RESPONSE_TIMEOUT`
value (in seconds), this is considered a Server Error so Sanic generates an `HTTP 503` response and sends that to the
client. Set this parameter's value higher if your application is likely to have long-running process that delay the
generation of a response.
The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application, set it to `False` to cause all client connections to close immediately after a response is sent, regardless of the Keep-Alive header on the request. #### `KEEP_ALIVE_TIMEOUT`
The amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds, this is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless you know your clients are using a browser which supports TCP connections held open for that long. ##### What is Keep Alive? And what does the Keep Alive Timeout value do?
`Keep-Alive` is a HTTP feature introduced in `HTTP 1.1`. When sending a HTTP request, the client (usually a web browser application)
can set a `Keep-Alive` header to indicate the http server (Sanic) to not close the TCP connection after it has send the response.
This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient
network traffic for both the client and the server.
The `KEEP_ALIVE` config variable is set to `True` in Sanic by default. If you don't need this feature in your application,
set it to `False` to cause all client connections to close immediately after a response is sent, regardless of
the `Keep-Alive` header on the request.
The amount of time the server holds the TCP connection open is decided by the server itself.
In Sanic, that value is configured using the `KEEP_ALIVE_TIMEOUT` value. By default, it is set to 5 seconds.
This is the same default setting as the Apache HTTP server and is a good balance between allowing enough time for
the client to send a new request, and not holding open too many connections at once. Do not exceed 75 seconds unless
you know your clients are using a browser which supports TCP connections held open for that long.
For reference: For reference:
``` ```

View File

@ -1,72 +0,0 @@
# Custom Protocols
*Note: this is advanced usage, and most readers will not need such functionality.*
You can change the behavior of Sanic's protocol by specifying a custom
protocol, which should be a subclass
of
[asyncio.protocol](https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes).
This protocol can then be passed as the keyword argument `protocol` to the `sanic.run` method.
The constructor of the custom protocol class receives the following keyword
arguments from Sanic.
- `loop`: an `asyncio`-compatible event loop.
- `connections`: a `set` to store protocol objects. When Sanic receives
`SIGINT` or `SIGTERM`, it executes `protocol.close_if_idle` for all protocol
objects stored in this set.
- `signal`: a `sanic.server.Signal` object with the `stopped` attribute. When
Sanic receives `SIGINT` or `SIGTERM`, `signal.stopped` is assigned `True`.
- `request_handler`: a coroutine that takes a `sanic.request.Request` object
and a `response` callback as arguments.
- `error_handler`: a `sanic.exceptions.Handler` which is called when exceptions
are raised.
- `request_timeout`: the number of seconds before a request times out.
- `request_max_size`: an integer specifying the maximum size of a request, in bytes.
## Example
An error occurs in the default protocol if a handler function does not return
an `HTTPResponse` object.
By overriding the `write_response` protocol method, if a handler returns a
string it will be converted to an `HTTPResponse object`.
```python
from sanic import Sanic
from sanic.server import HttpProtocol
from sanic.response import text
app = Sanic(__name__)
class CustomHttpProtocol(HttpProtocol):
def __init__(self, *, loop, request_handler, error_handler,
signal, connections, request_timeout, request_max_size):
super().__init__(
loop=loop, request_handler=request_handler,
error_handler=error_handler, signal=signal,
connections=connections, request_timeout=request_timeout,
request_max_size=request_max_size)
def write_response(self, response):
if isinstance(response, str):
response = text(response)
self.transport.write(
response.output(self.request.version)
)
self.transport.close()
@app.route('/')
async def string(request):
return 'string'
@app.route('/1')
async def response(request):
return text('response')
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)
```

View File

@ -0,0 +1,76 @@
Custom Protocols
================
.. note::
This is advanced usage, and most readers will not need such functionality.
You can change the behavior of Sanic's protocol by specifying a custom
protocol, which should be a subclass
of `asyncio.protocol <https://docs.python.org/3/library/asyncio-protocol.html#protocol-classes>`_.
This protocol can then be passed as the keyword argument ``protocol`` to the ``sanic.run`` method.
The constructor of the custom protocol class receives the following keyword
arguments from Sanic.
- ``loop``: an ``asyncio``-compatible event loop.
- ``connections``: a ``set`` to store protocol objects. When Sanic receives
``SIGINT`` or ``SIGTERM``, it executes ``protocol.close_if_idle`` for all protocol
objects stored in this set.
- ``signal``: a ``sanic.server.Signal`` object with the ``stopped`` attribute. When
Sanic receives ``SIGINT`` or ``SIGTERM``, ``signal.stopped`` is assigned ``True``.
- ``request_handler``: a coroutine that takes a ``sanic.request.Request`` object
and a ``response`` callback as arguments.
- ``error_handler``: a ``sanic.exceptions.Handler`` which is called when exceptions
are raised.
- ``request_timeout``: the number of seconds before a request times out.
- ``request_max_size``: an integer specifying the maximum size of a request, in bytes.
Example
-------
An error occurs in the default protocol if a handler function does not return
an ``HTTPResponse`` object.
By overriding the ``write_response`` protocol method, if a handler returns a
string it will be converted to an ``HTTPResponse object``.
.. code:: python
from sanic import Sanic
from sanic.server import HttpProtocol
from sanic.response import text
app = Sanic(__name__)
class CustomHttpProtocol(HttpProtocol):
def __init__(self, *, loop, request_handler, error_handler,
signal, connections, request_timeout, request_max_size):
super().__init__(
loop=loop, request_handler=request_handler,
error_handler=error_handler, signal=signal,
connections=connections, request_timeout=request_timeout,
request_max_size=request_max_size)
def write_response(self, response):
if isinstance(response, str):
response = text(response)
self.transport.write(
response.output(self.request.version)
)
self.transport.close()
@app.route('/')
async def string(request):
return 'string'
@app.route('/1')
async def response(request):
return text('response')
app.run(host='0.0.0.0', port=8000, protocol=CustomHttpProtocol)

View File

@ -11,6 +11,7 @@ A list of Sanic extensions created by the community.
## Security ## Security
- [Sanic JWT](https://github.com/ahopkins/sanic-jwt): Authentication, JWT, and permission scoping for Sanic. - [Sanic JWT](https://github.com/ahopkins/sanic-jwt): Authentication, JWT, and permission scoping for Sanic.
- [Secure](https://github.com/cakinney/secure): Secure 🔒 is a lightweight package that adds optional security headers and cookie attributes for Python web frameworks.
- [Sessions](https://github.com/subyraman/sanic_session): Support for sessions. Allows using redis, memcache or an in memory store. - [Sessions](https://github.com/subyraman/sanic_session): Support for sessions. Allows using redis, memcache or an in memory store.
- [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors. - [CORS](https://github.com/ashleysommer/sanic-cors): A port of flask-cors.
- [Sanic-JWT-Extended](https://github.com/devArtoria/Sanic-JWT-Extended): Provides extended JWT support for - [Sanic-JWT-Extended](https://github.com/devArtoria/Sanic-JWT-Extended): Provides extended JWT support for

View File

@ -1,100 +0,0 @@
# Logging
Sanic allows you to do different types of logging (access log, error log) on the requests based on the [python3 logging API](https://docs.python.org/3/howto/logging.html). You should have some basic knowledge on python3 logging if you want to create a new configuration.
### Quick Start
A simple example using default settings would be like this:
```python
from sanic import Sanic
from sanic.log import logger
from sanic.response import text
app = Sanic('test')
@app.route('/')
async def test(request):
logger.info('Here is your log')
return text('Hello World!')
if __name__ == "__main__":
app.run(debug=True, access_log=True)
```
After the server is running, you can see some messages looks like:
```
[2018-11-06 21:16:53 +0800] [24622] [INFO] Goin' Fast @ http://127.0.0.1:8000
[2018-11-06 21:16:53 +0800] [24667] [INFO] Starting worker [24667]
```
You can send a request to server and it will print the log messages:
```
[2018-11-06 21:18:53 +0800] [25685] [INFO] Here is your log
[2018-11-06 21:18:53 +0800] - (sanic.access)[INFO][127.0.0.1:57038]: GET http://localhost:8000/ 200 12
```
To use your own logging config, simply use `logging.config.dictConfig`, or
pass `log_config` when you initialize `Sanic` app:
```python
app = Sanic('test', log_config=LOGGING_CONFIG)
```
And to close logging, simply assign access_log=False:
```python
if __name__ == "__main__":
app.run(access_log=False)
```
This would skip calling logging functions when handling requests.
And you could even do further in production to gain extra speed:
```python
if __name__ == "__main__":
# disable debug messages
app.run(debug=False, access_log=False)
```
### Configuration
By default, log_config parameter is set to use sanic.log.LOGGING_CONFIG_DEFAULTS dictionary for configuration.
There are three `loggers` used in sanic, and **must be defined if you want to create your own logging configuration**:
- sanic.root:<br>
Used to log internal messages.
- sanic.error:<br>
Used to log error logs.
- sanic.access:<br>
Used to log access logs.
#### Log format:
In addition to default parameters provided by python (asctime, levelname, message),
Sanic provides additional parameters for access logger with:
- host (str)<br>
request.ip
- request (str)<br>
request.method + " " + request.url
- status (int)<br>
response.status
- byte (int)<br>
len(response.body)
The default access log format is
```python
%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d
```

103
docs/sanic/logging.rst Normal file
View File

@ -0,0 +1,103 @@
Logging
=======
Sanic allows you to do different types of logging (access log, error
log) on the requests based on the `python3 logging API`_. You should
have some basic knowledge on python3 logging if you want to create a new
configuration.
Quick Start
~~~~~~~~~~~
A simple example using default settings would be like this:
.. code:: python
from sanic import Sanic
from sanic.log import logger
from sanic.response import text
app = Sanic('test')
@app.route('/')
async def test(request):
logger.info('Here is your log')
return text('Hello World!')
if __name__ == "__main__":
app.run(debug=True, access_log=True)
After the server is running, you can see some messages looks like:
::
[2018-11-06 21:16:53 +0800] [24622] [INFO] Goin' Fast @ http://127.0.0.1:8000
[2018-11-06 21:16:53 +0800] [24667] [INFO] Starting worker [24667]
You can send a request to server and it will print the log messages:
::
[2018-11-06 21:18:53 +0800] [25685] [INFO] Here is your log
[2018-11-06 21:18:53 +0800] - (sanic.access)[INFO][127.0.0.1:57038]: GET http://localhost:8000/ 200 12
To use your own logging config, simply use
``logging.config.dictConfig``, or pass ``log_config`` when you
initialize ``Sanic`` app:
.. code:: python
app = Sanic('test', log_config=LOGGING_CONFIG)
And to close logging, simply assign access_log=False:
.. code:: python
if __name__ == "__main__":
app.run(access_log=False)
This would skip calling logging functions when handling requests. And
you could even do further in production to gain extra speed:
.. code:: python
if __name__ == "__main__":
# disable debug messages
app.run(debug=False, access_log=False)
Configuration
~~~~~~~~~~~~~
By default, ``log_config`` parameter is set to use
``sanic.log.LOGGING_CONFIG_DEFAULTS`` dictionary for configuration.
There are three ``loggers`` used in sanic, and **must be defined if you
want to create your own logging configuration**:
================ ==============================
Logger Name Usecase
================ ==============================
``sanic.root`` Used to log internal messages.
``sanic.error`` Used to log error logs.
``sanic.access`` Used to log access logs.
================ ==============================
Log format:
^^^^^^^^^^^
In addition to default parameters provided by python (``asctime``,
``levelname``, ``message``), Sanic provides additional parameters for
access logger with:
===================== ========================================== ========
Log Context Parameter Parameter Value Datatype
===================== ========================================== ========
``host`` ``request.ip`` str
``request`` ``request.method`` + " " + ``request.url`` str
``status`` ``response.status`` int
``byte`` ``len(response.body)`` int
===================== ========================================== ========
The default access log format is ``%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: %(request)s %(message)s %(status)d %(byte)d``
.. _python3 logging API: https://docs.python.org/3/howto/logging.html

View File

@ -164,24 +164,24 @@ url = app.url_for('post_handler', post_id=5, arg_one='one', arg_two='two')
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two']) url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'])
# /posts/5?arg_one=one&arg_one=two # /posts/5?arg_one=one&arg_one=two
``` ```
- Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not support now and will be ignored). For example: - Also some special arguments (`_anchor`, `_external`, `_scheme`, `_method`, `_server`) passed to `url_for` will have special url building (`_method` is not supported now and will be ignored). For example:
```python ```python
url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor') url = app.url_for('post_handler', post_id=5, arg_one='one', _anchor='anchor')
# /posts/5?arg_one=one#anchor # /posts/5?arg_one=one#anchor
url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True) url = app.url_for('post_handler', post_id=5, arg_one='one', _external=True)
# //server/posts/5?arg_one=one # //server/posts/5?arg_one=one
# _external requires passed argument _server or SERVER_NAME in app.config or url will be same as no _external # _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True) url = app.url_for('post_handler', post_id=5, arg_one='one', _scheme='http', _external=True)
# http://server/posts/5?arg_one=one # http://server/posts/5?arg_one=one
# when specifying _scheme, _external must be True # when specifying _scheme, _external must be True
# you can pass all special arguments one time # you can pass all special arguments at once
url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888') url = app.url_for('post_handler', post_id=5, arg_one=['one', 'two'], arg_two=2, _anchor='anchor', _scheme='http', _external=True, _server='another_server:8888')
# http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor # http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor
``` ```
- All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be thrown. - All valid parameters must be passed to `url_for` to build a URL. If a parameter is not supplied, or if a parameter does not match the specified type, a `URLBuildError` will be raised.
## WebSocket routes ## WebSocket routes
@ -209,7 +209,7 @@ async def feed(request, ws):
app.add_websocket_route(my_websocket_handler, '/feed') app.add_websocket_route(my_websocket_handler, '/feed')
``` ```
Handlers for a WebSocket route are passed the request as first argument, and a Handlers to a WebSocket route are invoked with the request as first argument, and a
WebSocket protocol object as second argument. The protocol object has `send` WebSocket protocol object as second argument. The protocol object has `send`
and `recv` methods to send and receive data respectively. and `recv` methods to send and receive data respectively.
@ -243,7 +243,8 @@ app.blueprint(bp)
## User defined route name ## User defined route name
You can pass `name` to change the route name to avoid using the default name (`handler.__name__`). A custom route name can be used by passing a `name` argument while registering the route which will
override the default route name generated using the `handler.__name__` attribute.
```python ```python
@ -305,8 +306,8 @@ def handler(request):
## Build URL for static files ## Build URL for static files
You can use `url_for` for static file url building now. Sanic supports using `url_for` method to build static file urls. In case if the static url
If it's for file directly, `filename` can be ignored. is pointing to a directory, `filename` parameter to the `url_for` can be ignored. q
```python ```python

View File

@ -1,7 +1,7 @@
WebSocket WebSocket
========= =========
Sanic supports websockets, to setup a WebSocket: Sanic provides an easy to user abstraction on top of `websockets`. To setup a WebSocket:
.. code:: python .. code:: python
@ -35,7 +35,7 @@ decorator:
app.add_websocket_route(feed, '/feed') app.add_websocket_route(feed, '/feed')
Handlers for a WebSocket route are passed the request as first argument, and a Handlers for a WebSocket route is invoked with the request as first argument, and a
WebSocket protocol object as second argument. The protocol object has ``send`` WebSocket protocol object as second argument. The protocol object has ``send``
and ``recv`` methods to send and receive data respectively. and ``recv`` methods to send and receive data respectively.

View File

@ -14,13 +14,22 @@ from requests import patch
GIT_COMMANDS = { GIT_COMMANDS = {
"get_tag": ["git describe --tags --abbrev=0"], "get_tag": ["git describe --tags --abbrev=0"],
"commit_version_change": [ "commit_version_change": [
"git add . && git commit -m 'Bumping up version from {current_version} to {new_version}'"], "git add . && git commit -m 'Bumping up version from "
"create_new_tag": ["git tag -a {new_version} -m 'Bumping up version from {current_version} to {new_version}'"], "{current_version} to {new_version}'"
],
"create_new_tag": [
"git tag -a {new_version} -m 'Bumping up version from "
"{current_version} to {new_version}'"
],
"push_tag": ["git push origin {new_version}"], "push_tag": ["git push origin {new_version}"],
"get_change_log": ['git log --no-merges --pretty=format:"%h::: %cn::: %s" {current_version}..'] "get_change_log": [
'git log --no-merges --pretty=format:"%h::: %cn::: %s" '
"{current_version}.."
],
} }
RELASE_NOTE_TEMPLATE = """
RELEASE_NOTE_TEMPLATE = """
# {{ release_name }} - {% now 'utc', '%Y-%m-%d' %} # {{ release_name }} - {% now 'utc', '%Y-%m-%d' %}
To see the exhaustive list of pull requests included in this release see: To see the exhaustive list of pull requests included in this release see:
@ -37,23 +46,25 @@ https://github.com/huge-success/sanic/milestone/{{milestone}}?closed=1
{% endfor %} {% endfor %}
""" """
JINJA_RELASE_NOTE_TEMPLATE = Environment( JINJA_RELEASE_NOTE_TEMPLATE = Environment(
loader=BaseLoader, extensions=['jinja2_time.TimeExtension']).from_string(RELASE_NOTE_TEMPLATE) loader=BaseLoader, extensions=["jinja2_time.TimeExtension"]
).from_string(RELEASE_NOTE_TEMPLATE)
RELEASE_NOTE_UPDATE_URL = \ RELEASE_NOTE_UPDATE_URL = (
"https://api.github.com/repos/huge-success/sanic/releases/tags/{new_version}?access_token={token}" "https://api.github.com/repos/huge-success/sanic/releases/tags/"
"{new_version}?access_token={token}"
)
def _run_shell_command(command: list): def _run_shell_command(command: list):
try: try:
process = Popen(command, stderr=PIPE, stdout=PIPE, stdin=PIPE, process = Popen(
shell=True) command, stderr=PIPE, stdout=PIPE, stdin=PIPE, shell=True
)
output, error = process.communicate() output, error = process.communicate()
return_code = process.returncode return_code = process.returncode
return output.decode("utf-8"), error, return_code return output.decode("utf-8"), error, return_code
except: except:
import traceback
traceback.print_exc()
return None, None, -1 return None, None, -1
@ -66,7 +77,10 @@ def _fetch_current_version(config_file: str) -> str:
config_parser = RawConfigParser() config_parser = RawConfigParser()
with open(config_file) as cfg: with open(config_file) as cfg:
config_parser.read_file(cfg) config_parser.read_file(cfg)
return config_parser.get("version", "current_version") or _fetch_default_calendar_release_version() return (
config_parser.get("version", "current_version")
or _fetch_default_calendar_release_version()
)
else: else:
return _fetch_default_calendar_release_version() return _fetch_default_calendar_release_version()
@ -77,8 +91,11 @@ def _change_micro_version(current_version: str):
return ".".join(version_string) return ".".join(version_string)
def _get_new_version(config_file: str = "./setup.cfg", current_version: str = None, def _get_new_version(
micro_release: bool = False): config_file: str = "./setup.cfg",
current_version: str = None,
micro_release: bool = False,
):
if micro_release: if micro_release:
if current_version: if current_version:
return _change_micro_version(current_version) return _change_micro_version(current_version)
@ -100,16 +117,21 @@ def _get_current_tag(git_command_name="get_tag"):
return None return None
def _update_release_version_for_sanic(current_version, new_version, config_file): def _update_release_version_for_sanic(
current_version, new_version, config_file
):
config_parser = RawConfigParser() config_parser = RawConfigParser()
with open(config_file) as cfg: with open(config_file) as cfg:
config_parser.read_file(cfg) config_parser.read_file(cfg)
config_parser.set("version", "current_version", new_version) config_parser.set("version", "current_version", new_version)
version_file = config_parser.get("version", "file") version_file = config_parser.get("version", "file")
current_version_line = config_parser.get("version", "current_version_pattern").format( current_version_line = config_parser.get(
current_version=current_version) "version", "current_version_pattern"
new_version_line = config_parser.get("version", "new_version_pattern").format(new_version=new_version) ).format(current_version=current_version)
new_version_line = config_parser.get(
"version", "new_version_pattern"
).format(new_version=new_version)
with open(version_file) as init_file: with open(version_file) as init_file:
data = init_file.read() data = init_file.read()
@ -122,10 +144,16 @@ def _update_release_version_for_sanic(current_version, new_version, config_file)
config_parser.write(config) config_parser.write(config)
command = GIT_COMMANDS.get("commit_version_change") command = GIT_COMMANDS.get("commit_version_change")
command[0] = command[0].format(new_version=new_version, current_version=current_version) command[0] = command[0].format(
new_version=new_version, current_version=current_version
)
_, err, ret = _run_shell_command(command) _, err, ret = _run_shell_command(command)
if int(ret) != 0: if int(ret) != 0:
print("Failed to Commit Version upgrade changes to Sanic: {}".format(err.decode("utf-8"))) print(
"Failed to Commit Version upgrade changes to Sanic: {}".format(
err.decode("utf-8")
)
)
exit(1) exit(1)
@ -144,20 +172,25 @@ def _generate_change_log(current_version: str = None):
for line in str(output).split("\n"): for line in str(output).split("\n"):
commit, author, description = line.split(":::") commit, author, description = line.split(":::")
if 'GitHub' not in author: if "GitHub" not in author:
commit_details["authors"][author] = 1 commit_details["authors"][author] = 1
commit_details["commits"].append(" - ".join([commit, description])) commit_details["commits"].append(" - ".join([commit, description]))
return commit_details return commit_details
def _generate_markdown_document(milestone, release_name, current_version, release_version): def _generate_markdown_document(
global JINJA_RELASE_NOTE_TEMPLATE milestone, release_name, current_version, release_version
):
global JINJA_RELEASE_NOTE_TEMPLATE
release_name = release_name or release_version release_name = release_name or release_version
change_log = _generate_change_log(current_version=current_version) change_log = _generate_change_log(current_version=current_version)
return JINJA_RELASE_NOTE_TEMPLATE.render( return JINJA_RELEASE_NOTE_TEMPLATE.render(
release_name=release_name, milestone=milestone, changelogs=change_log["commits"], release_name=release_name,
authors=change_log["authors"].keys()) milestone=milestone,
changelogs=change_log["commits"],
authors=change_log["authors"].keys(),
)
def _tag_release(new_version, current_version, milestone, release_name, token): def _tag_release(new_version, current_version, milestone, release_name, token):
@ -165,25 +198,27 @@ def _tag_release(new_version, current_version, milestone, release_name, token):
global RELEASE_NOTE_UPDATE_URL global RELEASE_NOTE_UPDATE_URL
for command_name in ["create_new_tag", "push_tag"]: for command_name in ["create_new_tag", "push_tag"]:
command = GIT_COMMANDS.get(command_name) command = GIT_COMMANDS.get(command_name)
command[0] = command[0].format(new_version=new_version, current_version=current_version) command[0] = command[0].format(
new_version=new_version, current_version=current_version
)
out, error, ret = _run_shell_command(command=command) out, error, ret = _run_shell_command(command=command)
if int(ret) != 0: if int(ret) != 0:
print("Failed to execute the command: {}".format(command[0])) print("Failed to execute the command: {}".format(command[0]))
exit(1) exit(1)
change_log = _generate_markdown_document(milestone, release_name, current_version, new_version) change_log = _generate_markdown_document(
milestone, release_name, current_version, new_version
)
body = { body = {"name": release_name or new_version, "body": change_log}
"name": release_name or new_version,
"body": change_log
}
headers = { headers = {"content-type": "application/json"}
"content-type": "application/json"
}
response = patch(RELEASE_NOTE_UPDATE_URL.format(new_version=new_version, token=token), response = patch(
data=dumps(body), headers=headers) RELEASE_NOTE_UPDATE_URL.format(new_version=new_version, token=token),
data=dumps(body),
headers=headers,
)
response.raise_for_status() response.raise_for_status()
@ -191,31 +226,79 @@ def release(args: Namespace):
current_tag = _get_current_tag() current_tag = _get_current_tag()
current_version = _fetch_current_version(args.config) current_version = _fetch_current_version(args.config)
if current_tag and current_version not in current_tag: if current_tag and current_version not in current_tag:
print("Tag mismatch between what's in git and what was provided by --current-version. " print(
"Existing: {}, Give: {}".format(current_tag, current_version)) "Tag mismatch between what's in git and what was provided by "
"--current-version. Existing: {}, Give: {}".format(
current_tag, current_version
)
)
exit(1) exit(1)
new_version = args.release_version or _get_new_version(args.config, current_version, args.micro_release) new_version = args.release_version or _get_new_version(
_update_release_version_for_sanic(current_version=current_version, new_version=new_version, config_file=args.config) args.config, current_version, args.micro_release
_tag_release(current_version=current_version, new_version=new_version, )
milestone=args.milestone, release_name=args.release_name, token=args.token) _update_release_version_for_sanic(
current_version=current_version,
new_version=new_version,
config_file=args.config,
)
_tag_release(
current_version=current_version,
new_version=new_version,
milestone=args.milestone,
release_name=args.release_name,
token=args.token,
)
if __name__ == '__main__': if __name__ == "__main__":
cli = ArgumentParser(description="Sanic Release Manager") cli = ArgumentParser(description="Sanic Release Manager")
cli.add_argument("--release-version", "-r", help="New Version to use for Release", cli.add_argument(
"--release-version",
"-r",
help="New Version to use for Release",
default=_fetch_default_calendar_release_version(), default=_fetch_default_calendar_release_version(),
required=False) required=False,
cli.add_argument("--current-version", "-cv", help="Current Version to default in case if you don't want to " )
cli.add_argument(
"--current-version",
"-cv",
help="Current Version to default in case if you don't want to "
"use the version configuration files", "use the version configuration files",
default=None, required=False) default=None,
cli.add_argument("--config", "-c", help="Configuration file used for release", default="./setup.cfg", required=False,
required=False) )
cli.add_argument("--token", "-t", help="Git access token with necessary access to Huge Sanic Org", cli.add_argument(
required=True) "--config",
cli.add_argument("--milestone", "-ms", help="Git Release milestone information to include in relase note", "-c",
required=True) help="Configuration file used for release",
cli.add_argument("--release-name", "-n", help="Release Name to use if any", required=False) default="./setup.cfg",
cli.add_argument("--micro-release", "-m", help="Micro Release with patches only", required=False,
default=False, action='store_true', required=False) )
cli.add_argument(
"--token",
"-t",
help="Git access token with necessary access to Huge Sanic Org",
required=True,
)
cli.add_argument(
"--milestone",
"-ms",
help="Git Release milestone information to include in relase note",
required=True,
)
cli.add_argument(
"--release-name",
"-n",
help="Release Name to use if any",
required=False,
)
cli.add_argument(
"--micro-release",
"-m",
help="Micro Release with patches only",
default=False,
action="store_true",
required=False,
)
args = cli.parse_args() args = cli.parse_args()
release(args) release(args)