0%

Invoer die code wordt · oefening

Begrens en normaliseer elk veld

validate_book laat de juiste sleutels door. Over de eronder zegt hij nog niets. Elk van deze drie bodies komt er als boek uit:

{"title": "(200 spaces)", "author": "A"}
{"title": "T", "author": "A", "year": true}
{"title": "T", "author": "A", "tags": ["tag", "tag", (500 of them)]}

Stuur de tweede of de derde naar je server en hij antwoordt 201: een boek met als jaar true, en een boek met 500 tags. Alleen de eerste wordt tegengehouden, door de oude book_error, die van lege titels weet en van weinig anders. with Python, hoofdstuk 5, zette een limiet op de hele body: 10.000 bytes. Alle drie de bodies passen erin. Niets begrenst een veld binnen een body die past.

Deze les geeft elk veld een regel, zet alle regels in validate_book, en schaft book_error af.

Normaliseer, controleer, en gebruik wat je controleerde

Begin met de titel. Er komen twee regels op: haal de witruimte eromheen weg met strip(), en sta hoogstens 200 tekens toe. De volgorde van die twee bepaalt wat er wordt opgeslagen.

De verstuurde titelEerst meten, dan strippenEerst strippen, dan meten
200 spatiesKomt erdoor met 200 tekens, en er wordt een lege titel opgeslagenGeweigerd: er blijft niets over
200 letters en vijf spatiesGeweigerd om zijn 205 tekensGeaccepteerd: 200 letters

De middelste kolom heeft beide gevallen fout, omdat hij de ene tekst mat en een andere opsloeg. De regel heeft dus drie stappen, in deze volgorde:

  1. Normaliseer. Breng de waarde in de ene vorm die je wilt opslaan.

  2. Controleer de genormaliseerde waarde.

  3. Gebruik wat je controleerde. De genormaliseerde waarde gaat het boek in. Naar de waarde die de client stuurde, wordt niet meer gekeken.

Een validator controleert len(title) <= 200 en slaat daarna title.strip() op. Een client stuurt 200 spaties. Wat wordt er opgeslagen?

Twee strings die er hetzelfde uitzien

Strippen is één soort normaliseren. Tekst heeft nog een tweede soort nodig. Het voorbeeldboek Café Society heeft een é in de titel, en Unicode heeft twee manieren om die letter te schrijven: als één teken, of als een gewone e gevolgd door een combinerend accent, een teken dat zich vastmaakt aan de letter ervoor. Op het scherm zien de twee er hetzelfde uit. In dit blok is ́ het combinerende accent:

import unicodedata

typed = "Café Society"
stored = "Café Society"
print(typed == stored, len(typed), len(stored))
print(unicodedata.normalize("NFC", typed) == stored)

Het drukt False 13 12 af en daarna True. De twee titels zijn verschillende met een verschillende lengte, dus ==, in en len houden ze allemaal uit elkaar, en een client mag elk van beide sturen.

unicodedata.normalize("NFC", text) geeft de tekst terug in één afgesproken vorm, NFC genoemd, waarin een letter en zijn accent tot één teken zijn samengevoegd. unicodedata hoort bij de standaardbibliotheek en wordt bovenaan reading_server.py al geïmporteerd. Dit is de enige nieuwe aanroep van de les. Voor elke tekstwaarde wordt de eerste van de drie stappen unicodedata.normalize("NFC", value).strip().

In deze code schrijft a het accent als een apart combinerend teken na de e, en schrijft b de é als één teken. Wat drukt hij af?

import unicodedata

a = "Café"
b = "Café"
print(a == b, len(a), len(b), unicodedata.normalize("NFC", a) == b)

Een limiet voor elk veld

VeldRegel
title, authorVerplicht. Tekst van 1 tot en met 200 tekens, geteld na normaliseren en strippen
yearOptioneel. Een int van 1 tot en met 2100, en geen bool
statusOptioneel. Een van to-read, reading, done, precies zoals verstuurd
tagsOptioneel. Een van hoogstens 10 tags. Elke tag is tekst van 1 tot en met 30 tekens, geteld zoals een titel. Geen tag twee keer

De getallen zijn constanten bovenaan het bestand: TEXT_LIMIT, FIRST_YEAR, LAST_YEAR, MOST_TAGS en TAG_LIMIT, naast de set STATUSES. Twee van de regels hebben in Python een valkuil.

De eerste is het jaar. De true van komt in Python binnen als True, en isinstance(True, int) is True: een bool telt als een int, en True + 1998 is 1999. Een test op alleen int accepteert hem. Vraag dus eerst naar bool: if isinstance(year, bool) or not isinstance(year, int). Dezelfde regel weigert de tekst "1999" en de float 1999.0.

De tweede is de status. status not in STATUSES lijkt compleet. Maar een client kan een lijst sturen, en ["done"] in STATUSES werpt op, omdat een lijst niet in een set kan worden opgezocht. Een TypeError glipt zo langs je except ValueError, en de fout van de client wordt een 500. Test eerst isinstance(status, str). De status wordt niet gestript: het is een code uit een vaste lijst, geen tekst die iemand vrij intypt.

