Eingaben, die zu Code werden · Übung
Begrenze und normalisiere jedes Feld
validate_book lässt die richtigen Schlüssel durch. Über die
{"title": "(200 spaces)", "author": "A"}
{"title": "T", "author": "A", "year": true}
{"title": "T", "author": "A", "tags": ["tag", "tag", (500 of them)]}
Sende den zweiten oder dritten an deinen Server, und er antwortet mit 201: ein Buch, dessen Jahr true ist, und ein Buch mit 500 Tags. Nur der erste wird gestoppt, vom alten book_error, das leere Titel kennt und sonst wenig.
Diese Lektion gibt jedem Feld eine Regel, steckt alle Regeln in validate_book und schickt book_error in den Ruhestand.
Normalisieren, dann prüfen, dann das Geprüfte verwenden
Fang mit dem Titel an. Zwei Regeln liegen nahe: den Leerraum drumherum mit strip() entfernen und höchstens 200 Zeichen erlauben. Die Reihenfolge der beiden entscheidet, was gespeichert wird.
| Der gesendete Titel | Messen, dann strippen | Strippen, dann messen |
|---|---|---|
| 200 Leerzeichen | Besteht mit 200 Zeichen, und ein leerer Titel wird gespeichert | Abgelehnt: Es bleibt nichts übrig |
| 200 Buchstaben und fünf Leerzeichen | Abgelehnt wegen seiner 205 Zeichen | Angenommen: 200 Buchstaben |
Die mittlere Spalte macht beide Fälle falsch, weil sie einen Text gemessen und einen anderen gespeichert hat. Also hat die Regel drei Schritte, in dieser Reihenfolge:
Normalisieren. Bring den Wert in die eine Form, die du speichern willst.
Prüfen, und zwar den normalisierten Wert.
Das Geprüfte verwenden. Der normalisierte Wert kommt in das Buch. Den Wert, den der Client gesendet hat, sieht sich niemand mehr an.
Ein Validator prüft len(title) <= 200 und speichert dann title.strip(). Ein Client sendet 200 Leerzeichen. Was wird gespeichert?
Zwei Strings, die gleich aussehen
Strippen ist eine Art des Normalisierens. Text braucht noch eine zweite. Das Beispielbuch Café Society hat ein é im Titel, und Unicode kennt zwei Arten, diesen Buchstaben zu schreiben: als ein Zeichen oder als ein einfaches e, gefolgt von einem kombinierenden Akzent, einem Zeichen, das sich an den Buchstaben davor hängt. Auf dem Bildschirm sehen beide gleich aus. In diesem Block ist ́ der kombinierende Akzent:
import unicodedata
typed = "Café Society"
stored = "Café Society"
print(typed == stored, len(typed), len(stored))
print(unicodedata.normalize("NFC", typed) == stored)
Es gibt False 13 12 aus und dann True. Die beiden Titel sind verschiedene ==, in und len sie alle, und ein Client kann jeden der beiden senden.
unicodedata.normalize("NFC", text) gibt den Text in einer vereinbarten Form zurück, NFC genannt, in der ein Buchstabe und sein Akzent zu einem Zeichen verbunden sind. unicodedata gehört zur Standardbibliothek und wird oben in reading_server.py bereits importiert. Das ist der eine neue Aufruf dieser Lektion. Für jeden Textwert wird der erste der drei Schritte zu unicodedata.normalize("NFC", value).strip().
In diesem Code schreibt a den Akzent als eigenes kombinierendes Zeichen nach dem e, und b schreibt é als ein einzelnes Zeichen. Was gibt er aus?
import unicodedata
a = "Café"
b = "Café"
print(a == b, len(a), len(b), unicodedata.normalize("NFC", a) == b)
Eine Grenze für jedes Feld
| Feld | Regel |
|---|---|
title, author | Pflicht. Text von 1 bis 200 Zeichen, gezählt nach dem Normalisieren und Strippen |
year | Optional. Ein int von 1 bis 2100, und kein bool |
status | Optional. Einer von to-read, reading, done, genau so, wie er gesendet wurde |
tags | Optional. Eine |
Die Zahlen sind Konstanten weit oben in der Datei: TEXT_LIMIT, FIRST_YEAR, LAST_YEAR, MOST_TAGS und TAG_LIMIT, neben dem Set STATUSES. Zwei der Regeln haben in Python eine Falle.
Die erste ist das Jahr. JSONs true kommt in Python als True an, und isinstance(True, int) ist True: Ein bool zählt als int, und True + 1998 ist 1999. Ein Test nur auf int nimmt es an. Frag also zuerst nach bool: if isinstance(year, bool) or not isinstance(year, int). Dieselbe Zeile lehnt den Text "1999" und die Fließkommazahl 1999.0 ab.
Die zweite ist der Status. status not in STATUSES sieht vollständig aus. Aber ein Client kann eine Liste senden, und ["done"] in STATUSES löst TypeError läuft direkt an deinem except ValueError vorbei, und der Fehler des Clients wird zu einer 500. Prüfe zuerst isinstance(status, str). Der Status wird nicht gestrippt: Er ist ein Code aus einer festen Liste, kein Text, den ein Mensch frei eintippt.
Warum lehnt die Prüfung des Jahrs True ab, bevor sie isinstance(year, int) fragt?
Jedes Tag durchläuft dieselben drei Schritte wie ein Titel, und der Test auf Wiederholung vergleicht die normalisierten Tags. Also sind "x" und " x " dasselbe Tag zweimal, und genauso ein Wort, das in beiden Unicode-Formen gesendet wird.
Welcher Wert kommt in das Buch, nachdem ein Tag normalisiert und geprüft wurde?
Deine Aufgabe
In reading_server.py:
Schreib zuerst eine Hilfsfunktion,
clean_text(value, name, limit). Sie löstValueErroraus, wennvaluekeinstrist. Sie normalisiert nach NFC und strippt. Sie löstValueErroraus, wenn das Ergebnis kürzer als 1 oder länger alslimitZeichen ist. Dann gibt sie das Ergebnis zurück.nameist für die Meldungen da, etwaf"{name} must be text".Erweitere
validate_book. Behalte die Schlüsselprüfungen aus Lektion 1 oben. Baue dann das neueFeld für Feld nach der Tabelle: titleundauthormüssen vorhanden sein und durchclean_textlaufen;year,statusundtagswerden geprüft, wenn sie vorhanden sind. Jede Ablehnung ist einValueError, dessen Meldung das Feld nennt.Entferne in
book_request_errorden Aufruf vonbook_error.validate_bookmeldet jetzt einen fehlenden Titel. Du kannst auch diebook_errorlöschen, weil sie sonst niemand aufruft.
Probier es dann im
python -c "import reading_server; print(reading_server.validate_book({'title': ' Dune ', 'author': 'Frank Herbert', 'tags': [' sand ']}))"
Es gibt {'title': 'Dune', 'author': 'Frank Herbert', 'tags': ['sand']} aus: Das Buch, das gespeichert wird, ist das, das geprüft wurde.
Zwei Grenzen all dessen. NFC macht Text nur dort gleich, wo der Unterschied ein Buchstabe war, der mit seinem Akzent verbunden oder getrennt geschrieben wurde. Buchstaben aus verschiedenen Alphabeten, die gleich aussehen, bleiben verschieden. Eine stärkere Form, NFKC, fasst mehr zusammen und ändert dabei auch, was manche Texte bedeuten, also wählt ein echter Dienst mit Bedacht. Und ein Titel darf weiterhin jedes Zeichen enthalten, auch einen Zeilenumbruch mitten darin. Ein solcher Titel besteht jede Regel dieser Lektion.
Klicke dann auf Check my work. In der nächsten Lektion geht es darum, warum ein Titel, der bestanden hat, trotzdem Schaden anrichten kann.
Aufgabe
Gib jedem Feld eines Buchs eine Regel, in validate_book in reading_server.py.
Behalte, was Lektion 1 geschrieben hat: Ein Body, der kein owner und id und jeder Schlüssel außerhalb von ALLOWED_FIELDS lösen
| Feld | Regel | Zurückgegeben als |
|---|---|---|
title, author | Pflicht. Ein str von 1 bis TEXT_LIMIT (200) Zeichen, gezählt nach unicodedata.normalize("NFC", value).strip() | Der normalisierte, gestrippte Text |
year | Optional. Ein int von FIRST_YEAR (1) bis LAST_YEAR (2100). True und False werden abgelehnt | Die Zahl |
status | Optional. Ein str, der in STATUSES steht, verglichen so, wie er gesendet wurde | Der Text |
tags | Optional. Eine list von höchstens MOST_TAGS (10) Tags. Jedes Tag ist ein str von 1 bis TAG_LIMIT (30) Zeichen, gezählt wie ein Titel. Dasselbe Tag zweimal, verglichen nach dem Normalisieren und Strippen, wird abgelehnt | Eine neue |
Jede Ablehnung ist ein ValueError, dessen Meldung das Feld nennt: title, author, year, status oder das Wort tag. Die Formulierung ist dir überlassen. Eine Hilfsfunktion clean_text(value, name, limit), die die drei Schritte für einen Textwert erledigt, hält die
Entferne in book_request_error den Aufruf von book_error. create_book und replace_book lassen ihre validate_book zurückgibt.
Klicke dann auf Check my work. Es ruft validate_book() mit Bodys auf, die du noch nicht gesehen hast, und vergleicht jeden angenommenen Wert genau: Text mit Leerraum drumherum, 200 Buchstaben gefolgt von fünf Leerzeichen, Text, der mit einem kombinierenden Akzent getippt wurde, die Jahre 1 und 2100, jeder Status, eine leere Liste von Tags, zehn Tags und ein Tag aus 30 Zeichen. Diese müssen mit ValueError abgelehnt werden: 200 Leerzeichen, 201 Zeichen, ein leerer oder fehlender Titel oder Autor, eine Zahl als Titel; True, False, 0, 2101, -5, "1999" und 1999.0 als Jahr; ein Status in Großbuchstaben, ein leerer, eine Zahl und eine Liste; Tags, die Text oder ein Dict sind, elf Tags, ein Tag aus 31 Zeichen, ein leeres oder nur aus Leerraum bestehendes Tag, eine Liste oder eine Zahl als Tag und dasselbe Tag zweimal in zwei Formen. Danach ruft es handle() mit eigenen Benutzern und Tokens auf: Das gespeicherte Buch muss die normalisierten Werte enthalten, und ein Wert vom falschen