Cómo hacer que su código sea rápido y asincrónico con Python y Sanic

Hola todos. En este artículo hablaré sobre la construcción de proyectos asincrónicos simples con el marco Sanic.

Introducción

Sanices un servidor web Python de código abierto muy parecido a un matraz y un marco web con más de 10K estrellas que está escrito para ir rápido. Permite el uso de la async/awaitsintaxis agregada en Python 3.5 (leer más), lo que hace que su código sea rápido y no bloqueante.

Sanic tiene bastante buena documentación y es mantenido por la comunidad, para la comunidad.

El objetivo del proyecto es proporcionar una forma sencilla de poner en funcionamiento un servidor HTTP de alto rendimiento que sea fácil de construir, expandir y, en última instancia, escalar.

Requisitos

Antes de comenzar, instalemos algunos paquetes y asegurémonos de tener todo listo para el desarrollo de este proyecto.

Nota: el código fuente está disponible en mi repositorio de github.com. Para cada paso hay un compromiso correspondiente.

Prerrequisitos:

  • Python3.6 +
  • pipenv (puede usar cualquier otro instalador de paquetes)
  • PostgreSQL(para base de datos, también puede ser MySQL o SQLite)

Paquetes:

  • secure es un paquete ligero que agrega encabezados de seguridad opcionales y atributos de cookies para los marcos web de Python.
  • environs es una biblioteca de Python para analizar variables de entorno. Le permite almacenar la configuración separada de su código, según la metodología de la aplicación de doce factores.
  • sanic-envconfig es una extensión que le ayuda a incorporar la línea de comandos y las variables de entorno en su configuración de Sanic.
  • Database es un paquete de Python que le permite realizar consultas utilizando el poderoso lenguaje de expresión SQLAlchemy Core y brinda soporte para PostgreSQL, MySQL y SQLite.

Creemos un directorio vacío e inicialicemos uno vacío Pipfileallí.

pipenv  -- python python3.6

Instale todos los paquetes necesarios usando los comandos pipenv a continuación.

pipenv install sanic secure environs sanic-envconfig

Para base de datos:

pipenv install databases[postgresql]

Las opciones sonpostgresql, mysql, sqlite.

Estructura

Ahora creemos algunos archivos y carpetas donde escribiremos nuestro código real.

├── .env├── Pipfile├── Pipfile.lock├── setup.py└── project ├── __init__.py ├── __main__.py ├── main.py ├── middlewares.py ├── routes.py ├── settings.py └── tables.py

Usaremos el setup.pyarchivo para que la projectcarpeta esté disponible como paquete en nuestro código.

from setuptools import setupsetup( name="project",)

Instalando…

pipenv install -e .

En el .envarchivo, almacenaremos algunas variables globales como la URL de conexión de la base de datos.

__main__.pyse crea para hacer que nuestro projectpaquete sea ejecutable desde la línea de comandos.

pipenv run python -m project

Inicialización

Hagamos nuestra primera llamada en el archivo __main__.py .

from project.main import initinit()

Este es el comienzo de nuestra aplicación. Ahora necesitamos crear la initfunción dentro del archivo main.py.

from sanic import Sanicapp = Sanic(__name__)def init(): app.run(host='0.0.0.0', port=8000, debug=True)

Simplemente creando la aplicación a partir de la clase Sanic , podemos ejecutarla especificando host , puerto y el argumento de palabra clave de depuración opcional .

Corriendo…

pipenv run python -m project

Así es como debería verse una salida exitosa en su aplicación Sanic. Si abre //0.0.0.0:8000 en su navegador, verá

Error: URL solicitada / no encontrada

Todavía no hemos creado ninguna ruta , así que por ahora está bien. Agregaremos algunas rutas a continuación.

Configuraciones

Ahora podemos modificar el entorno y la configuración. Necesitamos agregar algunas variables en el archivo .env , leerlas y pasar a la configuración de la aplicación Sanic.

Archivo .env .

DEBUG=TrueHOST=0.0.0.0POST=8000

Configuración…

from sanic import Sanic
from environs import Envfrom project.settings import Settings
app = Sanic(__name__)
def init(): env = Env() env.read_env() app.config.from_object(Settings) app.run( host=app.config.HOST, port=app.config.PORT, debug=app.config.DEBUG, auto_reload=app.config.DEBUG, )

archivo settings.py .

from sanic_envconfig import EnvConfigclass Settings(EnvConfig): DEBUG: bool = True HOST: str = '0.0.0.0' PORT: int = 8000

Tenga en cuenta que agregué un argumento de recarga automática opcional que activará o desactivará el recargador automático.

Base de datos

Ahora es el momento de configurar una base de datos.

Una pequeña nota sobre el paquete de bases de datos antes de continuar:

el paquete de bases de datos usa asyncpg que es una biblioteca de interfaz asincrónica para PostgreSQL. Es bastante rapido. Vea los resultados a continuación.

