Krax! Los millennials inventaron el marco de Python

Prólogo



¡Hola, Habr! Este artículo está dedicado al análisis de los pros y los contras del próximo marco de Python, que se publicó hace aproximadamente una semana.



Entonces, una pequeña digresión lírica. Durante los eventos conocidos, cuando estábamos un poco aislados de nosotros mismos, teníamos un poco más de tiempo libre. Alguien llegó a la lista de literatura reservada para lectura, alguien comenzó a estudiar otro idioma extranjero, alguien siguió presionando en Dotan y no prestó atención a los cambios. Pero yo (lo siento, este artículo contendrá mucho "yo", y estoy un poco avergonzado) decidí e intenté hacer algo útil. Sin embargo, la utilidad es discutible. Las preguntas obvias que el lector probablemente tendrá en primer lugar:“Um, ¿marco de Python? ¿Otro? Disculpe, pero ¿por qué? ¡Después de todo, no somos JavaScript! "



En realidad, esto es exactamente lo que se discutirá en este artículo: ¿Es necesario? Si es necesario, ¿a quién? ¿Cuál es la diferencia con lo que ya está ahí? Cómo puede resultar atractivo y por qué, por ejemplo, se puede enterrar sin esperar el primer cumpleaños. El artículo no planea mucho código; se pueden encontrar ejemplos de cómo escribir una aplicación y usar partes individuales en la documentación (hay mucho más código allí;)). Este artículo es más bien una descripción general.



Quien lo necesita



Una respuesta algo egoísta a esta pregunta: en primer lugar, por supuesto, yo mismo. Tengo algo de experiencia en la construcción de aplicaciones web usando frameworks existentes y regularmente me sorprendo pensando: “Sí, todo está bien, pero si tan solo fuera así…. Y aquí hay un comercial ... "... La mayoría de nosotros, de una forma u otra, tarde o temprano nos encontramos con el hecho de que algunas cosas no nos gustan y nos gustaría (o incluso tenemos que) cambiarlas. Intenté armar lo que me gusta de las herramientas que he utilizado. Espero no estar solo en mis preferencias, y que haya personas a las que estas ideas les parezcan cercanas. La idea principal detrás de Crax es que no impone ningún estilo particular de desarrollo tanto como sea posible. Por ejemplo, no necesitamos espacios de nombres, no queremos dividir la lógica en aplicaciones, queremos implementar rápidamente dos rutas y generar solicitudes y respuestas. Ok, en este caso podemos crear una aplicación de un solo archivo y obtener lo que queremos. Pero la situación contraria también es posible, y esto tampoco será un problema. Lo segundo que aboga Crax es la simplicidad. Código mínimo y lectura mínima de documentación para comenzar.Si una persona que recién está comenzando a aprender Python planea trabajar con el marco, debería poder superar sin dolor el umbral de entrada.



Si observa la cantidad de líneas de código necesarias para aprobar todas las pruebas de

TechEmpower (más sobre eso a continuación), entonces Crax en una aplicación que consta de un archivo es más compacta que todos los demás participantes, y no había ningún propósito para "encoger" este archivo. Realmente no hay nada más que escribir. Resumiendo lo anterior, podemos decir que Crax es adecuado para una gama muy diferente de tareas y una gama muy amplia de programadores de diversos grados de formación.



¿Por qué no utilizar las herramientas existentes?



Por qué no? Si sabes exactamente qué herramienta usar, cuál es la más adecuada para tu tarea actual, además, has trabajado con esta herramienta y conoces todos los matices. Por supuesto, elegirá lo que sepa y se adapte. No existe (y nunca lo habrá) el objetivo de posicionar a Crax como el "asesino de% framework_name%" No habrá ningún tipo de agitación: "Lanzar urgentemente% framework_name%, reescribir todo en Crax e inmediatamente aumentar el miembro de la cantidad de ventas" . Nada como esto. Puede notar por sí mismo que tenía otro en su caja de herramientas hace una semana. Usarlo o no depende de usted. Sin embargo, ¿por qué vale la pena intentarlo?