Waarom weigert de jaarcontrole True voordat hij isinstance(year, int) vraagt?

Elke tag gaat door dezelfde drie stappen als een titel, en de test op herhaling vergelijkt de genormaliseerde tags. Dus "x" en " x " zijn één tag die twee keer voorkomt, en dat geldt ook voor één woord dat in beide Unicode-vormen is verstuurd.

Welke waarde gaat het boek in nadat een tag is genormaliseerd en gecontroleerd?

Je taak

In reading_server.py:

  1. Schrijf eerst een helper, clean_text(value, name, limit). Die werpt ValueError op als value geen str is. Hij normaliseert naar NFC en stript. Hij werpt ValueError op als het resultaat korter is dan 1 of langer dan limit tekens. Daarna geeft hij het resultaat terug. name is voor de meldingen, zoals in f"{name} must be text".

  2. Laat validate_book groeien. Houd de sleutelcontroles uit les 1 bovenaan. Bouw daarna de nieuwe veld voor veld op, volgens de tabel: title en author moeten er zijn en gaan door clean_text; year, status en tags worden gecontroleerd als ze er zijn. Elke weigering is een ValueError waarvan de melding het veld noemt.

  3. Haal in book_request_error de aanroep van book_error weg. validate_book meldt nu een ontbrekende titel. Je kunt de book_error ook verwijderen, want niets anders roept hem aan.

Probeer het daarna in de :

python -c "import reading_server; print(reading_server.validate_book({'title': '  Dune ', 'author': 'Frank Herbert', 'tags': [' sand ']}))"

Het drukt {'title': 'Dune', 'author': 'Frank Herbert', 'tags': ['sand']} af: het boek dat wordt opgeslagen, is het boek dat werd gecontroleerd.

Dit alles heeft twee grenzen. NFC maakt tekst alleen gelijk waar het verschil een letter was die met zijn accent samengevoegd of los geschreven was. Letters uit verschillende alfabetten die op elkaar lijken, blijven verschillend. Een sterkere vorm, NFKC, vouwt meer samen en verandert ook wat sommige tekst betekent, dus een echte dienst kiest met zorg. En een titel mag nog steeds elk teken bevatten, ook een regeleinde in het midden. Zo’n titel komt door elke regel uit deze les.

Druk daarna op Check my work. De volgende les gaat over waarom een titel die door de controle kwam, toch schade kan doen.

Opdracht

Geef elk veld van een boek een regel, in validate_book in reading_server.py.

Houd wat les 1 schreef: een body die geen is, de sleutels owner en id, en elke sleutel buiten ALLOWED_FIELDS werpen op, en het resultaat is een nieuwe dict. Controleer daarna elke :

VeldRegelTeruggegeven als
title, authorVerplicht. Een str van 1 tot en met TEXT_LIMIT (200) tekens, geteld na unicodedata.normalize("NFC", value).strip()De genormaliseerde, gestripte tekst
yearOptioneel. Een int van FIRST_YEAR (1) tot en met LAST_YEAR (2100). True en False worden geweigerdHet getal
statusOptioneel. Een str die in STATUSES staat, vergeleken zoals hij werd verstuurdDe tekst
tagsOptioneel. Een list van hoogstens MOST_TAGS (10) tags. Elke tag is een str van 1 tot en met TAG_LIMIT (30) tekens, geteld zoals een titel. Dezelfde tag twee keer, vergeleken na normaliseren en strippen, wordt geweigerdEen nieuwe met de genormaliseerde, gestripte tags

Elke weigering is een ValueError waarvan de melding het veld noemt: title, author, year, status, of het woord tag. De formulering bepaal je zelf. Een helper clean_text(value, name, limit) die de drie stappen voor één uitvoert, houdt de kort.

Haal in book_request_error de aanroep van book_error weg. create_book en replace_book blijven lopen over wat validate_book teruggeeft.

Druk daarna op Check my work. Het roept validate_book() aan met bodies die je niet hebt gezien en vergelijkt elke geaccepteerde waarde exact: tekst met witruimte eromheen, 200 letters gevolgd door vijf spaties, tekst getypt met een combinerend accent, de jaren 1 en 2100, elke status, een lege lijst met tags, tien tags, en een tag van 30 tekens. Deze moeten met ValueError worden geweigerd: 200 spaties, 201 tekens, een lege of ontbrekende titel of auteur, een getal als titel; True, False, 0, 2101, -5, "1999" en 1999.0 als jaar; een status in hoofdletters, een lege, een getal en een lijst; tags die tekst of een dict zijn, elf tags, een tag van 31 tekens, een lege of blanco tag, een lijst of een getal als tag, en dezelfde tag twee keer in twee vormen. Daarna roept het handle() aan met eigen gebruikers en tokens: het opgeslagen boek moet de genormaliseerde waarden bevatten, en een waarde van het verkeerde type moet 422 krijgen, nooit 500. Niets uit je wordt beoordeeld.