Prolog
Hallo Habr! Dieser Artikel widmet sich der Analyse der Vor- und Nachteile des nächsten Python-Frameworks, das vor etwa einer Woche veröffentlicht wurde.
Also ein kleiner lyrischer Exkurs. Während der bekannten Ereignisse, als wir ein wenig selbstisoliert waren, hatten wir etwas mehr Freizeit. Jemand kam auf die Liste der Literatur, die zum Lesen reserviert war, jemand begann eine andere Fremdsprache zu lernen, jemand drückte weiter auf Dotan und achtete nicht auf die Änderungen. Aber ich (sorry, dieser Artikel wird viel "Ich" enthalten und ich schäme mich ein wenig) habe mich entschieden und versucht, etwas Nützliches zu tun. Der Nutzen ist jedoch umstritten. Die offensichtlichen Fragen, die der Leser höchstwahrscheinlich an erster Stelle haben wird:„Ähm, Python-Framework? Noch eins? Entschuldigung, aber warum? Wir sind schließlich kein JavaScript! "
Genau das wird in diesem Artikel besprochen: Ist es notwendig? Wenn nötig, an wen? Was ist der Unterschied zu dem, was bereits da ist? Wie attraktiv es sein kann und warum es zum Beispiel begraben werden kann, ohne auf den ersten Geburtstag zu warten. Der Artikel plant nicht viel Code - Beispiele für das Schreiben einer Anwendung und die Verwendung einzelner Teile finden Sie in der Dokumentation (dort gibt es viel mehr Code;)). Dieser Artikel ist eher eine Übersicht.
Wer braucht das?
Eine etwas egoistische Antwort auf diese Frage - vor allem natürlich ich. Ich habe Erfahrung im Erstellen von Webanwendungen mit vorhandenen Frameworks und denke regelmäßig: „Ja, alles ist cool, aber wenn es so wäre…. Und hier ist ein Werbespot ... "... Die meisten von uns stoßen auf die eine oder andere Weise früher oder später auf die Tatsache, dass manche Dinge nicht mögen und wir sie gerne ändern würden (oder sogar müssen). Ich habe versucht, aus den von mir verwendeten Tools zusammenzustellen, was mir gefällt. Ich hoffe, dass ich mit meinen Vorlieben nicht allein bin und dass es Menschen gibt, die diese Ideen nahe finden. Die Hauptidee hinter Crax ist, dass es nicht so viel wie möglich einen bestimmten Entwicklungsstil auferlegt. Zum Beispiel brauchen wir keine Namespaces, wir wollen die Logik nicht in Anwendungen aufteilen, wir wollen schnell zwei Routen bereitstellen und Anforderungen und Antworten steuern. Ok, in diesem Fall können wir einfach eine einzelne Dateianwendung erstellen und bekommen, was wir wollen. Es ist aber auch die gegenteilige Situation möglich, und dies wird auch kein Problem sein. Das zweite, was Crax befürwortet, ist die Einfachheit. Ein Minimum an Code und ein Minimum an Dokumentation zum Starten.Wenn eine Person, die gerade erst anfängt, Python zu lernen, plant, mit dem Framework zu arbeiten, sollte sie in der Lage sein, die Eintrittsschwelle schmerzlos zu überwinden.
Wenn Sie sich die Anzahl der Codezeilen ansehen, die zum Bestehen aller
TechEmpower- Tests erforderlich sind (mehr dazu weiter unten), ist Crax in einer Anwendung, die aus einer Datei besteht, kompakter als alle anderen Teilnehmer, und es gab keinen Zweck, diese Datei zu "verkleinern". Es gibt einfach wirklich nichts mehr zu schreiben. Zusammenfassend können wir sagen, dass Crax für ein sehr unterschiedliches Aufgabenspektrum und ein sehr breites Spektrum von Programmierern mit unterschiedlichem Ausbildungsgrad geeignet ist.
Warum nicht vorhandene Tools verwenden?
Warum nicht? Wenn Sie genau wissen, welches Werkzeug Sie verwenden sollen, welches für Ihre aktuelle Aufgabe am besten geeignet ist, haben Sie außerdem mit diesem Werkzeug gearbeitet und kennen alle Nuancen. Natürlich werden Sie wählen, was Sie wissen und passen. Es gibt kein Ziel (und wird es auch nie tun), Crax als "% framework_name% Killer" zu positionieren . Es wird keinen Agitationstyp geben: "Wirf dringend% framework_name%, schreibe alles auf Crax um und erhöhe sofort spürbar
Erstens ist es schnell genug. Es wird über die ASGI-Schnittstelle geschrieben (lesen Sie die Spezifikation hier) und ist viel schneller als Flask oder Django 1. *, 2. *. Aber Crax ist natürlich nicht das einzige Python-Framework, das ASGI verwendet, und vorläufige Tests zeigen, dass es mit anderen Frameworks, die diese Technologie verwenden, gut konkurriert. Zum Vergleich haben wir TechEmpower Performance Rating- Tests verwendet . Leider wird Crax, wie andere Frameworks, die in der Mitte der aktuellen Runde hinzugefügt wurden, nur in die nächste aufgenommen, und dann können Sie die Ergebnisse in der grafischen Ausgabe sehen. Nach jeder Pull-Anforderung führt Travis jedoch Tests durch, und Sie können die Vergleichseigenschaften von Frameworks im Travis-Protokoll sehen. Unter dem Link befindet sich ein langer Fuß des Travis-Protokolls für Python-Frameworks mit Namen in alphabetischer Reihenfolge von A bis F. Hier... Sie können versuchen, das Protokoll zu lesen und Crax zu vergleichen, zum Beispiel mit Apidaora, es wird sich als ziemlich gut herausstellen. Unten in der Grafik ist der aktuelle Stand der Tests der Runde 19 dargestellt.
Natürlich werden wir die tatsächlichen Ergebnisse und tatsächlichen Ergebnisse erst in der nächsten Runde sehen können, aber dennoch.
Wir haben jedoch, wie oben erwähnt, nicht weniger schnelle und bereits bewährte Werkzeuge.
Das gleiche asynchrone, mit nativer Unterstützung für Websockets und andere Freuden.
Sagen wir Starlette oder FastApi. Sie sind absolut erstaunliche Frameworks mit einer großen Community, die an der Entwicklung dieser Produkte interessiert ist. Es ist erwähnenswert, dass Crax Starlette oder FastAPI in seiner Ideologie am ähnlichsten ist und einige Ideen
from crax.utils import get_settings_variable
base_url = get_settings_variable('BASE_URL')
Es scheint jedoch ein zweifelhafter Vorteil zu sein, wenn die Konfigurationsdatei mit Variablen und Einstellungen überwachsen wird und wir Zugriff darauf haben möchten. Dies wird wichtig.
Das nächste wichtige Detail, über das ich sprechen möchte, ist die Organisation der Anwendungsstruktur. Wenn Sie ein kleines Projekt haben, dessen gesamte Logik in einer Datei abgelegt werden kann, ist dies eine Sache. Wenn Sie jedoch etwas globaleres schreiben, möchten Sie möglicherweise Ansichten, Modelle, Routenbeschreibungen usw. nach ihrer Logik trennen. In diesem Zusammenhang fallen mir großartige Flask-Blaupausen oder Django-Anwendungen ein. Crax spricht in diesem Sinne von Namespaces. Zunächst soll Ihre Bewerbung sein
Eine Reihe von Python-Paketen, die in der Hauptprojektdatei enthalten sind. Übrigens können die Namespaces (Ihre Teile der Anwendung) rekursiv verschachtelt werden (Hallo Flask), und die Namen der darin enthaltenen Dateien spielen keine Rolle. Warum das tun? Und was gibt es uns?
Erstens Routing. Namespaces erstellen automatisch eine URL basierend auf der Position des Namespace (dies kann jedoch natürlich gesteuert werden). Zum Beispiel:
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')
]
Ersetzen Sie Punkte durch Schrägstriche und Sie erhalten den Uri in Ihren Namespace (natürlich durch Hinzufügen eines letzten Handlers). Da wir das Routing bereits erwähnt haben, werden wir näher darauf eingehen.
Crax bietet neben der üblichen Arbeit mit regulären Ausdrücken oder der Arbeit über den Django-Pfad einige interessante Möglichkeiten.
# 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>/")
Es ist jedoch möglich, mehrere URLs an einen Handler zu binden.
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)
]
Sie können sich selbst überlegen, wo es für Sie nützlich sein kann. Außerdem gibt es eine Betriebsart des Resolvers im "Maskierungsmodus". Zum Beispiel möchten Sie nur eine Art Verzeichnis mit Vorlagen verteilen und möchten nichts anderes. Vielleicht ist dies die Sphinx-Dokumentation oder ähnliches. Sie können dies immer tun:
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),
]
Großartig, jetzt werden alle Vorlagen, die sich im Verzeichnis docs / templates befinden, mit einem Handler erfolgreich gerendert. Ein neugieriger Leser wird sagen, dass hier überhaupt keine Python benötigt wird, und all dies kann nur mit Hilfe von bedingtem Nginx durchgeführt werden. Ich stimme absolut zu, genau bis es beispielsweise notwendig ist, diese Vorlagen nach Rollen oder irgendwo auf der Seite zu verteilen, ist keine zusätzliche Logik erforderlich.
Zurück zu unseren
Es gibt kein ORM in Crax. Und es wird nicht angenommen. Wie auch immer, bis SQLAlchemy asynchrone Lösungen anbietet. Die Arbeit mit Datenbanken (Postgres, MySQL und SQLite) ist jedoch deklariert. Dies bedeutet, dass es möglich ist, eigene Modelle basierend auf Crax BaseTable zu schreiben . Unter der Haube ist dies eine sehr dünne Hülle über SQLAlchemy Core Table , und sie kann alles, was Core Table kann . Für was es gebraucht werden kann. Vielleicht um etwas Ähnliches zu tun.
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']
Und um mit Migrationen arbeiten zu können. Crax-Migrationen sind ein bisschen Code über SQLAlchemy Alembic. Da es sich um Namespaces und die Trennung von Logik handelt,
möchten wir natürlich Migrationen im selben Paket wie die andere Logik dieses Namespace speichern. So funktionieren Crax-Migrationen. Alle Migrationen werden entsprechend ihrem Namespace verteilt. Wenn dieser Namespace die Arbeit mit verschiedenen Datenbanken impliziert, erfolgt innerhalb des Migrationsverzeichnisses eine Unterteilung in Verzeichnisse der entsprechenden Datenbanken. Gleiches gilt für Offline-Migrationen - alle * .sql-Dateien werden nach Namespace und Modelldatenbank aufgeteilt. Ich werde hier nicht über das Schreiben von Abfragen malen - es steht in der Dokumentation, ich werde nur sagen, dass Sie noch mit SQLAlchemy Core arbeiten.
Wiederum bedeuten Namespaces die bequeme Speicherung von Vorlagen (Vererbung und andere Jinja2-Funktionen werden unterstützt + einige Annehmlichkeiten in Form von vorgefertigten CSRF-Token oder URL-Generierung). Das heißt, alle Ihre Vorlagen sind strukturiert. Natürlich stecke ich nicht im glorreichen Jahr 2007 fest, ich verstehe, dass Vorlagen (auch wenn sie asynchron gerendert werden) im Jahr 2020 wenig gefragt sein werden. Und das freut Sie höchstwahrscheinlich, die Logik von Frontend und Backend zu trennen. Crax leistet hervorragende Arbeit, die Ergebnisse können auf Github eingesehen werden.
HierVueJs wird als Frontend verwendet. Und da wir eine Art API haben, möchten wir wahrscheinlich eine interaktive Dokumentation erstellen. Crax kann sofort OpenAPI (Swagger) -Dokumentation basierend auf Ihren Routenlisten und Ihren Handler-Dokumentzeichenfolgen erstellen. Alle Beispiele finden Sie natürlich in der Dokumentation.
Bevor wir zum interessantesten Teil unseres kurzen Rückblicks übergehen, sollten wir ein wenig darüber sprechen, welche nützlichen Batterien bereits im Crax enthalten sind.
Im Debug-Modus können der Fehler und die vollständige Ablaufverfolgung natürlich direkt im Browser auf der Seite gelesen werden, auf der das Unglück passiert ist. Der Debug-Modus kann mit
Eingebauter Logger mit der Fähigkeit, gleichzeitig in die angegebene Datei zu schreiben und Protokolle an die Konsole zu senden (oder eine Sache zu tun). Möglichkeit, anstelle des Standardloggers einen eigenen Logger zuzuweisen. Sentry-Unterstützung durch Hinzufügen von zwei Zeilen zur Konfiguration (und ggf. Anpassung).
Zwei Arten vorinstallierter Middleware. Die erste wird verarbeitet, BEVOR die Anforderung von der Anwendung verarbeitet wird, und die zweite NACH.
Integrierte Unterstützung für CORS-Header. Sie müssen nur CORS-Regeln in der Konfiguration deklarieren.
Möglichkeit, Methoden zu definieren, die für jeden Handler direkt vor Ort verfügbar sind. Jeder Handler arbeitet mit einer Liste der angegebenen HTTP-Methoden (+ HEAD und OPTIONS) oder nur mit GET, HEAD und OPTIONS.
Sie können angeben, dass dieser Handler nur autorisierten Benutzern oder nur Benutzern aus der Gruppe Administratoren oder nur Mitgliedern der Superuser-Rolle zur Verfügung steht.
Es gibt eine Berechtigung für von HMAC signierte Sitzungen, für die Sie nicht in die Datenbank gehen müssen, sowie eine Reihe von Tools zum Erstellen und Verwalten von Benutzern. Sie können die Unterstützung für das Autorisierungs-Backend aktivieren und einen voreingestellten Benutzer sowie eine Reihe von Tools zum Arbeiten bereitstellen. Wie bei den meisten Crax-Tools können Sie es jedoch weglassen, verwenden und Ihre eigenen schreiben. Sie können keine Berechtigungen, Datenbanken, Modelle, Migrationen, Ansichten verwenden und Ihre eigenen benutzerdefinierten Lösungen nicht vollständig schreiben. Sie müssen keine Anstrengungen unternehmen, um dies zu tun. Sie haben es nicht aktiviert - es ist nicht so.
Es gibt verschiedene Arten von Antworten und verschiedene Arten von klassenbasierten Handlern, mit denen Sie Anwendungen schneller und präziser schreiben können. In diesem Fall funktioniert auch Ihre eigene, die nicht von den integrierten erbt.
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
CSRF-Schutzunterstützung. Generieren von Token, Überprüfen des Vorhandenseins eines Tokens im Anforderungshauptteil,
Deaktivieren der Überprüfung für bestimmte Handler.
Unterstützung für den ClickJacking-Schutz (Frame-, Iframe-, Embed- ... Rendering-Richtlinien)
Unterstützung für die Überprüfung der maximal zulässigen Körpergröße einer Anforderung, BEVOR die Anwendung mit der Verarbeitung beginnt.
Native Websocket-Unterstützung. Nehmen wir ein Beispiel aus der Dokumentation und schreiben eine einfache Anwendung, die Websocket-Nachrichten per Broadcast, pro Benutzergruppe oder Nachrichten an einen bestimmten Benutzer senden kann. Angenommen, wir haben Gruppen "Jungen" und "Mädchen" (es ist möglich, eine Gruppe "Eltern" hinzuzufügen). Wir können für ein Beispiel etwas Ähnliches schreiben (dies ist natürlich kein Produktcode).
#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>
Der vollständige Code kann in der Crax-Dokumentation eingesehen werden.
Nun, es ist an der Zeit, das Interessanteste in diesem Artikel zu finden.
Warum ist es unnötig?
Erstens gibt es, wie oben erwähnt, mehrere Frameworks, die dasselbe tun und eine bereits gebildete Community haben. Während Crax ein Baby ist, das eine Woche alt ist. Die Ein-Mann-Armee ist fast eine Garantie dafür, dass das Projekt früher oder später eingestellt wird. Es ist traurig, aber die Tatsache, dass die Arbeit am Tisch, die Veröffentlichung von Veröffentlichungen und Updates nur für Sie und Vasily von Syktyvkar, viel länger ist als wenn die Community an dem Projekt arbeitet. In der Zwischenzeit verfügt das Projekt nicht über eine Reihe von Funktionen, die 2020 unbedingt benötigt werden. Zum Beispiel: keine JWT-Unterstützung (JOSE). Keine sofort einsatzbereite Unterstützung für OAuth2-Tools. Keine GraphQL-Unterstützung. Es ist klar, dass Sie dies selbst für Ihr Projekt schreiben können, aber Starlette oder FastAPI haben es bereits. Ich muss das nur schreiben (ja, es ist in den Plänen). Abschließend wird ein wenig über die Pläne berichtet.
Die Entwickler von Netflix und Microsoft schreiben über FastAPI. Über Crax schreibt noname, es ist nicht bekannt, wo es erschien, und wer weiß, wo genau der Tag nach morgen Abgrund fähig ist. Sie
werden keinen Dampfer bei meinem idiotischen Namen nennen.
Meine Mutter weint nachts, weil sie einen Freak geboren hat ...
(c)
Das ist wichtig. Es heißt Ruf und Ökosystem. Crax hat auch nicht. Ohne diese wichtigen Dinge wird das Projekt garantiert auf die Mülldeponie gebracht, ohne jemals geboren zu werden.
Es lohnt sich zu verstehen. Was oben geschrieben steht, ist kein Versuch, Klassen einzugeben, und nicht der Text eines Obdachlosen im Zug. Dies ist eine nüchterne Bewertung und eine Warnung, dass "produktionsbereite Lösungen" nicht nur das Ergebnis der Erfassung des Quellcodes durch Tests sind, sondern eine allgemeine Bewertung der Reife der im Projekt verwendeten Technologien, Ansätze und Lösungen.
Wenn Sie gerade erst mit Python vertraut sind und Frameworks ausprobieren, sind Sie in Gefahr: Höchstwahrscheinlich finden Sie keine Antworten auf die Frage zu SO. Vielleicht helfen Ihnen erfahrenere Kameraden, die leider nicht da sind.
Die Ziele
Das erste, was ich vorhabe, ist natürlich, einige wichtige Dinge wie JWT (JOSE), OAuth2 und GraphQL-Unterstützung hinzuzufügen. Dies erleichtert mir und interessierten Menschen die Arbeit. Und dies ist in der Tat das Hauptziel von Crax - jemandem die Arbeit ein wenig zu erleichtern. Vielleicht beginnt bis dahin eine neue Runde bei TechEmpower und die Benchmarks werden deutlicher. Es ist sogar möglich, dass danach ein gewisses Interesse an der Community besteht.
Es gibt eine Idee, ein CMS zu schreiben, das auf Crax basiert.
Wenn ich mich nicht irre (wenn ich mich irre - korrigieren Sie es), haben wir noch kein asynchrones CMS in Python in unserem Toolkit. Ich könnte meine Meinung ändern und beschließen, eine Art E-Commerce-Lösung zu schreiben. Aber um zu verhindern, dass Crax vor Erreichen der Bojen ertrinkt, muss natürlich etwas Interessantes auf seiner Basis getan werden. Vielleicht interessieren sich Enthusiasten dafür. Enthusiasten sind, wenn es kostenlos ist. Weil es hier kein Geld gibt und höchstwahrscheinlich auch nicht. Crax ist für alle völlig kostenlos und ich habe keinen Cent für diesen Job bekommen. So ist die Entwicklung für "lange Winterabende" geplant und vielleicht wird im kommenden Jahr etwas Interessantes geboren.
Fazit
Ich habe darüber nachgedacht, in welche Gruppe dieser Artikel aufgenommen werden soll (dies ist übrigens meine erste Veröffentlichung in der Ressource). Vielleicht hat es sich sogar gelohnt, es unter dem Tag "I'm PR" zu platzieren. Was mich dazu gebracht hat, meine Meinung zu ändern: Zuallererst die Tatsache, dass es überhaupt keinen Werbecharakter hat.
Es gibt keinen Anruf "Jungs, melden Sie sich dringend für Pull-Anfragen an" . Es gibt keine Idee, hier einen Sponsor zu finden. Es gibt nicht einmal eine Idee, dass ich dir etwas gebracht habe, das du noch nie gesehen hast (natürlich gesehen). Sie können von der Idee, dass ich der Autor beider Artikel und dieses Tools bin, abstrahieren und das, was geschrieben wurde, als Rezension wahrnehmen. Und ja, das ist der beste Weg. Es wird ein hervorragendes Ergebnis für mich sein, wenn Sie nur daran denken, dass es so ist.
Darauf vielleicht alles.
„Also… es ist Zeit, die Angelruten zu nehmen.
- Warum?
- Harris 'rote Mütze erschreckte alle Fische.
(c)
Code in der GitHub-
Dokumentation