Primero, es lo suficientemente rápido. Está escrito usando la interfaz ASGI (lea la especificación aquí) y es mucho más rápido que Flask o Django 1. *, 2. *. Pero Crax, por supuesto, no es el único marco de Python que utiliza ASGI, y las pruebas preliminares muestran que compite bien con otros marcos que utilizan esta tecnología. A modo de comparación, utilizamos las pruebas de calificación de rendimiento de TechEmpower . Desafortunadamente, Crax, al igual que otros marcos agregados en el medio de la ronda actual, solo pasará a la siguiente, y luego podrá ver los resultados en la edición gráfica. Sin embargo, después de cada solicitud de extracción, Travis ejecuta pruebas y puede ver las características comparativas de los marcos en el registro de Travis. Debajo del enlace hay una tela larga del registro de Travis para los marcos de Python con los nombres en orden alfabético de la A a la F Aquí... Puede intentar leer el registro y comparar Crax, por ejemplo, con apidaora, resultará bastante bueno. A continuación, en el gráfico, se muestra la situación actual en las pruebas de la Ronda de 19.







Por supuesto, podremos ver los resultados reales y los resultados reales solo en la próxima ronda, pero no obstante.



Sin embargo, como se mencionó anteriormente, no tenemos herramientas menos rápidas y ya probadas.

Lo mismo asincrónico, con soporte nativo para websockets y otros placeres.



Digamos Starlette o FastApi. Son marcos absolutamente increíbles con una gran comunidad interesada en desarrollar estos productos. Vale la pena señalar que Crax es más similar a Starlette o FastAPI en su ideología, y algunas ideas han sido robadas.espiado en Starlette (por ejemplo, Response Middleware). Sin embargo, hay una serie de cosas que le pueden gustar de Crax y le hacen pensar: "Quizá lo intente para el próximo proyecto".... Por ejemplo, un archivo de configuración. Por supuesto, Starlette también tiene la capacidad de crear un archivo de configuración, pero es algo complicado para un principiante y, al final, su esencia se reduce al hecho de que todas las variables de configuración finalmente se pasan al inicializador de la clase de aplicación de todos modos. Si recopila TODAS las variables posibles, por ejemplo, configurando el registrador, middleware, CORS, etc., resultará ser demasiado. En Crax, todas las variables se declaran en el archivo principal (de configuración) (como Django) y no es necesario pasarlas a ningún lado. Además, siempre se puede acceder a todas las variables declaradas en el archivo de configuración en tiempo de ejecución (tanto desde la aplicación en ejecución como desde el exterior, hola Django ).



from crax.utils import get_settings_variable
base_url = get_settings_variable('BASE_URL')


Parecería una ventaja dudosa, sin embargo, cuando el archivo de configuración comienza a crecer con variables y configuraciones, y nos gustaría tener acceso a ellas, esto se vuelve importante.



El siguiente detalle importante del que me gustaría hablar es la organización de la estructura de la aplicación. Cuando tiene un proyecto pequeño, toda la lógica del cual se puede colocar en un archivo, esto es una cosa. Pero cuando escribe algo más global, es posible que desee separar vistas, modelos, descripciones de rutas, etc., de acuerdo con su lógica. En este contexto, vienen a la mente grandes proyectos de Flask o aplicaciones de Django. Crax habla de espacios de nombres en este sentido. Inicialmente, su aplicación está destinada a ser



un conjunto de paquetes de Python que se incluyen en el archivo principal del proyecto. Por cierto, los espacios de nombres (sus partes de la aplicación) se pueden anidar de forma recursiva (hola, Flask) y los nombres de los archivos que contienen no importan. ¿Por qué hacer eso? ¿Y qué nos aporta?



Primero, enrutamiento. Los espacios de nombres crearán uri basados ​​en la ubicación del espacio de nombres automáticamente (pero esto, por supuesto, se puede controlar). Por ejemplo:



from crax.urls import Route, Url, include

url_list = [
    Route(Url('/'), Home),
    Route(Url('/guest_book'), guest_view_coroutine),
    include('second_app.urls'),
    include('second_app.nested.urls'),
    include('third_app.urls')
]


Reemplace los puntos con barras y obtendrá el uri en su espacio de nombres (por supuesto, agregando un controlador final). Como ya hemos mencionado el enrutamiento, nos detendremos en él con más detalle.

Crax ofrece un par de posibilidades interesantes, además del trabajo habitual con expresiones regulares o el trabajo a través de la ruta de Django.



