0%

Eingaben, die zu Code werden · Übung

Begrenze und normalisiere jedes Feld

validate_book lässt die richtigen Schlüssel durch. Über die darunter sagt es noch nichts. Jeder dieser drei Bodys kommt als Buch daraus zurück:

{"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. with Python, Kapitel 5, hat den ganzen Body begrenzt, auf 10.000 Bytes. Alle drei Bodys passen hinein. Nichts begrenzt ein Feld innerhalb eines Bodys, der hineinpasst.

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 TitelMessen, dann strippenStrippen, dann messen
200 LeerzeichenBesteht mit 200 Zeichen, und ein leerer Titel wird gespeichertAbgelehnt: Es bleibt nichts übrig
200 Buchstaben und fünf LeerzeichenAbgelehnt wegen seiner 205 ZeichenAngenommen: 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:

  1. Normalisieren. Bring den Wert in die eine Form, die du speichern willst.

  2. Prüfen, und zwar den normalisierten Wert.

  3. 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 mit verschiedener Länge, also unterscheiden ==, 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

FeldRegel
title, authorPflicht. Text von 1 bis 200 Zeichen, gezählt nach dem Normalisieren und Strippen
yearOptional. Ein int von 1 bis 2100, und kein bool
statusOptional. Einer von to-read, reading, done, genau so, wie er gesendet wurde
tagsOptional. Eine von höchstens 10 Tags. Jedes Tag ist Text von 1 bis 30 Zeichen, gezählt wie ein Titel. Kein Tag zweimal

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 aus, weil eine Liste nicht in einem Set nachgeschlagen werden kann. Ein 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:

  1. Schreib zuerst eine Hilfsfunktion, clean_text(value, name, limit). Sie löst ValueError aus, wenn value kein str ist. Sie normalisiert nach NFC und strippt. Sie löst ValueError aus, wenn das Ergebnis kürzer als 1 oder länger als limit Zeichen ist. Dann gibt sie das Ergebnis zurück. name ist für die Meldungen da, etwa f"{name} must be text".

  2. Erweitere validate_book. Behalte die Schlüsselprüfungen aus Lektion 1 oben. Baue dann das neue Feld für Feld nach der Tabelle: title und author müssen vorhanden sein und durch clean_text laufen; year, status und tags werden geprüft, wenn sie vorhanden sind. Jede Ablehnung ist ein ValueError, dessen Meldung das Feld nennt.

  3. Entferne in book_request_error den Aufruf von book_error. validate_book meldet jetzt einen fehlenden Titel. Du kannst auch die book_error löschen, weil sie sonst niemand aufruft.

Probier es dann im aus:

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 ist, die Schlüssel owner und id und jeder Schlüssel außerhalb von ALLOWED_FIELDS lösen aus, und das Ergebnis ist ein neues Dict. Prüfe dann jeden :

FeldRegelZurückgegeben als
title, authorPflicht. Ein str von 1 bis TEXT_LIMIT (200) Zeichen, gezählt nach unicodedata.normalize("NFC", value).strip()Der normalisierte, gestrippte Text
yearOptional. Ein int von FIRST_YEAR (1) bis LAST_YEAR (2100). True und False werden abgelehntDie Zahl
statusOptional. Ein str, der in STATUSES steht, verglichen so, wie er gesendet wurdeDer Text
tagsOptional. 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 abgelehntEine neue der normalisierten, gestrippten Tags

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 kurz.

Entferne in book_request_error den Aufruf von book_error. create_book und replace_book lassen ihre weiter über das laufen, was 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 muss 422 bekommen, nie 500. Nichts aus deinem wird bewertet.