Obsah
1. Validace datových struktur v Pythonu (dokončení)
2. Instalace modulu pytest-voluptuous s jeho krátkým otestováním v REPLu
3. První příklad – základy použití modulu pytest-voluptuous
4. Výsledek spuštění prvního příkladu
5. Validace složitějších datových struktur (slovníků a seznamů slovníků)
6. Výsledek spuštění druhého příkladu
7. Praktičtější příklad – validace dat získaných z REST API
8. Zjištění, jestli hodnota odpovídá UUID verze 4
9. Výsledek běhu třetího demonstračního příkladu
10. Validace složitější datové struktury získané přes REST API – první varianta
11. Základní validace hodnot uložených ve slovníku
12. Podrobná validace hodnot uložených ve slovníku
13. Validace slovníků uložených v jiném slovníku
14. Úplný zdrojový kód čtvrtého demonstračního příkladu
15. Výsledek běhu čtvrtého demonstračního příkladu
17. Implicitní nastavení volitelných klíčů a konkrétní specifikace klíčů povinných
18. Pátý demonstrační příklad a výsledek jeho spuštění
19. Repositář s demonstračními příklady
1. Validace datových struktur v Pythonu (dokončení)
V posledním článku o knihovnách sloužících pro validaci složitých datových struktur v Pythonu navážeme na část první a taktéž druhou. Připomeňme si, že v prvním článku jsme si popsali knihovny pojmenované Schemagic a Schema. Obě tyto knihovny jsou založeny na tom, že samotný popis validačních schémat je nadeklarován přímo v Pythonu, takže se případní uživatelé těchto knihoven (což jsou většinou programátoři) nemusí učit nový DSL (doménově specifický jazyk). Knihovna Schemagic je zvláštní tím, že kromě vlastní validace provádí i konverzi dat. Ve druhé části jsme si pak popsali knihovnu s nevyslovitelným názvem Voluptuous. Autoři této knihovny dbají na to, aby byla validace co nejjednodušší, tj. aby nebylo nutné tvořit složité třídy (k čemuž vede knihovna Schema) atd.
Dnes si popíšeme poslední nástroj nazvaný pytest-voluptuous. Jedná se o sadu tříd obalujících knihovnu Voluptuous takovým způsobem, aby bylo testování co nejsnazší. Například je přetížen operátor ==, který umožňuje, aby se data „porovnávala“ se schématem v příkazu assert atd. Připomeňme si, jak může vypadat velmi jednoduché schéma sloužící pro validaci slovníku obsahujícího tři klíče:
user = Schema({"name": str, "surname": str, "id": pos})
Pro samotnou validaci si vytvoříme pomocnou funkci:
def validate(schema, data): try: print("\n\n") print(schema) print(data) schema(data) print("pass") except Exception as e: print(e)
Validace může probíhat následovně:
validate(user, {"name": "Eda", "surname": "Wasserfall", "id": 1}) validate(user, {"name": "Eda", "id": 1}) validate(user, {"name": "Eda", "surname": "Wasserfall", "id": 0})
V dalším textu uvidíme, že se celá validace může ještě nepatrně zjednodušit a bude ji možné velmi snadno použít například v jednotkových testech.
2. Instalace modulu pytest-voluptuous s jeho krátkým otestováním v REPLu
Před spuštěním demonstračních příkladů si knihovnu pytest-voluptuous nainstalujeme, a to klasicky s využitím nástroje pip3 (nebo pip), protože tato knihovna je samozřejmě registrována i na PyPI (Python Package Index). Pro jednoduchost provedeme instalaci jen pro právě aktivního uživatele:.
$ pip3 install --user pytest-voluptuous Downloading/unpacking pytest-voluptuous Downloading pytest_voluptuous-1.0.2-py2.py3-none-any.whl Requirement already satisfied (use --upgrade to upgrade): pytest in /usr/local/lib/python3.4/dist-packages (from pytest-voluptuous) Downloading/unpacking voluptuous>=0.9.0 (from pytest-voluptuous) Downloading voluptuous-0.11.1-py2.py3-none-any.whl Installing collected packages: pytest-voluptuous, voluptuous Successfully installed pytest-voluptuous voluptuous Cleaning up...
Pro jistotu si vypíšeme základní informace o této knihovně, tj. zjistíme, zda je databáze nástroje pip konzistentní:
$ pip3 show pytest-voluptuous --- Name: pytest-voluptuous Version: 1.0.2 Location: /home/tester/.local/lib/python3.4/site-packages Requires: voluptuous, pytest
Nyní si můžeme základní vlastnosti této knihovny odzkoušet přímo v interaktivní smyčce REPL programovacího jazyka Python. Knihovnu jsme instalovali pro Python 3.x, takže musíme spustit i odpovídající interpret:
$ python3
Spuštění interpretru:
Python 3.4.3 (default, Nov 28 2017, 16:41:13) [GCC 4.8.4] on linux Type "help", "copyright", "credits" or "license" for more information.
Import třídy S z modulu pytest_voluptuous (skutečně se používá pouze S a nikoli Schema):
>>> from pytest_voluptuous import S
Definice jednoduchého schématu, které odpovídá schématu uvedenému v první kapitole:
>>> user = S({"name": str, "surname": str, "id": int})
A konečně si můžeme ukázat, jak se provádí validace pomocí příkazu assert a přetíženého operátoru ==:
>>> assert user == {"name": "Eda", "surname": "Wasserfall", "id": 1} >>> assert user == {"name": "Eda", "surname": "Wasserfall", "id": "xyzzy"} Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError >>> assert user == {"name": "Eda", "surname": "Wasserfall"} Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
Podrobnosti budou samozřejmě uvedeny v navazujících kapitolách.
3. První příklad – základy použití modulu pytest-voluptuous
Ukažme si nyní zdrojový kód prvního příkladu, v němž modul pytest-voluptuous použijeme. Začátek je jednoduchý – naimportujeme především třídu S z modulu pytest_voluptuous a pro další testy i třídy All a Length z modulu voluptuous.validators (tyto třídy použijeme později):
#!/usr/bin/env python # vim: set fileencoding=utf-8 from pytest_voluptuous import S from voluptuous.validators import All, Length
Dále definujeme validační schéma představující libovolně dlouhý seznam celých čísel, čísel s plovoucí řádovou čárkou či čísel komplexních (v obecně libovolném pořadí a počtu):
number_list = S([int, float, complex])
Následuje sada testů (už nyní si můžete tipnout, které dopadnou v pořádku a které nikoli). Jak je u knihovny Pytest zvykem, začínají testovací funkce prefixem „test“:
def test1(): assert [1, 2, 3] == number_list assert [1, 2, 3.2] == number_list def test2(): assert [2j, 4j, 5j] == number_list assert [1+2j, 3+4j, 5j] == number_list def test3(): assert [1, "2", 3] == number_list def test4(): assert ["1", "2", "3"] == number_list def test5(): assert (1, 2, 3) == number_list
Druhé schéma představuje libovolně dlouhý seznam binárních číslic:
binary_numbers = S([0, 1]) def test6(): assert binary_numbers == [0, 0, 0] assert binary_numbers == [1, 1, 0] def test7(): assert binary_numbers == [1, 2, 3]
4. Výsledek spuštění prvního příkladu
Dnešní první demonstrační příklad nebudeme spouštět přímo intepretrem Pythonu, ale přes pytest, tedy takto:
$ pytest pytest-voluptuous-demo1.py
Výsledkem by měl být přibližně následující výstup (samozřejmě se mohou nepatrně lišit verze Pythonu i knihoven, ovšem testy by měly mít shodný průběh). Povšimněte si, že z celkem sedmi testů čtyři testy nalezly nevalidní data, přesně podle očekávání:
============================= test session starts ============================== platform linux -- Python 3.4.3, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 rootdir: /home/tester/temp/python/python-schema-checks/pytest-voluptuous-demo1, inifile: plugins: voluptuous-1.0.2 collected 7 items pytest-voluptuous-demo1.py ..FFF.F [100%] =================================== FAILURES =================================== ____________________________________ test3 _____________________________________ def test3(): > assert [1, "2", 3] == number_list E assert failed to validation error(s): E - 1: expected complex @ data[1] pytest-voluptuous-demo1.py:21: AssertionError ____________________________________ test4 _____________________________________ def test4(): > assert ["1", "2", "3"] == number_list E assert failed to validation error(s): E - 0: expected complex @ data[0] E - 1: expected complex @ data[1] E - 2: expected complex @ data[2] pytest-voluptuous-demo1.py:25: AssertionError ____________________________________ test5 _____________________________________ def test5(): > assert (1, 2, 3) == number_list E assert failed to validation error(s): E - : expected a list pytest-voluptuous-demo1.py:29: AssertionError ____________________________________ test7 _____________________________________ def test7(): > assert binary_numbers == [1, 2, 3] E assert failed to validation error(s): E - 1: not a valid value @ data[1] E - 2: not a valid value @ data[2] pytest-voluptuous-demo1.py:40: AssertionError ====================== 4 failed, 3 passed in 0.05 seconds ======================
5. Validace složitějších datových struktur (slovníků a seznamů slovníků)
V praxi se velmi často setkáme s tím, že je zapotřebí validovat nejenom „jednoduchý“ slovník, ale například i seznam (či n-tici) slovníků. Připomeňme si, že deklarace validačního kritéria pro jednoduchý slovník vypadá takto:
user = S({"name": str, "surname": str, "id": pos})
Validační kritérium pro seznam takových slovníků se zapíše následovně (povšimněte si, že se vlastně jedná o kombinaci kritéria známého z prvního demonstračního příkladu a kritéria zapsaného před tímto odstavcem:
users = S([S({"name": str, "surname": str, "id": pos})])
Takto zapsaná validační kritéria jsou použita v dnešním druhém demonstračním příkladu, jehož zdrojový kód vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from pytest_voluptuous import S, Partial, Exact from voluptuous import Invalid from voluptuous.validators import All, Length
Pomocná validační funkce pro celá kladná (přirozená) čísla:
def pos(value): if type(value) is not int or value <= 0: raise Invalid("positive integer value expected, but got {v} instead".format(v=value))
Samotné validační schéma pro jediný slovník:
user = S({"name": str, "surname": str, "id": pos}) def test1(): assert user == {"name": "Eda", "surname": "Wasserfall", "id": 1} def test2(): assert user == {"name": "Eda", "id": 1} def test3(): assert user == {"name": "Eda", "surname": "Wasserfall", "id": 0}
Validační schéma pro seznam slovníků:
users = S([S({"name": str, "surname": str, "id": pos})]) def test4(): assert users == [{"name": "Eda", "surname": "Wasserfall", "id": 1}, {"name": "Varel", "surname": "Frištenský", "id": 2}] def test5(): assert users == [{"name": "Eda", "surname": "Wasserfall", "id": 0}, {"surname": "Frištenský", "id": 2}]
6. Výsledek spuštění druhého příkladu
Opět si ukažme, jak bude vypadat výsledek spuštění dnešního druhého demonstračního příkladu. Opět použijeme příkaz pytest:
$ pytest pytest-voluptuous-demo2.py
Samotný výsledek pěti testů:
============================= test session starts ============================== platform linux -- Python 3.4.3, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 rootdir: /home/tester/temp/python/python-schema-checks/pytest-voluptuous-demo2, inifile: plugins: voluptuous-1.0.2 collected 5 items pytest-voluptuous-demo2.py .FF.F [100%] =================================== FAILURES =================================== ____________________________________ test2 _____________________________________ def test2(): > assert user == {"name": "Eda", "id": 1} E AssertionError: assert failed to validation error(s): E - surname: required key not provided @ data['surname'] pytest-voluptuous-demo2.py:26: AssertionError ____________________________________ test3 _____________________________________ def test3(): > assert user == {"name": "Eda", "surname": "Wasserfall", "id": 0} E AssertionError: assert failed to validation error(s): E - id: positive integer value expected, but got 0 instead for dictionary value @ data['id'] pytest-voluptuous-demo2.py:31: AssertionError ____________________________________ test5 _____________________________________ def test5(): > assert users == [{"name": "Eda", "surname": "Wasserfall", "id": 0}, {"surname": "Frištenský", "id": 2}] E AssertionError: assert failed to validation error(s): E - 0.id: positive integer value expected, but got 0 instead for dictionary value @ data[0]['id'] pytest-voluptuous-demo2.py:49: AssertionError ====================== 3 failed, 2 passed in 0.05 seconds ======================
7. Praktičtější příklad – validace dat získaných z REST API
Zkusme se nyní zaměřit již na praktičtěji orientované příklady. Nejprve si vyzkoušejme veřejně dostupnou webovou službu, která po požadavku typu GET poslaného na adresu https://httpbin.org/uuid vrátí JSON s jediným klíčem „uuid“, pod nímž je uložen řetězec odpovídající UUID verze 4. Chování této webové služby si samozřejmě můžeme velmi jednoduše ověřit:
$ curl https://httpbin.org/uuid
Výsledek může vypadat následovně (resp. přesněji řečeno takto s prakticky stoprocentní jistotou vypadat nebude, protože získáte odlišnou hodnotu :-):
{ "uuid": "19bd32b7-2ee8-40f0-a275-935d87331e83"
Načtení struktury vrácené v JSONu v Pythonu:
response = requests.get("https://httpbin.org/uuid").json()
Jednu z možností otestování, zda řetězec odpovídá formátu UUID verze 4, je možné v Pythonu implementovat pomocí knihovny uuid:
def uuid4(string): val = UUID(string, version=4) if val.hex != string.replace('-', ''): raise Invalid("the string '{s}' is not valid UUID4".format(s=string))
8. Zjištění, jestli hodnota odpovídá UUID verze 4
Celý demonstrační příklad (v pořadí již třetí), který nejprve zjistí, zda dva řetězce odpovídají formátu UUID verze 4 a následně zvaliduje JSON vrácený webovou službou, může vypadat následovně.
Nezbytné import na začátku:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from pytest_voluptuous import S, Partial, Exact from voluptuous import Invalid from voluptuous.validators import All, Length from uuid import UUID import requests
Již popsaná funkce pro validaci UUID verze 4:
def uuid4(string): val = UUID(string, version=4) if val.hex != string.replace('-', ''): raise Invalid("the string '{s}' is not valid UUID4".format(s=string))
Schéma pro strukturu, kterou vrátí webová služba:
uuid_response_struct = S({"uuid": uuid4})
Vlastní sada tří jednotkových testů:
def test_uuid_1(): assert {"uuid": "00ebf64b-c15e-4b5d-846a-28971dc05796"} == uuid_response_struct def test_uuid_2(): assert {"uuid": "00ebf64b-xxxx-4b5d-846a-28971dc05796"} == uuid_response_struct def test_uuid_returned_by_the_service(): response = requests.get("https://httpbin.org/uuid").json() assert response == uuid_response_struct
9. Výsledek běhu třetího demonstračního příkladu
Opět se podívejme na to, jak vypadá výsledek běhu třetího příkladu. Druhý test odhalil nevalidní řetězec (což je v pořádku, tuto chybu očekáváme), ale samotná data z webové služby jsou korektní:
============================= test session starts ============================== platform linux -- Python 3.6.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 rootdir: /home/tester/temp/python/python-schema-checks/pytest-voluptuous-demo3, inifile: plugins: voluptuous-1.0.2, cov-2.5.1 collected 3 items pytest-voluptuous-demo3.py .F. [100%] =================================== FAILURES =================================== _________________________________ test_uuid_2 __________________________________ def test_uuid_2(): > assert {"uuid": "00ebf64b-xxxx-4b5d-846a-28971dc05796"} == uuid_response_struct E AssertionError: assert failed to validation error(s): E - uuid: not a valid value for dictionary value @ data['uuid'] pytest-voluptuous-demo3.py:26: AssertionError ====================== 1 failed, 2 passed in 0.63 seconds ======================
10. Validace složitější datové struktury získané přes REST API – první varianta
Na stejném webu, jaký jsme použili v předchozím příkladu, naleznete i další zajímavý REST API endpoint, a to konkrétně na adrese https://httpbin.org/anything. Formát vygenerované odpovědi (opět poslané v JSON formátu) zjistíme příkazem curl:
$ curl https://httpbin.org/anything
Pro curl se vrátí následující struktura:
{ "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Connection": "close", "Host": "httpbin.org", "User-Agent": "curl/7.35.0" }, "json": null, "method": "GET", "origin": "111.111.111.111", "url": "https://httpbin.org/anything" }
Pokud stejný REST API endpoint použijete v prohlížeči, dostanete odlišná data v sekci „headers“; například. Zbylé klíče ovšem budou shodné:
{ "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.5", "Connection": "close", "Dnt": "1", "Host": "httpbin.org", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" }, "json": null, "method": "GET", "origin": "111.111.111.111", "url": "https://httpbin.org/anything" }
Podobně můžeme JSON s datovou strukturou získat přímo z Pythonu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import requests response = requests.get("https://httpbin.org/anything").json() print(response)
V tomto případě získáme ještě více dat v sekci „headers“ (REST API odpovídá údaji, které on nás získal):
{ "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.5", "Cache-Control": "max-age=0", "Connection": "close", "Cookie": "_gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1", "Host": "httpbin.org", "Referer": "https://httpbin.org/", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" }, "json": null, "method": "GET", "origin": "213.175.37.10", "url": "https://httpbin.org/anything" }
První test může jednoduše otestovat, zda jsme získali slovník s libovolnými hodnotami. Samotné validační kritérium vypadá takto:
anything_struct = S(dict)
Celý test i s nezbytnými importy:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from pytest_voluptuous import S, Partial, Exact from voluptuous import Invalid from voluptuous.validators import All, Length import requests import re def test_the_anything_endpoint_1(): anything_struct = S(dict) response = requests.get("https://httpbin.org/anything").json() assert response == anything_struct
11. Základní validace hodnot uložených ve slovníku
Výše uvedené validační kritérium pravděpodobně čtenáři tohoto článku příliš neocenili, takže se ho pokusme vylepšit. Prvním vylepšením bude explicitní vyjmenování všech klíčů, které se musí v datové struktuře nacházet. U všech klíčů navíc určíme (prozatím nepříliš přesně) i typy hodnot. Povšimněte si, že lze použít i None, což v JSONu odpovídá hodnotě null. A samozřejmě můžeme použít dict pro určení, že nějaký prvek je vloženým slovníkem:
def test_the_anything_endpoint_2(): anything_struct = S({"args": dict, "data": str, "files": dict, "form": dict, "headers": dict, "json": None, "method": str, "origin": str, "url": str}) response = requests.get("https://httpbin.org/anything").json() assert response == anything_struct
12. Podrobná validace hodnot uložených ve slovníku
Validační kritérium může být samozřejmě mnohem přesnější. Pro validaci hodnoty s URL uložené pod klíčem „url“ můžeme použít třídu Url naimportovanou přímo z knihovny Voluptuous:
from voluptuous import Url
Kritérium se změní následovně:
anything_struct = S({"args": dict, "data": str, "files": dict, "form": dict, "headers": dict, "json": None, "method": str, "origin": str, "url": Url()})
Dále můžeme u některých hodnot určit větší počet možností. Například pod klíčem „method“ je uloženo jméno HTTP metody, což je sice řetězec, ovšem s omezeným povoleným počtem hodnot. Totéž může platit u klíče „json“, který může obsahovat buď null, nebo nějaký řetězec. V předchozích knihovnách se pro tento účel používala klauzule Or, nyní použijeme klauzuli Any:
from voluptuous import Any
Validační kritérium se nyní změní takto:
anything_struct = S({"args": dict, "data": str, "files": dict, "form": dict, "headers": dict, "json": Any(None, str), "method": Any("GET", "POST", "PUT", "DELETE"), "origin": origin, "url": Url()})
13. Validace slovníků uložených v jiném slovníku
Pokud se podíváme podrobněji na sekci „headers“ vrácenou v JSONu z webové služby, zjistíme, že obsahuje vnořený slovník, jehož klíči i hodnotami jsou pouze řetězce:
"headers": { "Accept": "*/*", "Connection": "close", "Host": "httpbin.org", "User-Agent": "curl/7.35.0" },
Jak se takový slovník validuje, již víme:
S({str:str})
Nyní pouze stačí toto validační kritérium vložit do hlavního validačního kritéria, a to následujícím způsobem:
anything_struct = S({"args": dict, "data": str, "files": dict, "form": dict, "headers": S({str:str}), "json": Any(None, str), "method": Any("GET", "POST", "PUT", "DELETE"), "origin": origin, "url": Url()})
14. Úplný zdrojový kód čtvrtého demonstračního příkladu
Všechny tři varianty postupně vytvářeného validačního kritéria jsou součástí dnešního čtvrtého demonstračního příkladu, jehož zdrojový kód vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from pytest_voluptuous import S, Partial, Exact from voluptuous import Invalid, Url, Any from voluptuous.validators import All, Length import requests import re def test_the_anything_endpoint_1(): anything_struct = S(dict) response = requests.get("https://httpbin.org/anything").json() assert response == anything_struct def test_the_anything_endpoint_2(): anything_struct = S({"args": dict, "data": str, "files": dict, "form": dict, "headers": dict, "json": None, "method": str, "origin": str, "url": str}) response = requests.get("https://httpbin.org/anything").json() assert response == anything_struct def origin(value): if not re.fullmatch(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", value): raise Invalid("wrong input {i}, IP address expected".format(i=value)) def test_the_anything_endpoint_3(): anything_struct = S({"args": dict, "data": str, "files": dict, "form": dict, "headers": S({str:str}), "json": Any(None, str), "method": Any("GET", "POST", "PUT", "DELETE"), "origin": origin, "url": Url()}) response = requests.get("https://httpbin.org/anything").json() assert response == anything_struct
15. Výsledek běhu čtvrtého demonstračního příkladu
Opět si ukažme, jak bude vypadat čtvrtý demonstrační příklad po svém spuštění:
============================= test session starts ============================== platform linux -- Python 3.4.3, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 -- /usr/bin/python3 cachedir: .cache rootdir: /home/tester/temp/pytest-voluptuous-demo4, inifile: plugins: voluptuous-1.0.2 collecting ... collected 3 items pytest-voluptuous-demo2.py::test_the_anything_endpoint_1 PASSED [ 33%] pytest-voluptuous-demo2.py::test_the_anything_endpoint_2 PASSED [ 66%] pytest-voluptuous-demo2.py::test_the_anything_endpoint_3 PASSED [100%] =========================== 3 passed in 1.94 seconds ===========================
16. Použití klauzule Optional
V případě, že některý klíč (a k němu přiřazená hodnota) nemusí být ve slovníku uložen, je možné jméno klíče umístit do klauzule Optional. To znamená, že původní validační schéma:
user = S({"name": str, "surname": str, "id": pos})
je možné změnit takovým způsobem, aby ID uživatele nemuselo být z nějakého důvodu specifikováno:
user2 = S({"name": str, "surname": str, Optional("id"): pos})
17. Implicitní nastavení volitelných klíčů a konkrétní specifikace klíčů povinných
Někdy se setkáme i s opačnou možností – budeme chtít, aby většina klíčů byla jen volitelná, ovšem některé klíče naopak musí být zadány vždy (samozřejmě i s příslušnými navázanými hodnotami). V tomto případě máme dvě možnosti. Buď otrocky používat klauzuli Optional:
user3 = S({"name": str, "surname": str, Optional("id"): pos, Optional("address"): str, Optional("state"): str, Optional("zip"): int})
nebo naopak použít klauzuli Required u těch klíčů, které jsou povinné. Navíc ještě musíme při definici validačního kritéria nastavit parametr required na pravdivostní hodnotu False (tím se přepne chápání ostatních klíčů – budou nepovinné):
user4 = S({Required("name"): str, Required("surname"): str, "id": pos, "address": str, "state": str, "zip": int}, required=False)
S druhou možností se setkáme častěji.
18. Pátý demonstrační příklad a výsledek jeho spuštění
Obě klauzule Optional a Required použijeme v dnešním pátém a současně i posledním demonstračním příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from pytest_voluptuous import S, Partial, Exact from voluptuous import Invalid, Optional, Required from voluptuous.validators import All, Length def pos(value): if type(value) is not int or value <= 0: raise Invalid("positive integer value expected, but got {v} instead".format(v=value)) user = S({"name": str, "surname": str, "id": pos}) def test1(): assert user == {"name": "Eda", "surname": "Wasserfall", "id": 1} def test2(): assert user == {"name": "Eda", "id": 1} def test3(): assert user == {"name": "Eda", "surname": "Wasserfall", "id": 0} def test4(): assert user == {"name": "Eda", "surname": "Wasserfall"} user2 = S({"name": str, "surname": str, Optional("id"): pos}) def test5(): assert user2 == {"name": "Eda", "surname": "Wasserfall", "id": 1} def test6(): assert user2 == {"name": "Eda", "id": 1} def test7(): assert user2 == {"name": "Eda", "surname": "Wasserfall", "id": 0} def test8(): assert user2 == {"name": "Eda", "surname": "Wasserfall"} user3 = S({"name": str, "surname": str, Optional("id"): pos, Optional("address"): str, Optional("state"): str, Optional("zip"): int}) def test9(): assert user3 == {"name": "Eda", "surname": "Wasserfall", "id": 1} def test10(): assert user3 == {"name": "Eda", "surname": "Wasserfall"} def test11(): assert user3 == {"name": "Eda", "surname": "Wasserfall", "zip": 12345} user4 = S({Required("name"): str, Required("surname"): str, "id": pos, "address": str, "state": str, "zip": int}, required=False) def test12(): assert user4 == {"name": "Eda", "surname": "Wasserfall", "id": 1} def test13(): assert user4 == {"name": "Eda", "surname": "Wasserfall"} def test14(): assert user4 == {"name": "Eda", "surname": "Wasserfall", "zip": 12345}
Výsledek běhu příkladu:
============================= test session starts ============================== platform linux -- Python 3.4.3, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 rootdir: /home/tester/temp/python/python-schema-checks/pytest-voluptuous-demo5, inifile: plugins: voluptuous-1.0.2 collected 14 items pytest-voluptuous-demo5.py .FFF.FF....... [100%] =================================== FAILURES =================================== ____________________________________ test2 _____________________________________ def test2(): > assert user == {"name": "Eda", "id": 1} E AssertionError: assert failed to validation error(s): E - surname: required key not provided @ data['surname'] pytest-voluptuous-demo5.py:26: AssertionError ____________________________________ test3 _____________________________________ def test3(): > assert user == {"name": "Eda", "surname": "Wasserfall", "id": 0} E AssertionError: assert failed to validation error(s): E - id: positive integer value expected, but got 0 instead for dictionary value @ data['id'] pytest-voluptuous-demo5.py:31: AssertionError ____________________________________ test4 _____________________________________ def test4(): > assert user == {"name": "Eda", "surname": "Wasserfall"} E AssertionError: assert failed to validation error(s): E - id: required key not provided @ data['id'] pytest-voluptuous-demo5.py:37: AssertionError ____________________________________ test6 _____________________________________ def test6(): > assert user2 == {"name": "Eda", "id": 1} E AssertionError: assert failed to validation error(s): E - surname: required key not provided @ data['surname'] pytest-voluptuous-demo5.py:53: AssertionError ____________________________________ test7 _____________________________________ def test7(): > assert user2 == {"name": "Eda", "surname": "Wasserfall", "id": 0} E AssertionError: assert failed to validation error(s): E - id: positive integer value expected, but got 0 instead for dictionary value @ data['id'] pytest-voluptuous-demo5.py:58: AssertionError ====================== 5 failed, 9 passed in 0.07 seconds ======================
19. Repositář s demonstračními příklady
Všechny demonstrační projekty, které jsme si v dnešním článku popsali, byly uloženy do Git repositáře, který naleznete na adrese https://github.com/tisnik/python-schema-checks. V tabulkách pod tímto odstavcem jsou pro úplnost vypsány odkazy na všechny doposud zmíněné projekty rozdělené podle použité knihovny. Z tohoto důvodu zde naleznete i projekty zmíněné minule a samozřejmě i předminule.
Schemagic
Projekt | Stručný popis | Cesta |
---|---|---|
schemagic-demo-1 | základní vlastnosti knihovny Schemagic | https://github.com/tisnik/python-schema-checks/tree/master/schemagic-demo-1 |
schemagic-demo-2 | konverze prováděné při validaci | https://github.com/tisnik/python-schema-checks/tree/master/schemagic-demo-2 |
schemagic-demo-3 | vlastní validační funkce | https://github.com/tisnik/python-schema-checks/tree/master/schemagic-demo-3 |
schemagic-demo-4 | vylepšení předchozího příkladu | https://github.com/tisnik/python-schema-checks/tree/master/schemagic-demo-4 |
schemagic-demo-5 | validace slovníků | https://github.com/tisnik/python-schema-checks/tree/master/schemagic-demo-5 |
schemagic-demo-6 | validace slovníků podruhé | https://github.com/tisnik/python-schema-checks/tree/master/schemagic-demo-6 |
Schema
Projekt | Stručný popis | Cesta |
---|---|---|
schema-demo-1 | základní vlastnosti knihovny Schema | https://github.com/tisnik/python-schema-checks/tree/master/schema-demo-1 |
schema-demo-2 | validace slovníků a dalších typů | https://github.com/tisnik/python-schema-checks/tree/master/schema-demo-2 |
schema-demo-3 | validace slovníků a dalších typů | https://github.com/tisnik/python-schema-checks/tree/master/schema-demo-3 |
schema-demo-4 | validace slovníků a dalších typů | https://github.com/tisnik/python-schema-checks/tree/master/schema-demo-4 |
schema-demo-5 | validace slovníků a dalších typů | https://github.com/tisnik/python-schema-checks/tree/master/schema-demo-5 |
schema-demo-6 | validace slovníků a dalších typů | https://github.com/tisnik/python-schema-checks/tree/master/schema-demo-6 |
Voluptuous
Projekt | Stručný popis | Cesta |
---|---|---|
voluptuous-demo-1 | základní vlastnosti knihovny Voluptuous | https://github.com/tisnik/python-schema-checks/tree/master/voluptuous-demo-1 |
voluptuous-demo-2 | validace obsahu slovníků | https://github.com/tisnik/python-schema-checks/tree/master/voluptuous-demo-2 |
pytest_voluptuous
Projekt | Stručný popis | Cesta |
---|---|---|
pytest-voluptuous-demo1 | validace obsahu slovníků | https://github.com/tisnik/python-schema-checks/tree/master/pytest-voluptuous-demo1 |
pytest-voluptuous-demo2 | validace obsahu slovníků | https://github.com/tisnik/python-schema-checks/tree/master/pytest-voluptuous-demo2 |
pytest-voluptuous-demo3 | validace struktury získané z REST API | https://github.com/tisnik/python-schema-checks/tree/master/pytest-voluptuous-demo3 |
pytest-voluptuous-demo4 | validace složitější struktury získané z REST API | https://github.com/tisnik/python-schema-checks/tree/master/pytest-voluptuous-demo4 |
pytest-voluptuous-demo5 | použití klauzulí Optional a Required | https://github.com/tisnik/python-schema-checks/tree/master/pytest-voluptuous-demo5 |
20. Odkazy na Internetu
- 7 Best Python Libraries for Validating Data
https://www.yeahhub.com/7-best-python-libraries-validating-data/ - Universally unique identifier (Wikipedia)
https://en.wikipedia.org/wiki/Universally_unique_identifier - UUID objects according to RFC 4122 (knihovna pro Python)
https://docs.python.org/3.5/library/uuid.html#uuid.uuid4 - Object identifier (Wikipedia)
https://en.wikipedia.org/wiki/Object_identifier - Digital object identifier (Wikipedia)
https://en.wikipedia.org/wiki/Digital_object_identifier - voluptuous na (na PyPi)
https://pypi.python.org/pypi/voluptuous - voluptuous (na GitHubu)
https://github.com/alecthomas/voluptuous - pytest-voluptuous 1.0.2 (na PyPi)
https://pypi.org/project/pytest-voluptuous/ - pytest-voluptuous (na GitHubu)
https://github.com/F-Secure/pytest-voluptuous - schemagic 0.9.1 (na PyPi)
https://pypi.python.org/pypi/schemagic/0.9.1 - Schemagic / Schemagic.web (na GitHubu)
https://github.com/Mechrophile/schemagic - schema 0.6.7 (na PyPi)
https://pypi.python.org/pypi/schema - schema (na GitHubu)
https://github.com/keleshev/schema - XML Schema validator and data conversion library for Python
https://github.com/brunato/xmlschema - xmlschema 0.9.7
https://pypi.python.org/pypi/xmlschema/0.9.7 - jsonschema 2.6.0
https://pypi.python.org/pypi/jsonschema - warlock 1.3.0
https://pypi.python.org/pypi/warlock - Python Virtual Environments – A Primer
https://realpython.com/python-virtual-environments-a-primer/ - pip 1.1 documentation: Requirements files
https://pip.readthedocs.io/en/1.1/requirements.html - unittest.mock — mock object library
https://docs.python.org/3.5/library/unittest.mock.html - mock 2.0.0
https://pypi.python.org/pypi/mock - An Introduction to Mocking in Python
https://www.toptal.com/python/an-introduction-to-mocking-in-python - Unit testing (Wikipedia)
https://en.wikipedia.org/wiki/Unit_testing - Unit testing
https://cs.wikipedia.org/wiki/Unit_testing - Test-driven development (Wikipedia)
https://en.wikipedia.org/wiki/Test-driven_development - Pip (dokumentace)
https://pip.pypa.io/en/stable/ - 5 Differences between clojure.spec and Schema
https://lispcast.com/clojure.spec-vs-schema/ - Schema: Clojure(Script) library for declarative data description and validation
https://github.com/plumatic/schema - clojure.spec – Rationale and Overview
https://clojure.org/about/spec