# URL defined as regex with one floating (optional) parameter
Url(r"/cabinet/(?P<username>\w{0,30})/(?:(?P<optional>\w+))?", type="re_path")
# General way to define URL
Url("/v1/customer/<customer_id>/<discount_name>/")


Sin embargo, es posible vincular varias URL a un controlador.



from crax.urls import Route, Url

class APIView(TemplateView):
    template = "index.html"

urls = [
    Route(
        urls=(
            Url("/"),
            Url("/v1/customers"),
            Url("/v1/discounts"),
            Url("/v1/cart"),
            Url("/v1/customer/<customer_id:int>"),
            Url("/v1/discount/<discount_id:int>/<optional:str>/"),
        ),
        handler=APIView)
    ]


Usted mismo puede pensar en dónde puede resultarle útil. Y también, hay un modo de operación del resolver en el modo "enmascarado". Por ejemplo, solo desea distribuir algún tipo de directorio con plantillas y no desea nada más. Quizás esta sea la documentación de Sphinx, o algo similar. Siempre puedes hacer esto:



import os
from crax.urls import Url, Route

class Docs(TemplateView):
    template = 'index.html'
    scope = os.listdir('docs/templates')

URL_PATTERNS = [
    Route(urls=(
        Url('/documentation', masquerade=True),
        handler=Docs),
]


Genial, ahora todas las plantillas que están en el directorio docs / templates se renderizarán con éxito usando un controlador. Un lector curioso dirá que aquí no se necesita Python en absoluto, y todo esto solo se puede hacer con la ayuda de Nginx condicional. Estoy absolutamente de acuerdo, exactamente hasta que sea necesario, por ejemplo, distribuir estas plantillas por rol o en algún lugar lateral, no se requiere lógica adicional.



Sin embargo, volvamos a nuestros espacios de nombres de carneros . Sería muy triste si los espacios de nombres (aunque anidados) fueran necesarios solo para organizar la resolución de URL. Por supuesto, el propósito de los espacios de nombres es un poco más amplio. Por ejemplo, trabajar con modelos de bases de datos y migraciones.



No hay ORM en Crax. Y no se supone. De todos modos, hasta que SQLAlchemy ofrezca soluciones asincrónicas. Sin embargo, se declara el trabajo con bases de datos (Postgres, MySQL y SQLite). Esto significa que es posible escribir sus propios modelos basados ​​en Crax BaseTable . Bajo el capó, esta es una envoltura muy delgada sobre SQLAlchemy Core Table , y puede hacer todo lo que Core Table puede hacer . Por lo que pueda ser necesario. Quizás para hacer algo similar.



from crax.database.model import BaseTable
import sqlalchemy as sa

class BaseModelOne(BaseTable):
    # This model just passes it's fields to the child
    # Will not be created in database because the abstract is defined
    parent_one = sa.Column(sa.String(length=50), nullable=False)

    class Meta:
        abstract = True

class BaseModelTwo(BaseTable):
    # Also passes it's fields to the child
    # Will be created in database
    parent_two = sa.Column(sa.String(length=50), nullable=False)

class MyModel(BaseModelOne, BaseModelTwo):
    name = sa.Column(sa.String(length=50), nullable=False)

print([y.name for x in MyModel.metadata.sorted_tables for y in x._columns])
# Let's check our fields ['name', 'id', 'parent_one', 'parent_two']


Y para poder trabajar con migraciones. Las migraciones de Crax son un poco de código sobre SQLAlchemy Alembic. Dado que estamos hablando de espacios de nombres y separación de lógica, entonces,

obviamente, nos gustaría almacenar las migraciones en el mismo paquete que la otra lógica de este espacio de nombres. Así es como funcionan las migraciones de Crax. Todas las migraciones se distribuirán de acuerdo con su espacio de nombres, y si este espacio de nombres implica trabajar con diferentes bases de datos, entonces dentro del directorio de migración habrá una división en directorios de las bases de datos correspondientes. Lo mismo se aplica a las migraciones fuera de línea: todos los archivos * .sql se dividirán según el espacio de nombres y la base de datos del modelo. No pintaré aquí sobre cómo escribir consultas, está en la documentación, solo diré que todavía está trabajando con SQLAlchemy Core.



Nuevamente, los espacios de nombres implican un almacenamiento conveniente de plantillas (se admiten la herencia y otras características de Jinja2 + un par de comodidades en forma de tokens CSRF listos para usar o generación de URL). Es decir, todas tus plantillas están estructuradas. Bueno, por supuesto, no estoy atrapado en un 2007 glorioso, entiendo que las plantillas (incluso si se renderizan de forma asincrónica) tendrán poca demanda en 2020. Y que, muy probablemente, te complace separar la lógica del frontend y el backend. Crax hace un excelente trabajo con esto, los resultados se pueden ver en Github.

aquíVueJs se utiliza como interfaz. Y dado que tenemos algún tipo de API, probablemente queramos hacer documentación interactiva. Crax puede crear documentación OpenAPI (Swagger) lista para usar en función de sus listas de rutas y las cadenas de documentos de su controlador. Todos los ejemplos, por supuesto, están en la documentación.



Antes de pasar a la parte más interesante de nuestra breve descripción general, vale la pena hablar un poco sobre qué baterías útiles ya vienen con el Crax.



Naturalmente, el modo de depuración es cuando el error y el seguimiento completo se pueden leer directamente en el navegador, en la página donde ocurrió la desgracia. El modo de depuración se puede desactivar y personalizar con fondos de pantalla aburridoscon sus manejadores. Imprima una vista única para cada código de estado http. Sin embargo, esto se hace de manera muy simple, como todo en Crax.



Registrador integrado con la capacidad de escribir simultáneamente en el archivo especificado y enviar registros a la consola (o hacer una cosa). Posibilidad de asignar su propio registrador en lugar del predeterminado. Soporte Sentry agregando dos líneas a la configuración (y, si es necesario, personalización).



Dos tipos de middleware preinstalados. El primero se procesa ANTES de que la solicitud procese la solicitud, y el segundo DESPUÉS.



Soporte integrado para encabezados CORS. Solo necesita declarar las reglas CORS en el archivo config.

Capacidad para definir métodos disponibles para cada controlador directamente en el sitio. Cada controlador trabajará con la lista de métodos HTTP que se especifique (+ HEAD y OPTIONS), o solo con GET, HEAD y OPTIONS.



Posibilidad de especificar que este controlador está disponible solo para usuarios autorizados, o solo para usuarios del grupo Administradores, o solo para miembros del rol de superusuario.

Existe autorización para sesiones firmadas por HMAC, para las cuales no es necesario ingresar a la base de datos y una serie de herramientas para crear y administrar usuarios. Puede habilitar el soporte de backend de autorización y obtener un usuario preestablecido y una serie de herramientas con las que trabajar. Sin embargo, como la mayoría de las herramientas de Crax, puede dejarlo, usarlo y escribir el suyo propio. No puede utilizar autorización, bases de datos, modelos, migraciones, vistas y escribir completamente sus propias soluciones personalizadas. No necesita hacer ningún esfuerzo para hacer esto, no lo habilitó, no lo es.



Hay varios tipos de respuesta y varios tipos de controladores basados ​​en clases que le ayudarán a escribir aplicaciones de forma más rápida y concisa. En este caso, también funcionarán los suyos, que no heredan de los integrados.



from crax.views import BaseView

# Written your own stuff
class CustomView:
    methods = ['GET', 'POST']
    def __init__(self, request):
        self.request = request
    async def __call__(self, scope, receive, send):
        if self.request.method == 'GET':
            response = TextResponse(self.request, "Hello world")
            await response(scope, receive, send)
        elif self.request.method == 'POST':
            response = JSONResponse(self.request, {"Hello": "world"})
            await response(scope, receive, send)

# Crax based stuff
class CustomView(BaseView):
    methods = ['GET', 'POST']
    async def get(self):
        response = TextResponse(self.request, "Hello world")
        return response

    async def post(self):
        response = JSONResponse(self.request, {"Hello": "world"})
        return response

class CustomersList(TemplateView):
    template = 'second.html'

    # No need return anything in case if it is TemplateView.
    # Template will be rendered with params
    async def get(self):
        self.context['params'] = self.request.params


Soporte de protección CSRF. Generando tokens, verificando la presencia de un token en el cuerpo de la solicitud,

deshabilitando la verificación para manejadores específicos.



Soporte para protección ClickJacking (Frame, iframe, incrustación ... políticas de renderizado)



Soporte para verificar el tamaño de cuerpo máximo permitido de una solicitud ANTES de que la aplicación comience a procesarla.



Soporte de websocket nativo. Tomemos un ejemplo de la documentación y escribamos una aplicación simple que pueda enviar mensajes websocket por difusión, por grupo de usuarios o mensajes a un usuario específico. Supongamos que tenemos grupos de "niños" y "niñas" (es posible agregar un grupo de "padres"). Podemos escribir algo similar como ejemplo (por supuesto, este no es un código de producto).



#app.py

import asyncio
import json
import os
from base64 import b64decode
from functools import reduce

from crax.auth import login
from crax.auth.authentication import create_session_signer
from crax.auth.models import Group, UserGroup
from crax.response_types import JSONResponse
from crax.urls import Route, Url
from crax.views import TemplateView, WsView
from sqlalchemy import and_, select
from websockets import ConnectionClosedOK

BASE_URL = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = "SuperSecret"
MIDDLEWARE = [
    "crax.auth.middleware.AuthMiddleware",
    "crax.auth.middleware.SessionMiddleware",
]

APPLICATIONS = ["ws_app"]
CLIENTS = {'boys': [], 'girls': []}


class Home(TemplateView):
    template = "index.html"
    login_required = True


class Login(TemplateView):
    template = "login.html"
    methods = ["GET", "POST"]

    async def post(self):
        credentials = json.loads(self.request.post)
        try:
            await login(self.request, **credentials)
            if hasattr(self.request.user, "first_name"):
                context = {'success': f"Welcome back, {self.request.user.username}"}
                status_code = 200
            else:
                context = {'error': f"User or password wrong"}
                status_code = 401
        except Exception as e:
            context = {'error': str(e)}
            status_code = 500
        response = JSONResponse(self.request, context)
        response.status_code = status_code
        return response


class WebSocketsHome(WsView):

    def __init__(self, request):
        super(WebSocketsHome, self).__init__(request)
        self.group_name = None

    async def on_connect(self, scope, receive, send):
        # This coroutine will be called every time a client connects.
        # So at this point we can do some useful things when we find a new connection.

        await super(WebSocketsHome, self).on_connect(scope, receive, send)
        if self.request.user.username:
            cookies = self.request.cookies
            # In our example, we want to check a group and store the user in the desired location.

            query = select([Group.c.name]).where(
                and_(UserGroup.c.user_id == self.request.user.pk, Group.c.id == UserGroup.c.group_id)
            )
            group = await Group.query.fetch_one(query=query)
            self.group_name = group['name']

            # We also want to get the username from the user's session key for future access via direct messaging

            exists = any(x for x in CLIENTS[self.group_name] if cookies['session_id'] in list(x)[0])
            signer, max_age, _, _ = create_session_signer()
            session_cookie = b64decode(cookies['session_id'])
            user = signer.unsign(session_cookie, max_age=max_age)
            user = user.decode("utf-8")
            username = user.split(":")[0]
            val = {f"{cookies['session_id']}:{cookies['ws_secret']}:{username}": receive.__self__}

            # Since we have all the information we need, we can save the user
            # The key will be session: ws_cookie: username and the value will be an instance of uvicorn.WebSocketProtocol

            if not exists:
                CLIENTS[self.group_name].append(val)
            else:
                # We should clean up our storage to prevent existence of the same clients.
                # For example due to page reloading
                [
                    CLIENTS[self.group_name].remove(x) for x in
                    CLIENTS[self.group_name] if cookies['session_id'] in list(x)[0]
                ]
                CLIENTS[self.group_name].append(val)

    async def on_disconnect(self, scope, receive, send):
        # This coroutine will be called every time a client disconnects.
        # So at this point we can do some useful things when we find a client disconnects.
        # We remove the client from the storage

        cookies = self.request.cookies
        if self.group_name:
            try:
                [
                    CLIENTS[self.group_name].remove(x) for x in
                    CLIENTS[self.group_name] if cookies['session_id'] in list(x)[0]
                ]
            except ValueError:
                pass

    async def on_receive(self, scope, receive, send):
        # This coroutine will be called every time we receive a new incoming websocket message.
        # Check the type of message received and send a response according to the message type.

        if "text" in self.kwargs:
            message = json.loads(self.kwargs["text"])
            message_text = message["text"]
            clients = []
            if message["type"] == 'BroadCast':
                clients = reduce(lambda x, y: x + y, CLIENTS.values())

            elif message["type"] == 'Group':
                clients = CLIENTS[message['group']]

            elif message["type"] == 'Direct':
                username = message["user_name"]
                client_list = reduce(lambda x, y: x + y, CLIENTS.values())
                clients = [client for client in client_list if username.lower() in list(client)[0]]
            for client in clients:
                if isinstance(client, dict):
                    client = list(client.values())[0]
                    try:
                        await client.send(message_text)
                    except (ConnectionClosedOK, asyncio.streams.IncompleteReadError):
                        await client.close()
                        clients.remove(client)


URL_PATTERNS = [Route(Url("/"), Home), Route(Url("/", scheme="websocket"), WebSocketsHome), Route(Url("/login"), Login)]
DATABASES = {
        "default": {
            "driver": "sqlite",
            "name": f"/{BASE_URL}/ws_crax.sqlite",
        },
    }
app = Crax('ws_app.app')

if __name__ == "__main__":
    if sys.argv:
        from_shell(sys.argv, app.settings)




<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Crax Websockets</title>
    </head>
    <body>
        <div id="wsText"></div>
        <form>
            <input id="messageText"><br>
            <select id="targetGroup">
                <option>boys</option>
                <option>girls</option>
            </select>
            <select id="messageType">
                <option>BroadCast</option>
                <option>Group</option>
                <option>Direct</option>
            </select>
            <select id="userNames">
                <option>Greg</option>
                <option>Chuck</option>
                <option>Mike</option>
                <option>Amanda</option>
                <option>Lisa</option>
                <option>Anny</option>
            </select>
        </form>
        <a href="#" id="sendWs">Send Message</a>
        <script>
            var wsText = document.getElementById("wsText")
            var messageType = document.getElementById("messageType")
            var messageText = document.getElementById("messageText")
            var targetGroup = document.getElementById("targetGroup")
            var userName = document.getElementById("userNames")
            var sendButton = document.getElementById("sendWs")
            ws = new WebSocket("ws://127.0.0.1:8000")
            ws.onmessage = function(e){
                wsText.innerHTML+=e.data
            }

            sendButton.addEventListener("click", function (e) {
                e.preventDefault()
                var message = {type: messageType.value, text: messageText.value}
                var data
                if (messageText.value !== "") {
                    if (messageType.value === "BroadCast"){
                        // send broadcast message
                        data = message
                    }
                    else if (messageType.value === "Group"){
                        // send message to group
                        data = Object.assign(message, {group: targetGroup.value})
                    }
                    else if (messageType.value === "Direct"){
                        // send message to certain user
                        data = Object.assign(message, {user_name: userName.value})
                    }
                    ws.send(JSON.stringify(data))
                }
            })
        </script>
    </body>
    </html>


<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Crax Websockets</title>
</head>
<body>
    <form>
        <input id="username">
        <input id="password" type="password">
    </form>
    <div id="loginResults"></div>
    <a href="#" id="sendLogin">Login</a>

    <script>
        var loginButton = document.getElementById("sendLogin")
        var loginResults = document.getElementById("loginResults")
        var username = document.getElementById("username")
        var password = document.getElementById("password")
        loginButton.addEventListener("click", function (e) {
            e.preventDefault()
            if (username.value !== "" && password.value !== "") {
                var xhr = new XMLHttpRequest()
                xhr.overrideMimeType("application/json")
                xhr.open("POST", "/login")
                xhr.send(JSON.stringify({username: username.value, password: password.value}))
                xhr.onload = function () {
                    var result = JSON.parse(xhr.responseText)
                    if ("success" in result){
                        loginResults.innerHTML+="<h5 style='color: green'>"+result.success+ "</h5>"
                    }
                    else if ("error" in result) {
                        loginResults.innerHTML+="<h5 style='color: red'>"+result.error+ "</h5>"
                    }
                }
            }
        })
    </script>
</body>
</html>


El código completo se puede ver en la documentación de Crax.



Bueno, ha llegado el momento de lo más interesante en este artículo.



¿Por qué es innecesario?



Primero, como se mencionó anteriormente, hay varios marcos que hacen lo mismo y tienen una comunidad ya formada. Mientras que Crax es un bebé de una semana. El ejército de un solo hombre es casi una garantía de que tarde o temprano el proyecto será abandonado. Es triste, pero el hecho de que trabajar sobre la mesa, lanzando lanzamientos y actualizaciones solo para usted y Vasily de Syktyvkar, es mucho más largo que cuando la comunidad está trabajando en el proyecto. Mientras tanto, el proyecto no tiene una serie de características que sean imprescindibles en 2020. Por ejemplo: sin soporte JWT (JOSE). No hay soporte listo para usar para herramientas OAuth2. No es compatible con GraphQL. Está claro que puede escribir esto usted mismo para su proyecto, pero Starlette o FastAPI ya lo tienen. Solo tengo que escribir esto (sí, está en los planes). Habrá un poco sobre los planes en conclusión.



Los desarrolladores de Netflix y Microsoft escriben sobre FastAPI. Acerca de Crax escribe noname, no se sabe dónde apareció, y quién sabe dónde es capaz de exactamente pasado mañana en el abismo. No



van a llamar a un vaporizador por mi idiota nombre

Mi madre llora por las noches, porque dio a luz a un bicho raro ...

(c)


Esto es importante. Se llama reputación y ecosistema. Crax tampoco tiene. Sin estas cosas importantes, se garantiza que el proyecto irá al vertedero sin nacer nunca.



Vale la pena entenderlo. Lo que está escrito arriba no es un intento de mecanografiar clases ni el texto de una persona sin hogar en el tren. Esta es una evaluación sobria y una advertencia de que las "soluciones listas para producción" no son solo los resultados de la cobertura del código fuente mediante pruebas, es una evaluación general de la madurez de las tecnologías, el enfoque y las soluciones utilizadas en el proyecto.



Si recién está comenzando a familiarizarse con Python y está probando frameworks, está en peligro: lo más probable es que no encuentre respuestas a la pregunta sobre SO, quizás compañeros más experimentados lo ayudarán, quienes, desafortunadamente, pueden no estar allí.



Los objetivos



Lo primero que planeo hacer es, por supuesto, agregar algunas cosas imprescindibles como compatibilidad con JWT (JOSE), OAuth2 y GraphQL. Esto es lo que me facilitará el trabajo a mí y a las personas interesadas. Y este es, de hecho, el objetivo principal de Crax: facilitar un poco el trabajo de alguien. Quizás para entonces comenzará una nueva ronda en TechEmpower y los puntos de referencia serán más evidentes. Incluso es posible que después de eso haya algún interés en la comunidad.

Existe la idea de escribir un CMS basado en Crax.

Si no me equivoco (si me equivoco, corríjalo), todavía no tenemos ningún CMS asíncrono en Python en nuestro kit de herramientas. Podría cambiar de opinión y decidir escribir algún tipo de solución de comercio electrónico. Pero, obviamente, para evitar que Crax se ahogue antes de llegar a las boyas, es necesario hacer algo interesante en su base. Quizás los entusiastas estarán interesados ​​en esto. Los entusiastas son cuando es gratis. Porque aquí no hay dinero y, muy probablemente, no lo habrá. Crax es completamente gratis para todos y no recibí ni un centavo por este trabajo. Así, el desarrollo está previsto para "largas tardes de invierno" y, quizás, en el próximo año, nazca algo interesante.



Conclusión



Estaba pensando en qué grupo incluir este artículo (por cierto, esta es mi primera publicación sobre el recurso). Tal vez incluso valió la pena colocarlo bajo la etiqueta "Soy PR". Lo que me hizo cambiar de opinión: en primer lugar, el hecho de que no tiene ningún carácter publicitario.



No hay llamada "Muchachos, inscríbase urgentemente para solicitudes de extracción" . No hay idea de encontrar un patrocinador aquí. No hay ni idea de que te traje algo que nunca has visto (claro, visto). Puede abstraerse de la idea de que soy el autor de ambos artículos y de esta herramienta, y percibir lo que se ha escrito como una reseña. Y sí, esa es la mejor manera. Será un resultado excelente para mí si solo tiene en cuenta que lo es.

Esto, quizás, es todo.



“Entonces… es hora de tomar las cañas de pescar.

- ¿Por qué?

- La gorra roja de Harris asustó a todos los peces.

(c)


Código en la documentación de GitHub




All Articles