Usaremos dos de los oyentes de Sanic donde especificaremos las operaciones de conexión y desconexión de la base de datos. Estos son los oyentes que vamos a utilizar:

  • after_server_start
  • after_server_stop

main.py file.

from sanic import Sanic
from databases import Database
from environs import Envfrom project.settings import Settings
app = Sanic(__name__)
def setup_database(): app.db = Database(app.config.DB_URL) @app.listener('after_server_start') async def connect_to_db(*args, **kwargs): await app.db.connect() @app.listener('after_server_stop') async def disconnect_from_db(*args, **kwargs): await app.db.disconnect()
def init(): env = Env() env.read_env() app.config.from_object(Settings)
 setup_database()
 app.run( host=app.config.HOST, port=app.config.PORT, debug=app.config.DEBUG, auto_reload=app.config.DEBUG, )

Once more thing. We need to specify DB_URL in project settings and environment.

.env file.

DEBUG=TrueHOST=0.0.0.0POST=8000DB_URL=postgresql://postgres:[email protected]/postgres

And settings.py file.

from sanic_envconfig import EnvConfigclass Settings(EnvConfig): DEBUG: bool = True HOST: str = '0.0.0.0' PORT: int = 8000 DB_URL: str = ''

Make sure that DB_URL is correct and your database is running. Now you can access to database using app.db. See more detailed information in the next section.

Tables

Now we have a connection to our database and we can try to do some SQL queries.

Let’s declare a table in tables.py file using SQLAlchemy.

import sqlalchemymetadata = sqlalchemy.MetaData()books = sqlalchemy.Table( 'books', metadata, sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True), sqlalchemy.Column('title', sqlalchemy.String(length=100)), sqlalchemy.Column('author', sqlalchemy.String(length=60)),)

Here I assume that you already have a migrated database with a books table in it. For creating database migrations, I recommend that you use Alembic which is a lightweight and easy-to-use tool that you can use with the SQLAlchemy Database Toolkit for Python.

Now we can use any SQLAlchemy core queries. Check out some examples below.

# Executing manyquery = books.insert()values = [ {"title": "No Highway", "author": "Nevil Shute"}, {"title": "The Daffodil", "author": "SkyH. E. Bates"},]await app.db.execute_many(query, values)# Fetching multiple rowsquery = books.select()rows = await app.db.fetch_all(query)# Fetch single rowquery = books.select()row = await app.db.fetch_one(query)

Routes

Now we need to setup routes. Let’s go to routes.py and add a new route for books.

from sanic.response import json
from project.tables import books
def setup_routes(app): @app.route("/books") async def book_list(request): query = books.select() rows = await request.app.db.fetch_all(query) return json({ 'books': [{row['title']: row['author']} for row in rows] })

Of course we need to call setup_routes in init to make it work.

from project.routes import setup_routes
app = Sanic(__name__)
def init(): ... app.config.from_object(Settings) setup_database() setup_routes(app) ...

Requesting…

$ curl localhost:8000/books{"books":[{"No Highway":"Nevil Shute"},{"The Daffodil":"SkyH. E. Bates"}]}

Middlewares

What about checking the response headers and seeing what we can add or fix there?

$ curl -I localhost:8000Connection: keep-aliveKeep-Alive: 5Content-Length: 32Content-Type: text/plain; charset=utf-8

As you can see we need some security improvements. There are some missing headers such as X-XSS-Protection, Strict-Transport-Securityso let’s take care of them using a combination of middlewares and secure packages.

middlewares.py file.

from secure import SecureHeaderssecure_headers = SecureHeaders()def setup_middlewares(app): @app.middleware('response') async def set_secure_headers(request, response): secure_headers.sanic(response)

Setting up middlewares in main.py file.

from project.middlewares import setup_middlewares
app = Sanic(__name__)
def init(): ... app.config.from_object(Settings) setup_database() setup_routes(app) setup_middlewares(app) ...

The result is:

$ curl -I localhost:8000/booksConnection: keep-aliveKeep-Alive: 5Strict-Transport-Security: max-age=63072000; includeSubdomainsX-Frame-Options: SAMEORIGINX-XSS-Protection: 1; mode=blockX-Content-Type-Options: nosniffReferrer-Policy: no-referrer, strict-origin-when-cross-originPragma: no-cacheExpires: 0Cache-control: no-cache, no-store, must-revalidate, max-age=0Content-Length: 32Content-Type: text/plain; charset=utf-8

As I promised at the beginning, there is a github repository for each section in this article. Hope this small tutorial helped you to get started with Sanic. There are still many unexplored features in the Sanic framework that you can find and check out in the documentation.

davitovmasyan/sanic-project

Goin' Fast and asynchronous with Python and Sanic! - davitovmasyan/sanic-projectgithub.com

If you have thoughts on this, be sure to leave a comment.

If you found this article helpful, give me some claps ?

Thanks for reading. Go Fast with Sanic and good luck!!!