├── code ├── server │ ├── 16_deploy │ │ ├── .nowignore │ │ ├── requirements.txt │ │ ├── now.json │ │ ├── mojeapi.md │ │ └── index.py │ ├── 14_delete │ │ ├── design.txt │ │ ├── test1_3.txt │ │ ├── test2_3.txt │ │ ├── test3_42.txt │ │ └── index.py │ ├── 01_base │ │ ├── design.txt │ │ ├── test.txt │ │ └── index.py │ ├── 10_not_found │ │ ├── design_proposal.txt │ │ ├── test_42.txt │ │ ├── test_1.txt │ │ └── index.py │ ├── 09_movie │ │ ├── test_42.txt │ │ ├── test_hello.txt │ │ ├── design.txt │ │ ├── test_1.txt │ │ └── index.py │ ├── 15_forbidden │ │ ├── test.txt │ │ └── index.py │ ├── 02_data │ │ ├── test.txt │ │ └── index.py │ ├── 03_dict │ │ ├── test.txt │ │ └── index.py │ ├── 04_json │ │ ├── test.txt │ │ └── index.py │ ├── 05_json_simplified │ │ ├── test.txt │ │ └── index.py │ ├── 07_params │ │ ├── test.txt │ │ └── index.py │ ├── 11_repr │ │ ├── test_root.txt │ │ ├── test_movies.txt │ │ ├── design.txt │ │ ├── test_movie.txt │ │ └── index.py │ ├── 12_post │ │ ├── design.txt │ │ ├── test3_movie.txt │ │ ├── test1_post_unix.txt │ │ ├── test1_post_win.txt │ │ ├── test2_get.txt │ │ └── index.py │ ├── 06_movies │ │ ├── test.txt │ │ └── index.py │ ├── 13_created │ │ ├── design.txt │ │ ├── test_unix.txt │ │ ├── test_win.txt │ │ └── index.py │ ├── 08_db │ │ ├── test.txt │ │ └── index.py │ └── test_code_works.py ├── client │ ├── 06_newest │ │ ├── test.txt │ │ └── client.py │ ├── 04_movies │ │ ├── test.txt │ │ └── client.py │ ├── 05_year │ │ ├── test.txt │ │ └── client.py │ ├── 01_base │ │ ├── client.py │ │ └── test.txt │ ├── 03_json │ │ ├── client.py │ │ └── test.txt │ └── 02_json_error │ │ ├── client.py │ │ └── test.txt ├── curl_version.txt ├── curl_omdb.txt └── curl_cnb.txt ├── .github └── FUNDING.yml ├── _static ├── images │ ├── cnb.png │ ├── http.png │ ├── now.png │ ├── rss.png │ ├── chmu1.png │ ├── chmu2.png │ ├── cnb-api.png │ ├── heureka.png │ ├── spotify.png │ ├── https-cnb.png │ ├── rss-icon.png │ ├── cnb-website.png │ ├── https-github.png │ ├── me-api-curl.png │ ├── me-api-json.png │ ├── me-api-text.png │ ├── non-web-api.png │ ├── omdb-api-key.png │ ├── localhost-error.png │ └── omdb-westworld-browser.png └── css │ └── cojeapi.css ├── en ├── contributing.rst ├── LICENSE ├── index.rst ├── 01-api-intro.rst └── conf.py ├── cs ├── LICENSE ├── _nodejs_note.rst ├── 07-co-dal.rst ├── _author.rst ├── 06-web-jako-api.rst ├── _win_json_note.rst ├── index.rst ├── contributing.rst ├── workshop.rst ├── _install_curl.rst ├── _install_now.rst ├── conf.py ├── 02-klient-server.rst ├── 05-tvorime-klienta.rst ├── 01-uvod-do-api.rst └── 03-zakladni-pojmy.rst ├── pytest.ini ├── _templates └── layout.html ├── requirements.txt ├── cojeapi-apiary.apib ├── Makefile ├── _extensions ├── codeexample.py └── mdn.py ├── README.md ├── LICENSE ├── .gitignore └── .circleci └── config.yml /code/server/16_deploy/.nowignore: -------------------------------------------------------------------------------- 1 | venv 2 | -------------------------------------------------------------------------------- /code/server/16_deploy/requirements.txt: -------------------------------------------------------------------------------- 1 | falcon 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["honzajavorek"] 2 | patreon: "honzajavorek" 3 | -------------------------------------------------------------------------------- /code/client/06_newest/test.txt: -------------------------------------------------------------------------------- 1 | $ python client.py 2 | Nejnovější film: Sharknado 2013 3 | -------------------------------------------------------------------------------- /_static/images/cnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/cnb.png -------------------------------------------------------------------------------- /_static/images/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/http.png -------------------------------------------------------------------------------- /_static/images/now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/now.png -------------------------------------------------------------------------------- /_static/images/rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/rss.png -------------------------------------------------------------------------------- /_static/images/chmu1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/chmu1.png -------------------------------------------------------------------------------- /_static/images/chmu2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/chmu2.png -------------------------------------------------------------------------------- /_static/images/cnb-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/cnb-api.png -------------------------------------------------------------------------------- /_static/images/heureka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/heureka.png -------------------------------------------------------------------------------- /_static/images/spotify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/spotify.png -------------------------------------------------------------------------------- /_static/images/https-cnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/https-cnb.png -------------------------------------------------------------------------------- /_static/images/rss-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/rss-icon.png -------------------------------------------------------------------------------- /_static/images/cnb-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/cnb-website.png -------------------------------------------------------------------------------- /_static/images/https-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/https-github.png -------------------------------------------------------------------------------- /_static/images/me-api-curl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/me-api-curl.png -------------------------------------------------------------------------------- /_static/images/me-api-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/me-api-json.png -------------------------------------------------------------------------------- /_static/images/me-api-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/me-api-text.png -------------------------------------------------------------------------------- /_static/images/non-web-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/non-web-api.png -------------------------------------------------------------------------------- /_static/images/omdb-api-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/omdb-api-key.png -------------------------------------------------------------------------------- /_static/images/localhost-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/localhost-error.png -------------------------------------------------------------------------------- /code/server/14_delete/design.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/movies/3" --request DELETE 2 | HTTP/1.1 204 No Content 3 | -------------------------------------------------------------------------------- /_static/images/omdb-westworld-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honzajavorek/cojeapi/HEAD/_static/images/omdb-westworld-browser.png -------------------------------------------------------------------------------- /en/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | How to contribute? 4 | ================== 5 | 6 | .. todo:: 7 | Nothing here yet! 8 | -------------------------------------------------------------------------------- /code/client/04_movies/test.txt: -------------------------------------------------------------------------------- 1 | $ python client.py 2 | The Last Boy Scout 3 | Mies vailla menneisyyttä 4 | Sharknado 5 | Mega Shark vs. Giant Octopus 6 | -------------------------------------------------------------------------------- /code/client/05_year/test.txt: -------------------------------------------------------------------------------- 1 | $ python client.py 2 | The Last Boy Scout 1991 3 | Mies vailla menneisyyttä 2002 4 | Sharknado 2013 5 | Mega Shark vs. Giant Octopus 2009 6 | -------------------------------------------------------------------------------- /code/server/01_base/design.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/" 2 | HTTP/1.1 200 OK 3 | Content-Type: text/plain 4 | 5 | name: Honza 6 | surname: Javorek 7 | socks_size: 42 8 | -------------------------------------------------------------------------------- /cs/LICENSE: -------------------------------------------------------------------------------- 1 | © Copyright 2020, Honza Javorek 2 | 3 | Texty a obrázky materiálů jsou uvolněny pod licencí CC BY-SA 4.0 4 | https://creativecommons.org/licenses/by-sa/4.0 5 | -------------------------------------------------------------------------------- /code/server/14_delete/test1_3.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/3" --request DELETE 2 | HTTP/1.1 204 No Content 3 | Connection: close 4 | Date: Thu, 02 May 2019 20:58:43 GMT 5 | Server: waitress 6 | -------------------------------------------------------------------------------- /en/LICENSE: -------------------------------------------------------------------------------- 1 | © Copyright 2020, Honza Javorek 2 | 3 | Texts and images of the project are released under the CC BY-SA 4.0 license 4 | https://creativecommons.org/licenses/by-sa/4.0 5 | -------------------------------------------------------------------------------- /code/client/01_base/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | response = requests.get("https://cojeapi.honzajavorek.now.sh/") 4 | 5 | print(response.status_code) 6 | print(response.headers) 7 | print(response.text) 8 | -------------------------------------------------------------------------------- /code/client/04_movies/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | response = requests.get("https://cojeapi.honzajavorek.now.sh/movies") 4 | movies = response.json() 5 | for movie in movies: 6 | print(movie['name']) 7 | -------------------------------------------------------------------------------- /code/client/03_json/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | response = requests.get("https://cojeapi.honzajavorek.now.sh/") 4 | 5 | print(response.status_code) 6 | print(response.headers) 7 | print(response.json()) 8 | -------------------------------------------------------------------------------- /code/server/10_not_found/design_proposal.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/movies/42" 2 | HTTP/1.1 404 Not Found 3 | Content-Type: application/json; charset=UTF-8 4 | 5 | {"message": "Movie '42' doesn't exist"} 6 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = code 3 | addopts = --pylama --failed-first --verbose --verbose 4 | 5 | [pylama] 6 | paths = cs/conf.py en/conf.py code 7 | 8 | [pylama:pycodestyle] 9 | max_line_length = 100 10 | -------------------------------------------------------------------------------- /code/client/02_json_error/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | response = requests.get("https://cojeapi.honzajavorek.now.sh/") 4 | 5 | print(response.status_code) 6 | print(response.headers) 7 | print(response.text['surname']) 8 | -------------------------------------------------------------------------------- /code/server/10_not_found/test_42.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/42" 2 | HTTP/1.1 404 Not Found 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sun, 28 Apr 2019 20:31:40 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/09_movie/test_42.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/42" 2 | HTTP/1.1 200 OK 3 | Content-Length: 4 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sun, 28 Apr 2019 19:39:11 GMT 6 | Server: waitress 7 | 8 | null 9 | -------------------------------------------------------------------------------- /code/server/09_movie/test_hello.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/hello" 2 | HTTP/1.1 404 Not Found 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sun, 28 Apr 2019 21:48:49 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/14_delete/test2_3.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/3" --request DELETE 2 | HTTP/1.1 404 Not Found 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Thu, 02 May 2019 21:02:03 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/14_delete/test3_42.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/42" --request DELETE 2 | HTTP/1.1 404 Not Found 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Thu, 02 May 2019 21:02:03 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/15_forbidden/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/1" --request DELETE 2 | HTTP/1.1 403 Forbidden 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Thu, 02 May 2019 21:57:45 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/16_deploy/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { "src": "index.py", "use": "@now/python", "config": { "maxLambdaSize": "10mb" } } 5 | ], 6 | "routes": [ 7 | { "src": "/(.*)", "dest": "index.py" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /code/server/01_base/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/" 2 | HTTP/1.1 200 OK 3 | Content-Length: 44 4 | Content-Type: text/plain 5 | Date: Sun, 14 Apr 2019 20:37:56 GMT 6 | Server: waitress 7 | 8 | name: Honza 9 | surname: Javorek 10 | socks_size: 42 11 | -------------------------------------------------------------------------------- /code/server/02_data/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/" 2 | HTTP/1.1 200 OK 3 | Content-Length: 44 4 | Content-Type: text/plain 5 | Date: Sun, 14 Apr 2019 20:37:56 GMT 6 | Server: waitress 7 | 8 | name: Honza 9 | surname: Javorek 10 | socks_size: 42 11 | -------------------------------------------------------------------------------- /code/server/03_dict/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/" 2 | HTTP/1.1 200 OK 3 | Content-Length: 44 4 | Content-Type: text/plain 5 | Date: Sun, 14 Apr 2019 20:37:56 GMT 6 | Server: waitress 7 | 8 | name: Honza 9 | surname: Javorek 10 | socks_size: 42 11 | -------------------------------------------------------------------------------- /code/server/04_json/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/" 2 | HTTP/1.1 200 OK 3 | Content-Length: 59 4 | Content-Type: application/json 5 | Date: Thu, 25 Apr 2019 21:57:39 GMT 6 | Server: waitress 7 | 8 | {"name": "Honza", "surname": "Javorek", "socks_size": "42"} 9 | -------------------------------------------------------------------------------- /code/server/05_json_simplified/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/" 2 | HTTP/1.1 200 OK 3 | Content-Length: 59 4 | Content-Type: application/json 5 | Date: Thu, 25 Apr 2019 21:57:39 GMT 6 | Server: waitress 7 | 8 | {"name": "Honza", "surname": "Javorek", "socks_size": "42"} 9 | -------------------------------------------------------------------------------- /_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {% set page_title = title|striptags|e %} 4 | {% set project_title = 'cojeapi.cz' if language == 'cs' else 'whatisapi.org' %} 5 | 6 | {% block htmltitle %} 7 | {{ page_title }} — {{ project_title }} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /code/client/05_year/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | response = requests.get("https://cojeapi.honzajavorek.now.sh/movies") 4 | movies = response.json() 5 | 6 | for movie in movies: 7 | response = requests.get(movie['url']) 8 | movie_details = response.json() 9 | print(movie_details['name'], movie_details['year']) 10 | -------------------------------------------------------------------------------- /code/server/07_params/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies?name=shark" 2 | HTTP/1.1 200 OK 3 | Content-Length: 93 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sat, 27 Apr 2019 17:15:11 GMT 6 | Server: waitress 7 | 8 | [{"name": "Sharknado", "year": 2013}, {"name": "Mega Shark vs. Giant Octopus", "year": 2009}] 9 | -------------------------------------------------------------------------------- /code/server/11_repr/test_root.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/" 2 | HTTP/1.1 200 OK 3 | Content-Length: 113 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 11:21:02 GMT 6 | Server: waitress 7 | 8 | {"name": "Honza", "surname": "Javorek", "socks_size": "42", "movies_watchlist_url": "http://0.0.0.0:8080/movies"} 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -i https://pypi.org/simple 2 | 3 | # dependencies 4 | sphinx==3.3.1 5 | sphinx-tabs==1.3.0 6 | falcon==2.0.0 # used in Sphinx extensions to get status codes names 7 | 8 | # dev dependencies 9 | sphinx-autobuild==2020.9.1 10 | sphinx-rtd-theme==0.5.0 11 | pylama==7.7.1 12 | pytest==5.4.3 13 | waitress==1.4.4 14 | requests==2.25.0 15 | -------------------------------------------------------------------------------- /code/curl_version.txt: -------------------------------------------------------------------------------- 1 | $ curl --version 2 | curl 7.54.0 (x86_64-apple-darwin18.0) libcurl/7.54.0 LibreSSL/2.6.5 zlib/1.2.11 nghttp2/1.24.1 3 | Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 4 | Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy 5 | -------------------------------------------------------------------------------- /code/server/12_post/design.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/movies" --request POST --header "Content-Type: application/json" --data '{"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/"}' 2 | HTTP/1.1 200 OK 3 | Content-Type: application/json 4 | -------------------------------------------------------------------------------- /code/server/11_repr/test_movies.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies?name=shark" 2 | HTTP/1.1 200 OK 3 | Content-Length: 143 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Tue, 30 Apr 2019 19:20:16 GMT 6 | Server: waitress 7 | 8 | [{"name": "Sharknado", "url": "http://0.0.0.0:8080/movies/3"}, {"name": "Mega Shark vs. Giant Octopus", "url": "http://0.0.0.0:8080/movies/4"}] 9 | -------------------------------------------------------------------------------- /code/server/09_movie/design.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/movies/1" 2 | HTTP/1.1 200 OK 3 | Content-Type: application/json 4 | 5 | { 6 | "id": 1, 7 | "name": "The Last Boy Scout", 8 | "name_cs": "Poslední skaut", 9 | "year": 1991, 10 | "imdb_url": "https://www.imdb.com/title/tt0102266/", 11 | "csfd_url": "https://www.csfd.cz/film/8283-posledni-skaut/" 12 | } 13 | -------------------------------------------------------------------------------- /cs/_nodejs_note.rst: -------------------------------------------------------------------------------- 1 | Proč instalace zahrnuje i jakýsi `Node.js `__? Je to proto, že program now je napsán v jazyce JavaScript. Než jej tedy budeme moci nainstalovat a spustit, potřebujeme na náš počítač nejdříve doinstalovat JavaScript. Běžně se JavaScript spouští v internetovém prohlížeči, ale balík s názvem Node.js jej umožňuje používat k programování aplikací podobně jako se používá Python. 2 | -------------------------------------------------------------------------------- /code/server/06_movies/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies" 2 | HTTP/1.1 200 OK 3 | Content-Length: 196 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Fri, 26 Apr 2019 08:00:53 GMT 6 | Server: waitress 7 | 8 | [{"name": "The Last Boy Scout", "year": 1991}, {"name": "Mies vailla menneisyytt\u00e4", "year": 2002}, {"name": "Sharknado", "year": 2013}, {"name": "Mega Shark vs. Giant Octopus", "year": 2009}] 9 | -------------------------------------------------------------------------------- /cojeapi-apiary.apib: -------------------------------------------------------------------------------- 1 | # Moje API 2 | 3 | ## GET / 4 | 5 | Vypíše základní informace o mé osobě. 6 | 7 | + Response 200 (application/json) 8 | 9 | + Attributes 10 | + name: `Honza` (string) - Moje jméno 11 | + surname: `Javorek` (string) - Moje příjmení 12 | + socks_size: `42` (number) - Moje velikost ponožek 13 | + movies_watchlist_url (string) - Odkaz na seznam filmů, které chci vidět -------------------------------------------------------------------------------- /code/server/09_movie/test_1.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/1" 2 | HTTP/1.1 200 OK 3 | Content-Length: 201 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sun, 28 Apr 2019 19:20:45 GMT 6 | Server: waitress 7 | 8 | {"id": 1, "name": "The Last Boy Scout", "name_cs": "Posledn\u00ed skaut", "year": 1991, "imdb_url": "https://www.imdb.com/title/tt0102266/", "csfd_url": "https://www.csfd.cz/film/8283-posledni-skaut/"} 9 | -------------------------------------------------------------------------------- /code/server/11_repr/design.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/movies/1" 2 | HTTP/1.1 200 OK 3 | Content-Type: application/json 4 | 5 | { 6 | "url": "http://api.example.com/movies/1", 7 | "name": "The Last Boy Scout", 8 | "name_cs": "Poslední skaut", 9 | "year": 1991, 10 | "imdb_url": "https://www.imdb.com/title/tt0102266/", 11 | "csfd_url": "https://www.csfd.cz/film/8283-posledni-skaut/" 12 | } 13 | -------------------------------------------------------------------------------- /code/server/10_not_found/test_1.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/1" 2 | HTTP/1.1 200 OK 3 | Content-Length: 201 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sun, 28 Apr 2019 19:20:45 GMT 6 | Server: waitress 7 | 8 | {"id": 1, "name": "The Last Boy Scout", "name_cs": "Posledn\u00ed skaut", "year": 1991, "imdb_url": "https://www.imdb.com/title/tt0102266/", "csfd_url": "https://www.csfd.cz/film/8283-posledni-skaut/"} 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build-en: 2 | sphinx-build -nW -b html en _build 3 | 4 | serve-en: 5 | sphinx-autobuild en _build 6 | 7 | build-cs: 8 | sphinx-build -nW -b html cs _build 9 | 10 | serve-cs: 11 | sphinx-autobuild cs _build 12 | 13 | # shortcuts for -cs 14 | build: build-cs 15 | serve: serve-cs 16 | 17 | test: 18 | pytest 19 | 20 | linkcheck: 21 | sphinx-build -nW -b linkcheck en _build 22 | sphinx-build -nW -b linkcheck cs _build 23 | -------------------------------------------------------------------------------- /code/server/16_deploy/mojeapi.md: -------------------------------------------------------------------------------- 1 | # Moje API 2 | 3 | ## GET / 4 | 5 | Vypíše základní informace o mé osobě. 6 | 7 | + Response 200 (application/json) 8 | 9 | + Attributes 10 | + name: `Honza` (string) - Moje jméno 11 | + surname: `Javorek` (string) - Moje příjmení 12 | + socks_size: `42` (number) - Moje velikost ponožek 13 | + movies_watchlist_url (string) - Odkaz na seznam filmů, které chci vidět 14 | -------------------------------------------------------------------------------- /code/server/05_json_simplified/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.body = json.dumps(personal_details) 16 | 17 | 18 | app = falcon.API() 19 | app.add_route('/', PersonalDetailsResource()) 20 | -------------------------------------------------------------------------------- /code/server/12_post/test3_movie.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/5" 2 | HTTP/1.1 200 OK 3 | Content-Length: 224 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 15:03:29 GMT 6 | Server: waitress 7 | 8 | {"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/", "url": "http://0.0.0.0:8080/movies/5"} 9 | -------------------------------------------------------------------------------- /code/server/11_repr/test_movie.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies/1" 2 | HTTP/1.1 200 OK 3 | Content-Length: 231 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 12:40:00 GMT 6 | Server: waitress 7 | 8 | {"name": "The Last Boy Scout", "name_cs": "Posledn\u00ed skaut", "year": 1991, "imdb_url": "https://www.imdb.com/title/tt0102266/", "csfd_url": "https://www.csfd.cz/film/8283-posledni-skaut/", "url": "http://0.0.0.0:8080/movies/1"} 9 | -------------------------------------------------------------------------------- /code/server/12_post/test1_post_unix.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies" --request POST --header "Content-Type: application/json" --data '{"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/"}' 2 | HTTP/1.1 200 OK 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 15:18:44 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/01_base/index.py: -------------------------------------------------------------------------------- 1 | import falcon 2 | 3 | 4 | class PersonalDetailsResource(): 5 | 6 | def on_get(self, request, response): 7 | response.status = '200 OK' 8 | response.set_header('Content-Type', 'text/plain') 9 | response.body = ( 10 | 'name: Honza\n' 11 | 'surname: Javorek\n' 12 | 'socks_size: 42\n' 13 | ) 14 | 15 | 16 | app = falcon.API() 17 | app.add_route('/', PersonalDetailsResource()) 18 | -------------------------------------------------------------------------------- /code/server/12_post/test1_post_win.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://MY-COMPUTER:8080/movies" --request POST --header "Content-Type: application/json" --data "{\"name\": \"New Kids Turbo\", \"name_cs\": \"New Kids Turbo\", \"year\": 2010, \"imdb_url\": \"https://www.imdb.com/title/tt1648112/\", \"csfd_url\": \"https://www.csfd.cz/film/295395-new-kids-turbo/\"}" 2 | HTTP/1.1 200 OK 3 | Content-Length: 0 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 15:18:44 GMT 6 | Server: waitress 7 | -------------------------------------------------------------------------------- /code/server/02_data/index.py: -------------------------------------------------------------------------------- 1 | import falcon 2 | 3 | 4 | personal_details = ( 5 | 'name: Honza\n' 6 | 'surname: Javorek\n' 7 | 'socks_size: 42\n' 8 | ) 9 | 10 | 11 | class PersonalDetailsResource(): 12 | 13 | def on_get(self, request, response): 14 | response.status = '200 OK' 15 | response.set_header('Content-Type', 'text/plain') 16 | response.body = personal_details 17 | 18 | 19 | app = falcon.API() 20 | app.add_route('/', PersonalDetailsResource()) 21 | -------------------------------------------------------------------------------- /code/server/04_json/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.status = '200 OK' 16 | response.set_header('Content-Type', 'application/json') 17 | response.body = json.dumps(personal_details) 18 | 19 | 20 | app = falcon.API() 21 | app.add_route('/', PersonalDetailsResource()) 22 | -------------------------------------------------------------------------------- /code/client/01_base/test.txt: -------------------------------------------------------------------------------- 1 | $ python client.py 2 | 200 3 | {'Date': 'Sat, 04 May 2019 12:10:32 GMT', 'Content-Type': 'application/json', 'Connection': 'keep-alive', 'content-length': '129', 'cache-control': 'public, max-age=0, must-revalidate', 'x-now-cache': 'MISS', 'x-now-trace': 'zrh1', 'server': 'now', 'x-now-id': 'xdnnz-1556971832381-676f32a6396e2c1aff6d1100d3a5fd54', 'strict-transport-security': 'max-age=63072000'} 4 | {"name": "Honza", "surname": "Javorek", "socks_size": "42", "movies_watchlist_url": "https://cojeapi.honzajavorek.now.sh/movies"} 5 | -------------------------------------------------------------------------------- /code/client/03_json/test.txt: -------------------------------------------------------------------------------- 1 | $ python client.py 2 | 200 3 | {'Date': 'Sat, 04 May 2019 12:15:59 GMT', 'Content-Type': 'application/json', 'Connection': 'keep-alive', 'content-length': '129', 'cache-control': 'public, max-age=0, must-revalidate', 'x-now-cache': 'MISS', 'x-now-trace': 'zrh1', 'server': 'now', 'x-now-id': 'xdnnz-1556972159138-9eb3b82b14895f7f03b76b8c336b6ca1', 'strict-transport-security': 'max-age=63072000'} 4 | {'name': 'Honza', 'surname': 'Javorek', 'socks_size': '42', 'movies_watchlist_url': 'https://cojeapi.honzajavorek.now.sh/movies'} 5 | -------------------------------------------------------------------------------- /_extensions/codeexample.py: -------------------------------------------------------------------------------- 1 | from docutils import nodes 2 | 3 | 4 | GITHUB_LINK = ('https://github.com/honzajavorek/cojeapi/' 5 | 'blob/master/code/{path}') 6 | 7 | 8 | def code(name, rawtext, text, lineno, inliner, 9 | options=None, content=None): 10 | url = GITHUB_LINK.format(path=text) 11 | node = nodes.reference(rawtext, text, refuri=url, **(options or {})) 12 | return [node], [] 13 | 14 | 15 | def setup(app): 16 | app.add_role('codeexample', code) 17 | return {'version': '1.0', 'parallel_read_safe': True} 18 | -------------------------------------------------------------------------------- /code/client/06_newest/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | response = requests.get("https://cojeapi.honzajavorek.now.sh/movies") 4 | movies = response.json() 5 | 6 | newest_movie = None 7 | for movie in movies: 8 | response = requests.get(movie['url']) 9 | movie_details = response.json() 10 | 11 | if newest_movie is None: 12 | newest_movie = movie_details 13 | elif newest_movie['year'] < movie_details['year']: 14 | newest_movie = movie_details 15 | 16 | print('Nejnovější film:', newest_movie['name'], newest_movie['year']) 17 | -------------------------------------------------------------------------------- /code/client/02_json_error/test.txt: -------------------------------------------------------------------------------- 1 | $ python client.py 2 | 200 3 | {'Date': 'Sat, 04 May 2019 12:19:48 GMT', 'Content-Type': 'application/json', 'Connection': 'keep-alive', 'content-length': '129', 'cache-control': 'public, max-age=0, must-revalidate', 'x-now-cache': 'MISS', 'x-now-trace': 'zrh1', 'server': 'now', 'x-now-id': 'mmxqr-1556972388219-44d1b59d32681aa7074f73fcc1182fbd', 'strict-transport-security': 'max-age=63072000'} 4 | Traceback (most recent call last): 5 | File "client.py", line 7, in 6 | print(response.text['surname']) 7 | TypeError: string indices must be integers 8 | -------------------------------------------------------------------------------- /code/server/12_post/test2_get.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies" 2 | HTTP/1.1 200 OK 3 | Content-Length: 363 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 15:30:27 GMT 6 | Server: waitress 7 | 8 | [{"name": "The Last Boy Scout", "url": "http://0.0.0.0:8080/movies/1"}, {"name": "Mies vailla menneisyytt\u00e4", "url": "http://0.0.0.0:8080/movies/2"}, {"name": "Sharknado", "url": "http://0.0.0.0:8080/movies/3"}, {"name": "Mega Shark vs. Giant Octopus", "url": "http://0.0.0.0:8080/movies/4"}, {"name": "New Kids Turbo", "url": "http://0.0.0.0:8080/movies/5"}] 9 | -------------------------------------------------------------------------------- /code/server/03_dict/index.py: -------------------------------------------------------------------------------- 1 | import falcon 2 | 3 | 4 | personal_details = { 5 | 'name': 'Honza', 6 | 'surname': 'Javorek', 7 | 'socks_size': '42', 8 | } 9 | 10 | 11 | class PersonalDetailsResource(): 12 | 13 | def on_get(self, request, response): 14 | response.status = '200 OK' 15 | response.set_header('Content-Type', 'text/plain') 16 | 17 | body = '' 18 | for key, value in personal_details.items(): 19 | body += '{0}: {1}\n'.format(key, value) 20 | response.body = body 21 | 22 | 23 | app = falcon.API() 24 | app.add_route('/', PersonalDetailsResource()) 25 | -------------------------------------------------------------------------------- /code/server/13_created/design.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://api.example.com/movies" --request POST --header "Content-Type: application/json" --data '{"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/"}' 2 | HTTP/1.1 201 Created 3 | Content-Type: application/json 4 | Location: http://api.example.com/movies/42 5 | 6 | { 7 | "url": "http://api.example.com/movies/42", 8 | "name": "New Kids Turbo", 9 | "name_cs": "New Kids Turbo", 10 | "year": 1992, 11 | "imdb_url": "https://www.imdb.com/title/tt1648112/", 12 | "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/" 13 | } 14 | -------------------------------------------------------------------------------- /code/server/13_created/test_unix.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies" --request POST --header "Content-Type: application/json" --data '{"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/"}' 2 | HTTP/1.1 201 Created 3 | Content-Length: 224 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 16:30:48 GMT 6 | Location: http://0.0.0.0:8080/movies/5 7 | Server: waitress 8 | 9 | {"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/", "url": "http://0.0.0.0:8080/movies/5"} 10 | -------------------------------------------------------------------------------- /cs/07-co-dal.rst: -------------------------------------------------------------------------------- 1 | Co dál? 2 | ======== 3 | 4 | .. todo:: 5 | smyslem neni kompletni vseobjimajici navod, ale jen uvod 6 | je potreba umet anglicky 7 | zminit jak lze do tohoto seznamu neco pridat (contributing), primej odkaz 8 | 9 | Zajímavé zdroje 10 | --------------- 11 | 12 | .. todo:: 13 | odkazy na pokrocilejsi veci, i v anglictine 14 | vysosat neco z https://github.com/honzajavorek/jakpsatapi 15 | a https://gist.github.com/honzajavorek/02b7205bc2b59247d541 16 | 17 | Knihy 18 | ----- 19 | 20 | .. todo:: 21 | phil, mike 22 | 23 | Komunity 24 | -------- 25 | 26 | .. todo:: 27 | api-craft, apisyouwonthate 28 | 29 | Osobnosti 30 | --------- 31 | 32 | .. todo:: 33 | Phil S., Mike A., Kin Lane, Steve K., Z, ... 34 | -------------------------------------------------------------------------------- /code/server/13_created/test_win.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://MY-COMPUTER:8080/movies" --request POST --header "Content-Type: application/json" --data "{\"name\": \"New Kids Turbo\", \"name_cs\": \"New Kids Turbo\", \"year\": 2010, \"imdb_url\": \"https://www.imdb.com/title/tt1648112/\", \"csfd_url\": \"https://www.csfd.cz/film/295395-new-kids-turbo/\"}" 2 | HTTP/1.1 201 Created 3 | Content-Length: 224 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Wed, 01 May 2019 16:30:48 GMT 6 | Location: http://MY-COMPUTER:8080/movies/5 7 | Server: waitress 8 | 9 | {"name": "New Kids Turbo", "name_cs": "New Kids Turbo", "year": 2010, "imdb_url": "https://www.imdb.com/title/tt1648112/", "csfd_url": "https://www.csfd.cz/film/295395-new-kids-turbo/", "url": "http://0.0.0.0:8080/movies/5"} 10 | -------------------------------------------------------------------------------- /code/server/06_movies/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.body = json.dumps(personal_details) 16 | 17 | 18 | movies = [ 19 | {'name': 'The Last Boy Scout', 'year': 1991}, 20 | {'name': 'Mies vailla menneisyyttä', 'year': 2002}, 21 | {'name': 'Sharknado', 'year': 2013}, 22 | {'name': 'Mega Shark vs. Giant Octopus', 'year': 2009}, 23 | ] 24 | 25 | 26 | class MovieListResource(): 27 | 28 | def on_get(self, request, response): 29 | response.body = json.dumps(movies) 30 | 31 | 32 | app = falcon.API() 33 | app.add_route('/', PersonalDetailsResource()) 34 | app.add_route('/movies', MovieListResource()) 35 | -------------------------------------------------------------------------------- /code/curl_omdb.txt: -------------------------------------------------------------------------------- 1 | $ curl "https://www.omdbapi.com/?t=westworld&apikey=abcd123" 2 | {"Title":"Westworld","Year":"2016–","Rated":"TV-MA","Released":"02 Oct 2016","Runtime":"62 min","Genre":"Drama, Mystery, Sci-Fi, Western","Director":"N/A","Writer":"N/A","Actors":"Evan Rachel Wood, Thandie Newton, Jeffrey Wright, Ed Harris","Plot":"Set at the intersection of the near future and the reimagined past, explore a world in which every human appetite can be indulged without consequence.","Language":"English","Country":"USA","Awards":"Nominated for 3 Golden Globes. Another 29 wins & 75 nominations.","Poster":"https://m.media-amazon.com/images/M/MV5BNThjM2Y3MDUtYTIyNC00ZDliLWJlMmItNWY1N2E5NjhmMGM4XkEyXkFqcGdeQXVyNjU2ODM5MjU@._V1_SX300.jpg","Ratings":[{"Source":"Internet Movie Database","Value":"8.8/10"}],"Metascore":"N/A","imdbRating":"8.8","imdbVotes":"334,034","imdbID":"tt0475784","Type":"series","totalSeasons":"3","Response":"True"} 3 | -------------------------------------------------------------------------------- /cs/_author.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://www.gravatar.com/avatar/7b2e4bf7ecca28e530e1c421f0676c0b?s=200 2 | :alt: Honza Javorek 3 | :align: left 4 | :width: 160px 5 | 6 | Materiály píše `Honza Javorek `__. Od roku 2011 pomáhá budovat českou komunitu kolem jazyka `Python `__. V `Apiary `__ se staral o `Dredd `__, nástroj na testování webových API. Založil `junior.guru `__, průvodce programováním pro samouky. Pokud vám jeho projekty pomohly, můžete mu finančně přispět na `Patreonu `_, `GitHubu `_, nebo i `přímo na účet `_. 7 | 8 | `LinkedIn `_ 9 | ・ `GitHub `_ 10 | ・ `Twitter `_ 11 | -------------------------------------------------------------------------------- /cs/06-web-jako-api.rst: -------------------------------------------------------------------------------- 1 | Web jako API 2 | ============ 3 | 4 | .. warning:: 5 | Tato kapitola nebyla zatím připravena. 6 | 7 | .. todo:: 8 | webova API, hm hm 9 | prohlizec jako klient, hm hm 10 | webove frameworky (django) na tvorbu API, hm hm 11 | 12 | co ma vlastne api spolecneho s webem? 13 | web, html stranky, jsou webove api, ktere je ale urceno pro cloveka 14 | clovek umi premyslet, takze nepotrebuje jasnou a stabilni strukturu, zorientuje se 15 | 16 | nektere veci nemaji api 17 | muzeme zkusit program naucit s pracovat primo s webem 18 | rika se tomu scraping, pripadne crawling (googlebot, atd.) 19 | 20 | rozbiji se to 21 | je to slozitejsi, musi se osetrit spousta veci 22 | je to na hrane zakona (data si bereme hromadne bez jasneho dovoleni, moznost prekroceni zakona - kradeni databaze, copyright, apod.) 23 | 24 | nakej priklad (requests, lxml) 25 | knihovny pro scraping (scrapy) 26 | 27 | -------------------------------------------------------------------------------- /code/server/08_db/test.txt: -------------------------------------------------------------------------------- 1 | $ curl -i "http://0.0.0.0:8080/movies" 2 | HTTP/1.1 200 OK 3 | Content-Length: 875 4 | Content-Type: application/json; charset=UTF-8 5 | Date: Sun, 28 Apr 2019 18:35:00 GMT 6 | Server: waitress 7 | 8 | [{"id": 1, "name": "The Last Boy Scout", "name_cs": "Posledn\u00ed skaut", "year": 1991, "imdb_url": "https://www.imdb.com/title/tt0102266/", "csfd_url": "https://www.csfd.cz/film/8283-posledni-skaut/"}, {"id": 2, "name": "Mies vailla menneisyytt\u00e4", "name_cs": "Mu\u017e bez minulosti", "year": 2002, "imdb_url": "https://www.imdb.com/title/tt0311519/", "csfd_url": "https://www.csfd.cz/film/35366-muz-bez-minulosti/"}, {"id": 3, "name": "Sharknado", "name_cs": "\u017dralokon\u00e1do", "year": 2013, "imdb_url": "https://www.imdb.com/title/tt2724064/", "csfd_url": "https://www.csfd.cz/film/343017-zralokonado/"}, {"id": 4, "name": "Mega Shark vs. Giant Octopus", "name_cs": "Mega\u017eralok vs. ob\u0159\u00ed chobotnice", "year": 2009, "imdb_url": "https://www.imdb.com/title/tt1350498/", "csfd_url": "https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/"}] 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Co je API?

2 |

Materiály, díky kterým pochopíte API

3 | 4 | Když si jako laik na Wikipedii najdete [článek o API](https://cs.wikipedia.org/wiki/API), nejspíš z něj moudří nebudete. Tyto materiály, nazvané [Co je API?](https://cojeapi.cz), se snaží API vysvětlit běžným lidem a zároveň umožnit začátečníkům v programování, aby se s API naučili pracovat. 5 | 6 | ------ 7 | 8 | #### 🚧 Upozornění 9 | 10 | Materiály nejsou dokončené a jejich čtení je zatím na vlastní nebezpečí. Zatím se podle nich konaly dva [PyWorking workshopy](https://pyworking.cz/) a v rámci nich vznikla [spousta připomínek a nápadů na vylepšení](https://github.com/honzajavorek/cojeapi/issues). Jakákoliv pomoc v dokončování materiálů nebo řešení připomínek je vítána. 11 | 12 | #### 🇦🇺 Překlad 13 | 14 | Na [této adrese](https://whatisapi.org/) a v [této složce](en) vzniká překlad materiálů do angličtiny. Pokud máte zájem pomoci, napište prosím pod [#52](https://github.com/honzajavorek/cojeapi/issues/52). Protože však zatím nejsou ani české materiály dotažené do nějaké rozumné podoby, je vhodné s překladem spíše počkat. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Honza Javorek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /cs/_win_json_note.rst: -------------------------------------------------------------------------------- 1 | .. note:: 2 | 3 | JSON se musí psát vždy s dvojitými uvozovkami. Na Linuxu nebo macOS to není problém, protože v příkazové řádce fungují na ohraničení i jednoduché uvozovky a lze udělat následující: 4 | 5 | .. code-block:: text 6 | 7 | --data '{"message": "Ahoj"}' 8 | 9 | Na Windows bohužel nelze jednoduché uvozovky použít, k dispozici jsou pouze dvojité a ty kolidují s těmi v JSONu. Proto musíme v celém JSONu přepsat uvozovky na ``\"``: 10 | 11 | .. code-block:: text 12 | 13 | --data "{\"message\": \"Ahoj\"}" 14 | 15 | Další nepříjemností je skutečnost, že kvůli specifickému kódování textu v příkazové řádce na Windows není jednoduché posílat data, která budou obsahovat diakritiku. Následující tedy nebude fungovat: 16 | 17 | .. code-block:: text 18 | 19 | --data "{\"message\": \"Čauky mňauky\"}" 20 | 21 | V tomto návodu přidáme před uvozovky lomítka a diakritice se vyhneme. V praxi se dají oba problémy řešit tím, že data nebudeme psát přímo do příkazové řádky, ale uložíme si je vedle do souboru a řekneme programu curl, aby je z něj načetl: 22 | 23 | .. code-block:: text 24 | 25 | --data @new-movie.json 26 | -------------------------------------------------------------------------------- /code/curl_cnb.txt: -------------------------------------------------------------------------------- 1 | $ curl "https://www.cnb.cz/cs/financni-trhy/devizovy-trh/kurzy-devizoveho-trhu/kurzy-devizoveho-trhu/denni_kurz.txt" 2 | 10.05.2019 #89 3 | země|měna|množství|kód|kurz 4 | Austrálie|dolar|1|AUD|16,023 5 | Brazílie|real|1|BRL|5,796 6 | Bulharsko|lev|1|BGN|13,157 7 | Čína|žen-min-pi|1|CNY|3,359 8 | Dánsko|koruna|1|DKK|3,446 9 | EMU|euro|1|EUR|25,730 10 | Filipíny|peso|100|PHP|43,823 11 | Hongkong|dolar|1|HKD|2,920 12 | Chorvatsko|kuna|1|HRK|3,473 13 | Indie|rupie|100|INR|32,737 14 | Indonesie|rupie|1000|IDR|1,600 15 | Island|koruna|100|ISK|18,781 16 | Izrael|nový šekel|1|ILS|6,433 17 | Japonsko|jen|100|JPY|20,876 18 | Jižní Afrika|rand|1|ZAR|1,610 19 | Kanada|dolar|1|CAD|17,005 20 | Korejská republika|won|100|KRW|1,942 21 | Maďarsko|forint|100|HUF|7,953 22 | Malajsie|ringgit|1|MYR|5,510 23 | Mexiko|peso|1|MXN|1,194 24 | MMF|ZPČ|1|XDR|31,708 25 | Norsko|koruna|1|NOK|2,620 26 | Nový Zéland|dolar|1|NZD|15,112 27 | Polsko|zlotý|1|PLN|5,990 28 | Rumunsko|leu|1|RON|5,406 29 | Rusko|rubl|100|RUB|35,081 30 | Singapur|dolar|1|SGD|16,812 31 | Švédsko|koruna|1|SEK|2,379 32 | Švýcarsko|frank|1|CHF|22,619 33 | Thajsko|baht|100|THB|72,557 34 | Turecko|lira|1|TRY|3,738 35 | USA|dolar|1|USD|22,915 36 | Velká Británie|libra|1|GBP|29,835 37 | -------------------------------------------------------------------------------- /code/server/07_params/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.body = json.dumps(personal_details) 16 | 17 | 18 | movies = [ 19 | {'name': 'The Last Boy Scout', 'year': 1991}, 20 | {'name': 'Mies vailla menneisyyttä', 'year': 2002}, 21 | {'name': 'Sharknado', 'year': 2013}, 22 | {'name': 'Mega Shark vs. Giant Octopus', 'year': 2009}, 23 | ] 24 | 25 | 26 | def filter_movies(movies, name): 27 | if name is not None: 28 | filtered_movies = [] 29 | for movie in movies: 30 | if name in movie['name'].lower(): 31 | filtered_movies.append(movie) 32 | return filtered_movies 33 | else: 34 | return movies 35 | 36 | 37 | class MovieListResource(): 38 | 39 | def on_get(self, request, response): 40 | name = request.get_param('name') 41 | response.body = json.dumps(filter_movies(movies, name)) 42 | 43 | 44 | app = falcon.API() 45 | app.add_route('/', PersonalDetailsResource()) 46 | app.add_route('/movies', MovieListResource()) 47 | -------------------------------------------------------------------------------- /cs/index.rst: -------------------------------------------------------------------------------- 1 | Co je API? 2 | ========== 3 | 4 | Když si jako laik na Wikipedii najdete `článek o API `__, nejspíš z něj moudří nebudete. Tyto materiály, nazvané `Co je API? `__, se snaží API vysvětlit běžným lidem a zároveň umožnit začátečníkům v programování, aby se s API naučili pracovat. 5 | 6 | .. image:: ../_static/images/spotify.png 7 | :align: center 8 | :width: 60% 9 | 10 | 11 | Obsah 12 | ----- 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | 01-uvod-do-api 18 | 02-klient-server 19 | 03-zakladni-pojmy 20 | 04-tvorime-server 21 | 05-tvorime-klienta 22 | 06-web-jako-api 23 | 07-co-dal 24 | 25 | .. toctree:: 26 | :hidden: 27 | 28 | contributing 29 | workshop 30 | 31 | 32 | Přidejte ruku k dílu 33 | -------------------- 34 | 35 | Materiály jsou tvořeny jako `otevřené `__, do jejich zdrojového kódu a textů :ref:`může kdokoliv navrhovat změny `. Texty a obrázky těchto materiálů jsou uvolněny pod licencí `CC BY-SA 4.0 `__. Původním autorem je :ref:`Honza Javorek `. 36 | 37 | 38 | .. _honzajavorek-cs: 39 | 40 | Autor 41 | ----- 42 | 43 | .. include:: _author.rst 44 | -------------------------------------------------------------------------------- /en/index.rst: -------------------------------------------------------------------------------- 1 | What is API? 2 | ============ 3 | 4 | .. warning:: 5 | 6 | This is an attempt to translate `Co je API? `__ materials to English. It is a very early edition and there's almost nothing done yet. If you want to help, please coordinate `here `__. 7 | 8 | 9 | Contents 10 | -------- 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | 01-api-intro 16 | 17 | .. toctree:: 18 | :hidden: 19 | 20 | contributing 21 | 22 | 23 | .. _honzajavorek-en: 24 | 25 | Author 26 | ------ 27 | 28 | .. image:: https://www.gravatar.com/avatar/7b2e4bf7ecca28e530e1c421f0676c0b?s=120 29 | :alt: Honza Javorek 30 | :align: left 31 | :width: 120px 32 | 33 | `Honza Javorek `__ is a software engineer. Since 2011 he has been helping to drive the grow and success of the `Czech Python User Group `__. In `Apiary `__ he used to take care of the `Dredd `__, a tool to test web APIs. He founded `junior.guru `__, a project helping people to learn to code and to get their first job in tech. 34 | 35 | `LinkedIn `_ 36 | ・ `GitHub `_ 37 | ・ `Twitter `_ 38 | -------------------------------------------------------------------------------- /_extensions/mdn.py: -------------------------------------------------------------------------------- 1 | import falcon 2 | from docutils import nodes 3 | 4 | 5 | MDN_LINK = 'https://developer.mozilla.org/en-US/docs/Web/HTTP/{path}' 6 | 7 | 8 | def status(name, rawtext, text, lineno, inliner, 9 | options=None, content=None): 10 | try: 11 | code = int(text) 12 | if code < 100 or code >= 600: 13 | raise ValueError 14 | name = getattr(falcon, 'HTTP_' + text) 15 | 16 | except (ValueError, AttributeError): 17 | message = "Invalid HTTP status code: '{}'".format(text) 18 | error = inliner.reporter.error(message, line=lineno) 19 | problematic = inliner.problematic(rawtext, rawtext, error) 20 | return [problematic], [error] 21 | 22 | url = MDN_LINK.format(path='Status/' + text) 23 | node = nodes.reference(rawtext, name, refuri=url, **(options or {})) 24 | return [node], [] 25 | 26 | 27 | def header(name, rawtext, text, lineno, inliner, 28 | options=None, content=None): 29 | url = MDN_LINK.format(path='Headers/' + text) 30 | node = nodes.reference(rawtext, text, refuri=url, **(options or {})) 31 | return [node], [] 32 | 33 | 34 | def method(name, rawtext, text, lineno, inliner, 35 | options=None, content=None): 36 | name = text.upper() 37 | url = MDN_LINK.format(path='Methods/' + name) 38 | node = nodes.reference(rawtext, name, refuri=url, **(options or {})) 39 | return [node], [] 40 | 41 | 42 | def setup(app): 43 | app.add_role('status', status) 44 | app.add_role('header', header) 45 | app.add_role('method', method) 46 | return {'version': '1.0', 'parallel_read_safe': True} 47 | -------------------------------------------------------------------------------- /cs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _jak-prispivat: 2 | 3 | Jak přispívat? 4 | ============== 5 | 6 | Našli jste chybu? Chtěli byste něco doplnit? Následující odstavce 7 | popisují, jak lze materiály upravovat a návrhy na změny posílat autorům. 8 | 9 | Rychlé úpravy bez instalace 10 | --------------------------- 11 | 12 | Abyste něco změnili v textech, nemusíte nic instalovat. Obsah lze upravovat online přes `repozitář na GitHubu `__ pomocí ikony s tužkou v pravém horním rohu u každého souboru. 13 | 14 | Instalace 15 | --------- 16 | 17 | Když toho upravujete víc, nebo máte zálusk na nějaké složitější kejkle, je lepší mít materiály nainstalované na svém počítači. Vytvořte si virtualenv s Pythonem 3 a nainstalujte závislosti standardně pomocí ``pip install -r requirements.txt``. 18 | 19 | Běžná práce 20 | ----------- 21 | 22 | #. ``make serve`` 23 | #. Otevřete si v prohlížeči ``__ 24 | #. V editoru upravujete texty a v prohlížeči si kontrolujete výsledek 25 | 26 | Testy 27 | ----- 28 | 29 | .. todo:: 30 | Sem napsat něco o testech. 31 | 32 | Continuous Integration 33 | ---------------------- 34 | 35 | Na repozitáři je zapojeno `CircleCI `__. Kontroluje různé věci v rámci projektu. Kontrolka: 36 | 37 | .. image:: https://circleci.com/gh/honzajavorek/cojeapi/tree/master.svg?style=svg 38 | :target: https://circleci.com/gh/honzajavorek/cojeapi/tree/master 39 | :alt: Continuous Integration Status 40 | 41 | Pokud se něco nepovedlo, podrobnosti lze zjistit `zde `__. 42 | 43 | ZEIT Now 44 | -------- 45 | 46 | .. todo:: 47 | Sem napsat něco o deploymentu. 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | _build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # scripts/lint_requirements.sh 107 | *-requirements.txt 108 | 109 | # ZEIT Now 110 | .now 111 | -------------------------------------------------------------------------------- /_static/css/cojeapi.css: -------------------------------------------------------------------------------- 1 | /* 2 | Minor tuning to get the Font-Awesome icons used in buttons slightly padded. 3 | */ 4 | .btn .fa { 5 | margin-right: 0.3rem; 6 | } 7 | .btn.float-right .fa { 8 | margin-right: 0; 9 | margin-left: 0.3rem; 10 | } 11 | 12 | /* 13 | The :rfc: role renders bold for no obvious reason. This resets it so 14 | the links don't stand out. 15 | */ 16 | a.rfc strong { 17 | font-weight: normal; 18 | } 19 | 20 | /* 21 | Inline code renders bold for no obvious reason when in
. This resets it 22 | so it doesn't stand out. 23 | */ 24 | .rst-content dl:not(.docutils) code { 25 | font-weight: normal; 26 | } 27 | 28 | /* 29 | Workaround for https://github.com/djungelorm/sphinx-tabs/pull/35 30 | */ 31 | .rst-content .section ul:last-child, 32 | .rst-content .toctree-wrapper ul:last-child, 33 | article ul:last-child { 34 | margin-bottom: 24px; 35 | } 36 | .rst-content .tab ul:last-child { 37 | margin-bottom: 0; 38 | } 39 | 40 | /* 41 | Workaround for https://github.com/djungelorm/sphinx-tabs/issues/38 42 | */ 43 | .sphinx-tabs .sphinx-menu .item p { 44 | margin: 0; 45 | } 46 | 47 | /* 48 |
is hard to recognize 49 | */ 50 | blockquote { 51 | color: gray; 52 | } 53 | 54 | /* 55 | Distinguishing proposed "examples" from real "tests" by a blueprint-like 56 | background (taken from https://codepen.io/tennowman/pen/ynrih) 57 | */ 58 | .design .highlight { 59 | background-color: #2980B9; 60 | background-image: 61 | linear-gradient(rgba(255,255,255,.1) 2px, transparent 2px), 62 | linear-gradient(90deg, rgba(255,255,255,.1) 2px, transparent 2px), 63 | linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px), 64 | linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px); 65 | background-size: 80px 80px, 80px 80px, 10px 10px, 10px 10px; 66 | background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px; 67 | } 68 | 69 | .design .highlight .hll { 70 | background: none; 71 | background-color: rgba(255,255,255,.2); 72 | } 73 | -------------------------------------------------------------------------------- /cs/workshop.rst: -------------------------------------------------------------------------------- 1 | .. _workshop: 2 | 3 | Workshop „Webová API“ 4 | ===================== 5 | 6 | Podle materiálů `cojeapi.cz `__ se občas koná `PyWorking `__ workshop. 7 | 8 | Co očekávat 9 | ----------- 10 | 11 | Workshop není veden jako výuka ve škole. Budeš si samostatně procházet materiály krok za krokem, vlastním tempem. Pokud budeš mít problém nebo dotaz, na místě jsou k ruce ochotní koučové. 12 | 13 | Nevýhody 14 | ^^^^^^^^ 15 | 16 | - celý den čtení a samostudia 17 | - pocit, že sis to mohla přečíst i doma 18 | 19 | Výhody 20 | ^^^^^^ 21 | 22 | - doma se k tomu nedokopeš a toto je čas určený jenom na to 23 | - skupinová dynamika, společné jásání na konci 24 | - koučové ochotní ti s čímkoliv pomoci nebo ti cokoliv zodpovědět 25 | 26 | Obsah workshopu 27 | ^^^^^^^^^^^^^^^ 28 | 29 | Materiály nemají dokončenou :ref:`část o tvorbě klienta ` a i kdyby dokončená byla, nejspíš by ani nebylo dost času tuto část projít. Přitom je to přesně to, co je pro začátečníka praktické umět. Na druhou stranu, :ref:`tvorba serveru ` je to složitější, co se nenaučíte ze Stack Overflow. Pomůže vám to pochopit API z druhé strany, do hloubky, a vše si následně budete umět lépe představit. Dostudovat si s touto znalostí dotazování API přes klienta není zas takový problém. 30 | 31 | Autor materiálů 32 | --------------- 33 | 34 | .. include:: _author.rst 35 | 36 | Co budeš potřebovat 37 | ------------------- 38 | 39 | Potřebuješ už mít za sebou základní kurz z `naucse.python.cz `_ (tzn. začátečnický PyLadies kurz). 40 | 41 | Je dobré mít už před workshopem nainstalované programy :ref:`curl ` a :ref:`Now `. Pokud by byl s instalací problém, napiš organizátorkám workshopu, aby o tobě věděly, a přijď dřív - koučové ti ještě před začátkem pomůžou. 42 | 43 | .. _workshop-curl: 44 | 45 | Instalace programu curl 46 | ^^^^^^^^^^^^^^^^^^^^^^^ 47 | 48 | .. include:: _install_curl.rst 49 | 50 | .. _workshop-now: 51 | 52 | Instalace programu now 53 | ^^^^^^^^^^^^^^^^^^^^^^ 54 | 55 | .. include:: _install_now.rst 56 | -------------------------------------------------------------------------------- /code/server/08_db/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.body = json.dumps(personal_details) 16 | 17 | 18 | movies = [ 19 | { 20 | 'id': 1, 21 | 'name': 'The Last Boy Scout', 22 | 'name_cs': 'Poslední skaut', 23 | 'year': 1991, 24 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 25 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 26 | }, 27 | { 28 | 'id': 2, 29 | 'name': 'Mies vailla menneisyyttä', 30 | 'name_cs': 'Muž bez minulosti', 31 | 'year': 2002, 32 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 33 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 34 | }, 35 | { 36 | 'id': 3, 37 | 'name': 'Sharknado', 38 | 'name_cs': 'Žralokonádo', 39 | 'year': 2013, 40 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 41 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 42 | }, 43 | { 44 | 'id': 4, 45 | 'name': 'Mega Shark vs. Giant Octopus', 46 | 'name_cs': 'Megažralok vs. obří chobotnice', 47 | 'year': 2009, 48 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 49 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 50 | }, 51 | ] 52 | 53 | 54 | def filter_movies(movies, name): 55 | if name is not None: 56 | filtered_movies = [] 57 | for movie in movies: 58 | if name in movie['name'].lower(): 59 | filtered_movies.append(movie) 60 | return filtered_movies 61 | else: 62 | return movies 63 | 64 | 65 | class MovieListResource(): 66 | 67 | def on_get(self, request, response): 68 | name = request.get_param('name') 69 | response.body = json.dumps(filter_movies(movies, name)) 70 | 71 | 72 | app = falcon.API() 73 | app.add_route('/', PersonalDetailsResource()) 74 | app.add_route('/movies', MovieListResource()) 75 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | win: "circleci/windows@2.2.0" 5 | 6 | common: 7 | install: &install 8 | run: | 9 | python -m venv ./venv 10 | ./venv/bin/pip install -r requirements.txt 11 | 12 | jobs: 13 | test-linux: 14 | docker: 15 | - image: "circleci/python:3.8" 16 | working_directory: "~/repo" 17 | steps: 18 | - checkout 19 | - *install 20 | - run: | 21 | source ./venv/bin/activate 22 | make test 23 | 24 | test-windows: 25 | executor: "win/default" 26 | working_directory: "~/repo" 27 | steps: 28 | - checkout 29 | - run: "choco install -y python3 make" 30 | - run: | 31 | python -m venv .\venv 32 | .\venv\Scripts\activate 33 | python -m pip install -r requirements.txt 34 | - run: | 35 | .\venv\Scripts\activate 36 | make test 37 | 38 | linkcheck: 39 | docker: 40 | - image: "circleci/python:3.8" 41 | working_directory: "~/repo" 42 | steps: 43 | - checkout 44 | - *install 45 | - run: | 46 | source ./venv/bin/activate 47 | make linkcheck 48 | 49 | # NOW_TOKEN needs to be set in CircleCI project settings 50 | deploy: 51 | docker: 52 | - image: "circleci/python:3.8-node" 53 | working_directory: "~/repo" 54 | steps: 55 | - checkout 56 | - *install 57 | - run: 58 | name: "Installing now.sh" 59 | command: "sudo npm install --global --unsafe-perm now@16" 60 | - run: 61 | name: "Building & Deploying cojeapi.cz" 62 | command: | 63 | source ./venv/bin/activate 64 | make build-cs 65 | now ./_build --name=cojeapi-docs-cs --prod --token=$NOW_TOKEN 66 | - run: 67 | name: "Building & Deploying whatisapi.org" 68 | command: | 69 | source ./venv/bin/activate 70 | make build-en 71 | now ./_build --name=cojeapi-docs-en --prod --token=$NOW_TOKEN 72 | - run: 73 | name: "Deploying Server" 74 | command: "now ./code/server/16_deploy --name=cojeapi --token=$NOW_TOKEN" 75 | 76 | workflows: 77 | version: 2 78 | build: 79 | jobs: 80 | - test-linux 81 | - test-windows 82 | - linkcheck 83 | - deploy: 84 | requires: 85 | - test-linux 86 | - test-windows 87 | filters: 88 | branches: 89 | only: master 90 | -------------------------------------------------------------------------------- /code/server/09_movie/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.body = json.dumps(personal_details) 16 | 17 | 18 | movies = [ 19 | { 20 | 'id': 1, 21 | 'name': 'The Last Boy Scout', 22 | 'name_cs': 'Poslední skaut', 23 | 'year': 1991, 24 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 25 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 26 | }, 27 | { 28 | 'id': 2, 29 | 'name': 'Mies vailla menneisyyttä', 30 | 'name_cs': 'Muž bez minulosti', 31 | 'year': 2002, 32 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 33 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 34 | }, 35 | { 36 | 'id': 3, 37 | 'name': 'Sharknado', 38 | 'name_cs': 'Žralokonádo', 39 | 'year': 2013, 40 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 41 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 42 | }, 43 | { 44 | 'id': 4, 45 | 'name': 'Mega Shark vs. Giant Octopus', 46 | 'name_cs': 'Megažralok vs. obří chobotnice', 47 | 'year': 2009, 48 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 49 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 50 | }, 51 | ] 52 | 53 | 54 | def filter_movies(movies, name): 55 | if name is not None: 56 | filtered_movies = [] 57 | for movie in movies: 58 | if name in movie['name'].lower(): 59 | filtered_movies.append(movie) 60 | return filtered_movies 61 | else: 62 | return movies 63 | 64 | 65 | class MovieListResource(): 66 | 67 | def on_get(self, request, response): 68 | name = request.get_param('name') 69 | response.body = json.dumps(filter_movies(movies, name)) 70 | 71 | 72 | def get_movie_by_id(movies, id): 73 | for movie in movies: 74 | if movie['id'] == id: 75 | return movie 76 | 77 | 78 | class MovieDetailResource(): 79 | 80 | def on_get(self, request, response, id): 81 | response.body = json.dumps(get_movie_by_id(movies, id)) 82 | 83 | 84 | app = falcon.API() 85 | app.add_route('/', PersonalDetailsResource()) 86 | app.add_route('/movies', MovieListResource()) 87 | app.add_route('/movies/{id:int}', MovieDetailResource()) 88 | -------------------------------------------------------------------------------- /code/server/10_not_found/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | response.body = json.dumps(personal_details) 16 | 17 | 18 | movies = [ 19 | { 20 | 'id': 1, 21 | 'name': 'The Last Boy Scout', 22 | 'name_cs': 'Poslední skaut', 23 | 'year': 1991, 24 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 25 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 26 | }, 27 | { 28 | 'id': 2, 29 | 'name': 'Mies vailla menneisyyttä', 30 | 'name_cs': 'Muž bez minulosti', 31 | 'year': 2002, 32 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 33 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 34 | }, 35 | { 36 | 'id': 3, 37 | 'name': 'Sharknado', 38 | 'name_cs': 'Žralokonádo', 39 | 'year': 2013, 40 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 41 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 42 | }, 43 | { 44 | 'id': 4, 45 | 'name': 'Mega Shark vs. Giant Octopus', 46 | 'name_cs': 'Megažralok vs. obří chobotnice', 47 | 'year': 2009, 48 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 49 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 50 | }, 51 | ] 52 | 53 | 54 | def filter_movies(movies, name): 55 | if name is not None: 56 | filtered_movies = [] 57 | for movie in movies: 58 | if name in movie['name'].lower(): 59 | filtered_movies.append(movie) 60 | return filtered_movies 61 | else: 62 | return movies 63 | 64 | 65 | class MovieListResource(): 66 | 67 | def on_get(self, request, response): 68 | name = request.get_param('name') 69 | response.body = json.dumps(filter_movies(movies, name)) 70 | 71 | 72 | def get_movie_by_id(movies, id): 73 | for movie in movies: 74 | if movie['id'] == id: 75 | return movie 76 | 77 | 78 | class MovieDetailResource(): 79 | 80 | def on_get(self, request, response, id): 81 | movie = get_movie_by_id(movies, id) 82 | if movie is None: 83 | response.status = '404 Not Found' 84 | else: 85 | response.body = json.dumps(movie) 86 | 87 | 88 | app = falcon.API() 89 | app.add_route('/', PersonalDetailsResource()) 90 | app.add_route('/movies', MovieListResource()) 91 | app.add_route('/movies/{id:int}', MovieDetailResource()) 92 | -------------------------------------------------------------------------------- /cs/_install_curl.rst: -------------------------------------------------------------------------------- 1 | Je dost možné, že curl je již přímo v našem systému (většina Linuxů, macOS, Windows 10) a není potřeba nic instalovat. Necháme program vypsat svou verzi, čímž ověříme, jestli funguje: 2 | 3 | .. literalinclude:: ../code/curl_version.txt 4 | :language: text 5 | 6 | Vypíše-li se verze programu curl, jak je na příkladu výše, máme hotovo. Program curl je v systému a je funkční. Můžeme instalaci přeskočit. Pokud se ale místo verze vypíše něco v tom smyslu, že příkaz ani program curl neexistuje, pak je potřeba curl doinstalovat. 7 | 8 | .. tabs:: 9 | 10 | .. group-tab:: Linux 11 | 12 | Instalujeme standardní cestou přes správce balíčků. V distribucích Debian nebo Ubuntu takto: 13 | 14 | .. code-block:: text 15 | 16 | $ sudo apt install curl 17 | 18 | V distribuci Fedora takto: 19 | 20 | .. code-block:: text 21 | 22 | $ sudo dnf install curl 23 | 24 | Nakonec necháme program vypsat svou verzi, čímž ověříme, jestli funguje: 25 | 26 | .. code-block:: text 27 | 28 | $ curl --version 29 | 30 | .. group-tab:: Windows < 10 31 | 32 | Pokud máme *Git for Windows* nebo *Cygwin*, je velká šance, že curl už máme, jen jej musíme spouštět ze speciálního terminálu poskytovaného těmito nástroji. Otevřeme tento speciální terminál a necháme program vypsat verzi: 33 | 34 | .. code-block:: text 35 | 36 | $ curl --version 37 | 38 | Vypíše-li se verze programu curl, máme hotovo a můžeme instalaci přeskočit. Pokud se ale místo verze vypíše něco v tom smyslu, že příkaz ani program curl neexistuje, pak je potřeba curl doinstalovat. Máme-li `Chocolatey `__, mělo by stačit v příkazové řádce spustit následující: 39 | 40 | .. code-block:: text 41 | 42 | $ choco install curl 43 | 44 | Poté necháme curl vypsat svou verzi: 45 | 46 | .. code-block:: text 47 | 48 | $ curl --version 49 | 50 | Vypíše-li se verze programu curl, máme hotovo a můžeme instalaci přeskočit. Pokud se ale místo verze vypíše něco v tom smyslu, že choco nebo curl neexistují, musíme curl stáhnout a nainstalovat ručně. 51 | 52 | Na `stránkách programu najdeme verzi pro Windows `__ a podle typu našich Windows (`Jak zjistit, zda máme 32bitové, nebo 64bitové? `__) vybereme odpovídající verzi. Po rozbalení dostaneme spustitelný soubor ``curl.exe``, který přidáme do systémové cesty. Pokud přidávat programy do systémové cesty neumíme, pro účely tohoto návodu postačí, pokud soubor ``curl.exe`` dáme do té složky, ze které jej budeme chtít spouštět. Nakonec necháme program vypsat svou verzi, čímž ověříme, jestli funguje: 53 | 54 | .. code-block:: text 55 | 56 | $ curl --version 57 | -------------------------------------------------------------------------------- /code/server/11_repr/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | movies_watchlist_url = '{0}/movies'.format(request.prefix) 16 | 17 | personal_details_repr = dict(personal_details) 18 | personal_details_repr['movies_watchlist_url'] = movies_watchlist_url 19 | 20 | response.body = json.dumps(personal_details_repr) 21 | 22 | 23 | movies = [ 24 | { 25 | 'id': 1, 26 | 'name': 'The Last Boy Scout', 27 | 'name_cs': 'Poslední skaut', 28 | 'year': 1991, 29 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 30 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 31 | }, 32 | { 33 | 'id': 2, 34 | 'name': 'Mies vailla menneisyyttä', 35 | 'name_cs': 'Muž bez minulosti', 36 | 'year': 2002, 37 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 38 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 39 | }, 40 | { 41 | 'id': 3, 42 | 'name': 'Sharknado', 43 | 'name_cs': 'Žralokonádo', 44 | 'year': 2013, 45 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 46 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 47 | }, 48 | { 49 | 'id': 4, 50 | 'name': 'Mega Shark vs. Giant Octopus', 51 | 'name_cs': 'Megažralok vs. obří chobotnice', 52 | 'year': 2009, 53 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 54 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 55 | }, 56 | ] 57 | 58 | 59 | def filter_movies(movies, name): 60 | if name is not None: 61 | filtered_movies = [] 62 | for movie in movies: 63 | if name in movie['name'].lower(): 64 | filtered_movies.append(movie) 65 | return filtered_movies 66 | else: 67 | return movies 68 | 69 | 70 | def represent_movies(movies, base_url): 71 | movies_list = [] 72 | for movie in movies: 73 | movies_list.append({ 74 | 'name': movie['name'], 75 | 'url': '{0}/movies/{1}'.format(base_url, movie['id']), 76 | }) 77 | return json.dumps(movies_list) 78 | 79 | 80 | class MovieListResource(): 81 | 82 | def on_get(self, request, response): 83 | name = request.get_param('name') 84 | base_url = request.prefix 85 | 86 | filtered_movies = filter_movies(movies, name) 87 | response.body = represent_movies(filtered_movies, base_url) 88 | 89 | 90 | def get_movie_by_id(movies, id): 91 | for movie in movies: 92 | if movie['id'] == id: 93 | return movie 94 | 95 | 96 | class MovieDetailResource(): 97 | 98 | def on_get(self, request, response, id): 99 | movie = get_movie_by_id(movies, id) 100 | if movie is None: 101 | response.status = '404 Not Found' 102 | else: 103 | base_url = request.prefix 104 | 105 | movie_repr = dict(movie) 106 | movie_repr['url'] = '{0}/movies/{1}'.format(base_url, movie['id']) 107 | del movie_repr['id'] 108 | 109 | response.body = json.dumps(movie_repr) 110 | 111 | 112 | app = falcon.API() 113 | app.add_route('/', PersonalDetailsResource()) 114 | app.add_route('/movies', MovieListResource()) 115 | app.add_route('/movies/{id:int}', MovieDetailResource()) 116 | -------------------------------------------------------------------------------- /en/01-api-intro.rst: -------------------------------------------------------------------------------- 1 | .. _intro: 2 | 3 | API introduction 4 | ================ 5 | 6 | You cannot touch API and it is not possible to see it, however these days it indirectly influences everybody’s lives. 7 | 8 | Unfortunately, it is not easy to find out, what APIs are - actually. `The article on Wikipedia `__ begins with an explanation, that it is an application programming interface (API) and continues with professional technical text, which common mortals have difficulties to understand. This document is trying to explain API **on the common example using plain words**. 9 | 10 | 11 | .. _meteo: 12 | 13 | Weather prediction on mobile phone 14 | ---------------------------------- 15 | 16 | Predicting weather on your mobile phone is common on any device. But how does it get there? You probably do not suspect, that predictions do happen in your `national meteorological organization `__. But how is it possible that as soon as the meteorologists agree on tomorrow’s storm, you do have it on your mobile phone display **within a second**? 17 | 18 | .. image:: ../_static/images/chmu1.png 19 | :align: center 20 | :width: 60% 21 | 22 | In the old days, to get weather prediction for the newspaper, radio or television the editorial staff had to submit a request for weather prediction from the meteorologists only **once a day**. I am not sure, how it was done, but I can imagine, that someone from the news called to the meteorological organization headquarter and on the other side of the phone the meteorologist told them the sunny and cloudy weather predicted. 23 | 24 | Today, it could not work like this anymore. Weather data, which are publicly reported, are needed to be displayed **instantly on thousands of places on the internet at the same time**. 25 | 26 | The meteorological organizations have their own websites, where the weather forecast is published. To access the information requires a person, who **reads and transcripts the information** to somewhere else. 27 | 28 | In case of the news, one can imagine a poor intern, who desperately nonstop monitors the meteorological organization website and copies flood and other weather warnings on the news website. But in case of your mobile phone application, nothing like that is really an option. 29 | 30 | Your mobile phone needs to be able to **detect predictions automatically**. Therefore, the meteorologists store weather information in order to be **machine-readable** and makes it **available for download** on their website. At a rough guess, image it as so that instead of drawing clouds on the web, the meteorologists will put everything in a table, even Excel spreadsheet, where it is predetermined, what does each line and column mean. In addition, they will tell you that this table is always at ``http://weather.gov/forecast.xslx`` and will be always up to date. 31 | 32 | .. image:: ../_static/images/chmu2.png 33 | :align: center 34 | :width: 60% 35 | 36 | Then every hour the application on your mobile phone can access the table at ``http://weather.gov/forecast.xslx`` and download it, decipher the rows and columns, combine the current weather forecasts and show you the resulting clouds and sunnies. This mechanism, when **one party provides something on a stable web address in machine-readable form, and the other party is able to machine read it anytime and to do something useful with it**, it is called the web API. 37 | 38 | 39 | .. todo:: 40 | The rest of the translation is in `this Google Doc `__ until someone puts it here. If you want to help, please coordinate under the issue `#52 `__. 41 | -------------------------------------------------------------------------------- /code/server/12_post/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | movies_watchlist_url = '{0}/movies'.format(request.prefix) 16 | 17 | personal_details_repr = dict(personal_details) 18 | personal_details_repr['movies_watchlist_url'] = movies_watchlist_url 19 | 20 | response.body = json.dumps(personal_details_repr) 21 | 22 | 23 | movies = [ 24 | { 25 | 'id': 1, 26 | 'name': 'The Last Boy Scout', 27 | 'name_cs': 'Poslední skaut', 28 | 'year': 1991, 29 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 30 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 31 | }, 32 | { 33 | 'id': 2, 34 | 'name': 'Mies vailla menneisyyttä', 35 | 'name_cs': 'Muž bez minulosti', 36 | 'year': 2002, 37 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 38 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 39 | }, 40 | { 41 | 'id': 3, 42 | 'name': 'Sharknado', 43 | 'name_cs': 'Žralokonádo', 44 | 'year': 2013, 45 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 46 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 47 | }, 48 | { 49 | 'id': 4, 50 | 'name': 'Mega Shark vs. Giant Octopus', 51 | 'name_cs': 'Megažralok vs. obří chobotnice', 52 | 'year': 2009, 53 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 54 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 55 | }, 56 | ] 57 | 58 | 59 | def filter_movies(movies, name): 60 | if name is not None: 61 | filtered_movies = [] 62 | for movie in movies: 63 | if name in movie['name'].lower(): 64 | filtered_movies.append(movie) 65 | return filtered_movies 66 | else: 67 | return movies 68 | 69 | 70 | def represent_movies(movies, base_url): 71 | movies_list = [] 72 | for movie in movies: 73 | movies_list.append({ 74 | 'name': movie['name'], 75 | 'url': '{0}/movies/{1}'.format(base_url, movie['id']), 76 | }) 77 | return movies_list 78 | 79 | 80 | def create_movie_id(movies): 81 | ids = [] 82 | for movie in movies: 83 | ids.append(movie['id']) 84 | return max(ids) + 1 85 | 86 | 87 | class MovieListResource(): 88 | 89 | def on_get(self, request, response): 90 | name = request.get_param('name') 91 | base_url = request.prefix 92 | 93 | filtered_movies = filter_movies(movies, name) 94 | movies_repr = represent_movies(filtered_movies, base_url) 95 | response.body = json.dumps(movies_repr) 96 | 97 | def on_post(self, request, response): 98 | movie = json.load(request.bounded_stream) 99 | movie['id'] = create_movie_id(movies) 100 | movies.append(movie) 101 | 102 | 103 | def get_movie_by_id(movies, id): 104 | for movie in movies: 105 | if movie['id'] == id: 106 | return movie 107 | 108 | 109 | class MovieDetailResource(): 110 | 111 | def on_get(self, request, response, id): 112 | movie = get_movie_by_id(movies, id) 113 | if movie is None: 114 | response.status = '404 Not Found' 115 | else: 116 | base_url = request.prefix 117 | 118 | movie_repr = dict(movie) 119 | movie_repr['url'] = '{0}/movies/{1}'.format(base_url, movie['id']) 120 | del movie_repr['id'] 121 | 122 | response.body = json.dumps(movie_repr) 123 | 124 | 125 | app = falcon.API() 126 | app.add_route('/', PersonalDetailsResource()) 127 | app.add_route('/movies', MovieListResource()) 128 | app.add_route('/movies/{id:int}', MovieDetailResource()) 129 | -------------------------------------------------------------------------------- /cs/_install_now.rst: -------------------------------------------------------------------------------- 1 | Nejdříve ověříme, zda náhodou už nemáme nainstalovaný program now, kterým se služba Now ovládá. V příkazové řádce necháme program vypsat svou verzi, čímž ověříme, jestli funguje: 2 | 3 | .. code-block:: text 4 | 5 | $ now --version 6 | > UPDATE AVAILABLE Run `npm i -g now@latest` to install Now CLI 16.7.3 7 | > Changelog: https://github.com/zeit/now/releases/tag/now@16.7.3 8 | 16.7.1 9 | 10 | Vypíše-li se verze programu now, jak je na příkladu výše, máme hotovo. Na číslu verze nezáleží. Program now je funkční a instalaci můžeme přeskočit. 11 | 12 | Pokud se místo verze vypíše něco v tom smyslu, že příkaz ani program now neexistuje, pak je potřeba jej doinstalovat. 13 | 14 | .. tabs:: 15 | 16 | .. group-tab:: Linux 17 | 18 | Začneme instalací Node.js. 19 | 20 | .. note:: 21 | .. include:: _nodejs_note.rst 22 | 23 | Instalujeme standardní cestou přes správce balíčků. V distribucích Debian nebo Ubuntu takto: 24 | 25 | .. code-block:: text 26 | 27 | $ sudo apt-get install nodejs 28 | 29 | V distribuci Fedora takto: 30 | 31 | .. code-block:: text 32 | 33 | $ sudo dnf install nodejs 34 | 35 | Balík Node.js nainstaluje i program s názvem `npm `__ (něco jako `pip `__ pro JavaScript), kterým už můžeme nainstalovat `now `__: 36 | 37 | .. code-block:: text 38 | 39 | $ npm install now@latest --global 40 | 41 | Nakonec necháme program now vypsat svou verzi, čímž ověříme, jestli funguje: 42 | 43 | .. code-block:: text 44 | 45 | $ now --version 46 | 47 | .. group-tab:: Windows 48 | 49 | Začneme instalací Node.js. 50 | 51 | .. note:: 52 | .. include:: _nodejs_note.rst 53 | 54 | Na `stránkách Node.js stáhneme „Windows Installer“ `__ a spustíme. Nainstaluje se i program s názvem `npm `__ (něco jako `pip `__ pro JavaScript), kterým už můžeme nainstalovat `now `__: 55 | 56 | .. code-block:: text 57 | 58 | $ npm install now@latest --global 59 | 60 | Nakonec necháme program now vypsat svou verzi, čímž ověříme, jestli funguje: 61 | 62 | .. code-block:: text 63 | 64 | $ now --version 65 | 66 | .. group-tab:: macOS (brew) 67 | 68 | Pokud používáme `Homebrew `__ k instalaci programů, budeme mít práci o něco snazší. Začneme instalací Node.js. 69 | 70 | .. note:: 71 | .. include:: _nodejs_note.rst 72 | 73 | Instalujeme standardní cestou: 74 | 75 | .. code-block:: text 76 | 77 | $ brew install node 78 | 79 | Balík Node.js nainstaluje i program s názvem `npm `__ (něco jako `pip `__ pro JavaScript), kterým už můžeme nainstalovat `now `__: 80 | 81 | .. code-block:: text 82 | 83 | $ npm install now@latest --global 84 | 85 | Nakonec necháme program now vypsat svou verzi, čímž ověříme, jestli funguje: 86 | 87 | .. code-block:: text 88 | 89 | $ now --version 90 | 91 | .. group-tab:: macOS 92 | 93 | Začneme instalací Node.js. 94 | 95 | .. note:: 96 | .. include:: _nodejs_note.rst 97 | 98 | Na `stránkách Node.js stáhneme „macOS Installer“ `__ a spustíme. Nainstaluje se i program s názvem `npm `__ (něco jako `pip `__ pro JavaScript), kterým už můžeme nainstalovat `now `__: 99 | 100 | .. code-block:: text 101 | 102 | $ npm install now@latest --global 103 | 104 | Nakonec necháme program now vypsat svou verzi, čímž ověříme, jestli funguje: 105 | 106 | .. code-block:: text 107 | 108 | $ now --version 109 | -------------------------------------------------------------------------------- /code/server/13_created/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | movies_watchlist_url = '{0}/movies'.format(request.prefix) 16 | 17 | personal_details_repr = dict(personal_details) 18 | personal_details_repr['movies_watchlist_url'] = movies_watchlist_url 19 | 20 | response.body = json.dumps(personal_details_repr) 21 | 22 | 23 | movies = [ 24 | { 25 | 'id': 1, 26 | 'name': 'The Last Boy Scout', 27 | 'name_cs': 'Poslední skaut', 28 | 'year': 1991, 29 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 30 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 31 | }, 32 | { 33 | 'id': 2, 34 | 'name': 'Mies vailla menneisyyttä', 35 | 'name_cs': 'Muž bez minulosti', 36 | 'year': 2002, 37 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 38 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 39 | }, 40 | { 41 | 'id': 3, 42 | 'name': 'Sharknado', 43 | 'name_cs': 'Žralokonádo', 44 | 'year': 2013, 45 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 46 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 47 | }, 48 | { 49 | 'id': 4, 50 | 'name': 'Mega Shark vs. Giant Octopus', 51 | 'name_cs': 'Megažralok vs. obří chobotnice', 52 | 'year': 2009, 53 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 54 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 55 | }, 56 | ] 57 | 58 | 59 | def filter_movies(movies, name): 60 | if name is not None: 61 | filtered_movies = [] 62 | for movie in movies: 63 | if name in movie['name'].lower(): 64 | filtered_movies.append(movie) 65 | return filtered_movies 66 | else: 67 | return movies 68 | 69 | 70 | def represent_movies(movies, base_url): 71 | movies_list = [] 72 | for movie in movies: 73 | movies_list.append({ 74 | 'name': movie['name'], 75 | 'url': '{0}/movies/{1}'.format(base_url, movie['id']), 76 | }) 77 | return movies_list 78 | 79 | 80 | def create_movie_id(movies): 81 | ids = [] 82 | for movie in movies: 83 | ids.append(movie['id']) 84 | return max(ids) + 1 85 | 86 | 87 | class MovieListResource(): 88 | 89 | def on_get(self, request, response): 90 | name = request.get_param('name') 91 | base_url = request.prefix 92 | 93 | filtered_movies = filter_movies(movies, name) 94 | movies_repr = represent_movies(filtered_movies, base_url) 95 | response.body = json.dumps(movies_repr) 96 | 97 | def on_post(self, request, response): 98 | movie = json.load(request.bounded_stream) 99 | movie['id'] = create_movie_id(movies) 100 | movies.append(movie) 101 | 102 | movie_url = '{0}/movies/{1}'.format(request.prefix, movie['id']) 103 | 104 | movie_repr = dict(movie) 105 | movie_repr['url'] = movie_url 106 | del movie_repr['id'] 107 | 108 | response.status = '201 Created' 109 | response.set_header('Location', movie_url) 110 | response.body = json.dumps(movie_repr) 111 | 112 | 113 | def get_movie_by_id(movies, id): 114 | for movie in movies: 115 | if movie['id'] == id: 116 | return movie 117 | 118 | 119 | class MovieDetailResource(): 120 | 121 | def on_get(self, request, response, id): 122 | movie = get_movie_by_id(movies, id) 123 | if movie is None: 124 | response.status = '404 Not Found' 125 | else: 126 | base_url = request.prefix 127 | 128 | movie_repr = dict(movie) 129 | movie_repr['url'] = '{0}/movies/{1}'.format(base_url, movie['id']) 130 | del movie_repr['id'] 131 | 132 | response.body = json.dumps(movie_repr) 133 | 134 | 135 | app = falcon.API() 136 | app.add_route('/', PersonalDetailsResource()) 137 | app.add_route('/movies', MovieListResource()) 138 | app.add_route('/movies/{id:int}', MovieDetailResource()) 139 | -------------------------------------------------------------------------------- /code/server/14_delete/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | movies_watchlist_url = '{0}/movies'.format(request.prefix) 16 | 17 | personal_details_repr = dict(personal_details) 18 | personal_details_repr['movies_watchlist_url'] = movies_watchlist_url 19 | 20 | response.body = json.dumps(personal_details_repr) 21 | 22 | 23 | movies = [ 24 | { 25 | 'id': 1, 26 | 'name': 'The Last Boy Scout', 27 | 'name_cs': 'Poslední skaut', 28 | 'year': 1991, 29 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 30 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 31 | }, 32 | { 33 | 'id': 2, 34 | 'name': 'Mies vailla menneisyyttä', 35 | 'name_cs': 'Muž bez minulosti', 36 | 'year': 2002, 37 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 38 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 39 | }, 40 | { 41 | 'id': 3, 42 | 'name': 'Sharknado', 43 | 'name_cs': 'Žralokonádo', 44 | 'year': 2013, 45 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 46 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 47 | }, 48 | { 49 | 'id': 4, 50 | 'name': 'Mega Shark vs. Giant Octopus', 51 | 'name_cs': 'Megažralok vs. obří chobotnice', 52 | 'year': 2009, 53 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 54 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 55 | }, 56 | ] 57 | 58 | 59 | def filter_movies(movies, name): 60 | if name is not None: 61 | filtered_movies = [] 62 | for movie in movies: 63 | if name in movie['name'].lower(): 64 | filtered_movies.append(movie) 65 | return filtered_movies 66 | else: 67 | return movies 68 | 69 | 70 | def represent_movies(movies, base_url): 71 | movies_list = [] 72 | for movie in movies: 73 | movies_list.append({ 74 | 'name': movie['name'], 75 | 'url': '{0}/movies/{1}'.format(base_url, movie['id']), 76 | }) 77 | return movies_list 78 | 79 | 80 | def create_movie_id(movies): 81 | ids = [] 82 | for movie in movies: 83 | ids.append(movie['id']) 84 | return max(ids) + 1 85 | 86 | 87 | class MovieListResource(): 88 | 89 | def on_get(self, request, response): 90 | name = request.get_param('name') 91 | base_url = request.prefix 92 | 93 | filtered_movies = filter_movies(movies, name) 94 | movies_repr = represent_movies(filtered_movies, base_url) 95 | response.body = json.dumps(movies_repr) 96 | 97 | def on_post(self, request, response): 98 | movie = json.load(request.bounded_stream) 99 | movie['id'] = create_movie_id(movies) 100 | movies.append(movie) 101 | 102 | movie_url = '{0}/movies/{1}'.format(request.prefix, movie['id']) 103 | 104 | movie_repr = dict(movie) 105 | movie_repr['url'] = movie_url 106 | del movie_repr['id'] 107 | 108 | response.status = '201 Created' 109 | response.set_header('Location', movie_url) 110 | response.body = json.dumps(movie_repr) 111 | 112 | 113 | def get_movie_by_id(movies, id): 114 | for movie in movies: 115 | if movie['id'] == id: 116 | return movie 117 | 118 | 119 | def remove_movie_by_id(movies, id): 120 | for i, movie in enumerate(movies): 121 | if movie['id'] == id: 122 | del movies[i] 123 | return True 124 | return False 125 | 126 | 127 | class MovieDetailResource(): 128 | 129 | def on_get(self, request, response, id): 130 | movie = get_movie_by_id(movies, id) 131 | if movie is None: 132 | response.status = '404 Not Found' 133 | else: 134 | base_url = request.prefix 135 | 136 | movie_repr = dict(movie) 137 | movie_repr['url'] = '{0}/movies/{1}'.format(base_url, movie['id']) 138 | del movie_repr['id'] 139 | 140 | response.body = json.dumps(movie_repr) 141 | 142 | def on_delete(self, request, response, id): 143 | removed = remove_movie_by_id(movies, id) 144 | if removed: 145 | response.status = '204 No Content' 146 | else: 147 | response.status = '404 Not Found' 148 | 149 | 150 | app = falcon.API() 151 | app.add_route('/', PersonalDetailsResource()) 152 | app.add_route('/movies', MovieListResource()) 153 | app.add_route('/movies/{id:int}', MovieDetailResource()) 154 | -------------------------------------------------------------------------------- /code/server/test_code_works.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import time 4 | import subprocess 5 | import shlex 6 | import platform 7 | from pathlib import Path 8 | from operator import itemgetter 9 | 10 | import pytest 11 | 12 | 13 | WIN = platform.system() == 'Windows' 14 | HOST = f"{platform.node() if WIN else '0.0.0.0'}:8080" 15 | 16 | SYSTEM_SUFFIXES = ('_win.txt', '_unix.txt') 17 | CURRENT_SYSTEM_SUFFIX = '_win.txt' if WIN else '_unix.txt' 18 | 19 | 20 | def parse_response(text): 21 | text = text.replace('\r\n', '\n') 22 | head, body = text.split('\n\n') if '\n\n' in text else (text, None) 23 | 24 | head = head.replace('application/json; charset=UTF-8', 'application/json') 25 | head_lines = head.splitlines() 26 | head_lines = [h for h in head_lines if not h.lower().startswith('date:')] 27 | 28 | status = int(re.search(r' (\d{3}) ', head_lines[0]).group(1)) 29 | headers = head_lines[1:] 30 | body = (body.strip() if '/json' in head else body) if body and body.strip() else None 31 | body_json = json.loads(body) if body and '/json' in head else None 32 | 33 | return dict(status=status, headers=headers, body=body, body_json=body_json) 34 | 35 | 36 | def parse_test(text, filename=None): 37 | lines = text.splitlines() 38 | 39 | command = replace_host(lines[0][2:]) 40 | response = parse_response('\n'.join(lines[1:]) + '\n') 41 | 42 | # Temporarily parse headers into a dict 43 | headers = dict([header.split(': ') for header in response['headers']]) 44 | 45 | # Replace host in the Location header which usually contains a link 46 | if 'Location' in headers: 47 | headers['Location'] = replace_host(headers['Location']) 48 | 49 | # Because we'll be altering body and the Content-Length header a few lines 50 | # below, let's verify first that the original file has it right. 51 | if response['body'] is not None: 52 | assert int(headers['Content-Length']) == len(response['body']), filename 53 | 54 | # The body can contain links, so we must replace host even there. That 55 | # changes the Content-Length though, so it must be re-calculated. 56 | response['body'] = replace_host(response['body']) 57 | headers['Content-Length'] = len(response['body']) 58 | 59 | # We must update the parsed JSON body as well 60 | if response['body_json'] is not None: 61 | response['body_json'] = json.loads(response['body']) 62 | 63 | # Squash headers back 64 | response['headers'] = [f'{name}: {value}' for name, value 65 | in headers.items()] 66 | 67 | return dict(command=command, response=response) 68 | 69 | 70 | def replace_host(text): 71 | return (text.replace('0.0.0.0:8080', HOST) 72 | .replace('MY-COMPUTER:8080', HOST) 73 | .replace('api.example.com', HOST)) 74 | 75 | 76 | def is_test(basename): 77 | if basename.startswith(('test', 'example')): 78 | if basename.endswith(SYSTEM_SUFFIXES): 79 | return basename.endswith(CURRENT_SYSTEM_SUFFIX) 80 | return True 81 | return False 82 | 83 | 84 | def generate(dir): 85 | for path in sorted(dir.iterdir()): 86 | if not path.is_dir() or path.name == '__pycache__': 87 | continue 88 | 89 | tests = [ 90 | parse_test(f.read_text(), filename=str(f.resolve())) 91 | for f in sorted(path.iterdir()) if is_test(f.name) 92 | ] 93 | yield dict(src=path, name='code/server/' + path.name, tests=tests) 94 | 95 | 96 | def run_test(command): 97 | client = subprocess.run(shlex.split(command), 98 | stdout=subprocess.PIPE, 99 | stderr=subprocess.PIPE) 100 | return parse_response(client.stdout.decode('utf-8')) 101 | 102 | 103 | test_batches = list(generate(Path(__file__).parent)) 104 | 105 | 106 | @pytest.mark.parametrize('test_batch', test_batches, ids=itemgetter('name')) 107 | def test_code_works(test_batch): 108 | server = subprocess.Popen(['waitress-serve', 'index:app'], 109 | cwd=test_batch['src']) 110 | time.sleep(0.5) 111 | try: 112 | tests = [ 113 | (test['command'], run_test(test['command']), test['response']) 114 | for test in test_batch['tests'] 115 | ] 116 | finally: 117 | server.terminate() 118 | 119 | for command, response, expected_response in tests: 120 | print(command) 121 | assert response['status'] == expected_response['status'] 122 | for header in expected_response['headers']: 123 | assert header in response['headers'] 124 | if response['body_json']: 125 | assert response['body_json'] == expected_response['body_json'] 126 | else: 127 | assert response['body'] == expected_response['body'] 128 | -------------------------------------------------------------------------------- /code/server/16_deploy/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | movies_watchlist_url = '{0}/movies'.format(request.prefix) 16 | 17 | personal_details_repr = dict(personal_details) 18 | personal_details_repr['movies_watchlist_url'] = movies_watchlist_url 19 | 20 | response.body = json.dumps(personal_details_repr) 21 | 22 | 23 | movies = [ 24 | { 25 | 'id': 1, 26 | 'name': 'The Last Boy Scout', 27 | 'name_cs': 'Poslední skaut', 28 | 'year': 1991, 29 | 'stars': ['Bruce Willis', 'Damon Wayans', 'Chelsea Field'], 30 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 31 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 32 | }, 33 | { 34 | 'id': 2, 35 | 'name': 'Mies vailla menneisyyttä', 36 | 'name_cs': 'Muž bez minulosti', 37 | 'year': 2002, 38 | 'stars': ['Markku Peltola', 'Kati Outinen', 'Juhani Niemelä'], 39 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 40 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 41 | }, 42 | { 43 | 'id': 3, 44 | 'name': 'Sharknado', 45 | 'name_cs': 'Žralokonádo', 46 | 'year': 2013, 47 | 'stars': ['Ian Ziering', 'Tara Reid', 'John Heard'], 48 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 49 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 50 | }, 51 | { 52 | 'id': 4, 53 | 'name': 'Mega Shark vs. Giant Octopus', 54 | 'name_cs': 'Megažralok vs. obří chobotnice', 55 | 'year': 2009, 56 | 'stars': ['Debbie Gibson', 'Lorenzo Lamas', 'Vic Chao'], 57 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 58 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 59 | }, 60 | ] 61 | 62 | 63 | def filter_movies(movies, name): 64 | if name is not None: 65 | filtered_movies = [] 66 | for movie in movies: 67 | if name in movie['name'].lower(): 68 | filtered_movies.append(movie) 69 | return filtered_movies 70 | else: 71 | return movies 72 | 73 | 74 | def represent_movies(movies, base_url): 75 | movies_list = [] 76 | for movie in movies: 77 | movies_list.append({ 78 | 'name': movie['name'], 79 | 'url': '{0}/movies/{1}'.format(base_url, movie['id']), 80 | }) 81 | return movies_list 82 | 83 | 84 | def create_movie_id(movies): 85 | ids = [] 86 | for movie in movies: 87 | ids.append(movie['id']) 88 | return max(ids) + 1 89 | 90 | 91 | class MovieListResource(): 92 | 93 | def on_get(self, request, response): 94 | name = request.get_param('name') 95 | base_url = request.prefix 96 | 97 | filtered_movies = filter_movies(movies, name) 98 | movies_repr = represent_movies(filtered_movies, base_url) 99 | response.body = json.dumps(movies_repr) 100 | 101 | def on_post(self, request, response): 102 | movie = json.load(request.bounded_stream) 103 | movie['id'] = create_movie_id(movies) 104 | movies.append(movie) 105 | 106 | movie_url = '{0}/movies/{1}'.format(request.prefix, movie['id']) 107 | 108 | movie_repr = dict(movie) 109 | movie_repr['url'] = movie_url 110 | del movie_repr['id'] 111 | 112 | response.status = '201 Created' 113 | response.set_header('Location', movie_url) 114 | response.body = json.dumps(movie_repr) 115 | 116 | 117 | def get_movie_by_id(movies, id): 118 | for movie in movies: 119 | if movie['id'] == id: 120 | return movie 121 | 122 | 123 | def remove_movie_by_id(movies, id): 124 | for i, movie in enumerate(movies): 125 | if movie['id'] == id: 126 | del movies[i] 127 | return True 128 | return False 129 | 130 | 131 | class MovieDetailResource(): 132 | 133 | def on_get(self, request, response, id): 134 | movie = get_movie_by_id(movies, id) 135 | if movie is None: 136 | response.status = '404 Not Found' 137 | else: 138 | base_url = request.prefix 139 | 140 | movie_repr = dict(movie) 141 | movie_repr['url'] = '{0}/movies/{1}'.format(base_url, movie['id']) 142 | del movie_repr['id'] 143 | 144 | response.body = json.dumps(movie_repr) 145 | 146 | def on_delete(self, request, response, id): 147 | movie = get_movie_by_id(movies, id) 148 | if movie is None: 149 | response.status = '404 Not Found' 150 | elif 'Bruce Willis' in movie['stars']: 151 | response.status = '403 Forbidden' 152 | else: 153 | remove_movie_by_id(movies, id) 154 | response.status = '204 No Content' 155 | 156 | 157 | app = falcon.API() 158 | app.add_route('/', PersonalDetailsResource()) 159 | app.add_route('/movies', MovieListResource()) 160 | app.add_route('/movies/{id:int}', MovieDetailResource()) 161 | -------------------------------------------------------------------------------- /code/server/15_forbidden/index.py: -------------------------------------------------------------------------------- 1 | import json 2 | import falcon 3 | 4 | 5 | personal_details = { 6 | 'name': 'Honza', 7 | 'surname': 'Javorek', 8 | 'socks_size': '42', 9 | } 10 | 11 | 12 | class PersonalDetailsResource(): 13 | 14 | def on_get(self, request, response): 15 | movies_watchlist_url = '{0}/movies'.format(request.prefix) 16 | 17 | personal_details_repr = dict(personal_details) 18 | personal_details_repr['movies_watchlist_url'] = movies_watchlist_url 19 | 20 | response.body = json.dumps(personal_details_repr) 21 | 22 | 23 | movies = [ 24 | { 25 | 'id': 1, 26 | 'name': 'The Last Boy Scout', 27 | 'name_cs': 'Poslední skaut', 28 | 'year': 1991, 29 | 'stars': ['Bruce Willis', 'Damon Wayans', 'Chelsea Field'], 30 | 'imdb_url': 'https://www.imdb.com/title/tt0102266/', 31 | 'csfd_url': 'https://www.csfd.cz/film/8283-posledni-skaut/', 32 | }, 33 | { 34 | 'id': 2, 35 | 'name': 'Mies vailla menneisyyttä', 36 | 'name_cs': 'Muž bez minulosti', 37 | 'year': 2002, 38 | 'stars': ['Markku Peltola', 'Kati Outinen', 'Juhani Niemelä'], 39 | 'imdb_url': 'https://www.imdb.com/title/tt0311519/', 40 | 'csfd_url': 'https://www.csfd.cz/film/35366-muz-bez-minulosti/', 41 | }, 42 | { 43 | 'id': 3, 44 | 'name': 'Sharknado', 45 | 'name_cs': 'Žralokonádo', 46 | 'year': 2013, 47 | 'stars': ['Ian Ziering', 'Tara Reid', 'John Heard'], 48 | 'imdb_url': 'https://www.imdb.com/title/tt2724064/', 49 | 'csfd_url': 'https://www.csfd.cz/film/343017-zralokonado/', 50 | }, 51 | { 52 | 'id': 4, 53 | 'name': 'Mega Shark vs. Giant Octopus', 54 | 'name_cs': 'Megažralok vs. obří chobotnice', 55 | 'year': 2009, 56 | 'stars': ['Debbie Gibson', 'Lorenzo Lamas', 'Vic Chao'], 57 | 'imdb_url': 'https://www.imdb.com/title/tt1350498/', 58 | 'csfd_url': 'https://www.csfd.cz/film/258268-megazralok-vs-obri-chobotnice/', 59 | }, 60 | ] 61 | 62 | 63 | def filter_movies(movies, name): 64 | if name is not None: 65 | filtered_movies = [] 66 | for movie in movies: 67 | if name in movie['name'].lower(): 68 | filtered_movies.append(movie) 69 | return filtered_movies 70 | else: 71 | return movies 72 | 73 | 74 | def represent_movies(movies, base_url): 75 | movies_list = [] 76 | for movie in movies: 77 | movies_list.append({ 78 | 'name': movie['name'], 79 | 'url': '{0}/movies/{1}'.format(base_url, movie['id']), 80 | }) 81 | return movies_list 82 | 83 | 84 | def create_movie_id(movies): 85 | ids = [] 86 | for movie in movies: 87 | ids.append(movie['id']) 88 | return max(ids) + 1 89 | 90 | 91 | class MovieListResource(): 92 | 93 | def on_get(self, request, response): 94 | name = request.get_param('name') 95 | base_url = request.prefix 96 | 97 | filtered_movies = filter_movies(movies, name) 98 | movies_repr = represent_movies(filtered_movies, base_url) 99 | response.body = json.dumps(movies_repr) 100 | 101 | def on_post(self, request, response): 102 | movie = json.load(request.bounded_stream) 103 | movie['id'] = create_movie_id(movies) 104 | movies.append(movie) 105 | 106 | movie_url = '{0}/movies/{1}'.format(request.prefix, movie['id']) 107 | 108 | movie_repr = dict(movie) 109 | movie_repr['url'] = movie_url 110 | del movie_repr['id'] 111 | 112 | response.status = '201 Created' 113 | response.set_header('Location', movie_url) 114 | response.body = json.dumps(movie_repr) 115 | 116 | 117 | def get_movie_by_id(movies, id): 118 | for movie in movies: 119 | if movie['id'] == id: 120 | return movie 121 | 122 | 123 | def remove_movie_by_id(movies, id): 124 | for i, movie in enumerate(movies): 125 | if movie['id'] == id: 126 | del movies[i] 127 | return True 128 | return False 129 | 130 | 131 | class MovieDetailResource(): 132 | 133 | def on_get(self, request, response, id): 134 | movie = get_movie_by_id(movies, id) 135 | if movie is None: 136 | response.status = '404 Not Found' 137 | else: 138 | base_url = request.prefix 139 | 140 | movie_repr = dict(movie) 141 | movie_repr['url'] = '{0}/movies/{1}'.format(base_url, movie['id']) 142 | del movie_repr['id'] 143 | 144 | response.body = json.dumps(movie_repr) 145 | 146 | def on_delete(self, request, response, id): 147 | movie = get_movie_by_id(movies, id) 148 | if movie is None: 149 | response.status = '404 Not Found' 150 | elif 'Bruce Willis' in movie['stars']: 151 | response.status = '403 Forbidden' 152 | else: 153 | remove_movie_by_id(movies, id) 154 | response.status = '204 No Content' 155 | 156 | 157 | app = falcon.API() 158 | app.add_route('/', PersonalDetailsResource()) 159 | app.add_route('/movies', MovieListResource()) 160 | app.add_route('/movies/{id:int}', MovieDetailResource()) 161 | -------------------------------------------------------------------------------- /en/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder 2 | 3 | import os 4 | import sys 5 | 6 | 7 | # -- Environment ------------------------------------------------------------- 8 | 9 | # Explicitly put the extensions directory to Python path 10 | sys.path.append(os.path.abspath('../_extensions')) 11 | 12 | 13 | # -- Project information ----------------------------------------------------- 14 | 15 | project = 'What is API?' 16 | copyright = '2020, Honza Javorek' 17 | author = 'Honza Javorek' 18 | 19 | # The short X.Y version 20 | version = '' 21 | # The full version, including alpha/beta/rc tags 22 | release = '' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.todo', 36 | 'sphinx.ext.githubpages', 37 | 'sphinx_tabs.tabs', 38 | 'mdn', 39 | 'codeexample', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['../_templates'] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = '.rst' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | # 57 | # This is also used if you do content translation via gettext catalogs. 58 | # Usually you set "language" from the command line for these cases. 59 | language = 'en' 60 | 61 | # List of patterns, relative to source directory, that match files and 62 | # directories to ignore when looking for source files. 63 | # This pattern also affects html_static_path and html_extra_path . 64 | exclude_patterns = ['Thumbs.db', '.DS_Store'] 65 | 66 | # The name of the Pygments (syntax highlighting) style to use. 67 | pygments_style = 'monokai' 68 | 69 | 70 | # -- Options for HTML output ------------------------------------------------- 71 | 72 | # The theme to use for HTML and HTML Help pages. See the documentation for 73 | # a list of builtin themes. 74 | html_theme = 'sphinx_rtd_theme' 75 | 76 | html_logo = '../_static/images/api-i.svg' 77 | 78 | # Theme options are theme-specific and customize the look and feel of a theme 79 | # further. For a list of options available for each theme, see the 80 | # documentation. 81 | # 82 | # html_theme_options = {} 83 | 84 | # Add any paths that contain custom static files (such as style sheets) here, 85 | # relative to this directory. They are copied after the builtin static files, 86 | # so a file named "default.css" will overwrite the builtin "default.css". 87 | html_static_path = ['../_static'] 88 | 89 | # Custom sidebar templates, must be a dictionary that maps document names 90 | # to template names. 91 | # 92 | # The default sidebars (for documents that don't match any pattern) are 93 | # defined by theme itself. Builtin themes are using these templates by 94 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 95 | # 'searchbox.html']``. 96 | # 97 | # html_sidebars = {} 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'whatisapi-doc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, 128 | # author, documentclass [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'whatisapi.tex', project, author, 'manual'), 131 | ] 132 | 133 | 134 | # -- Options for manual page output ------------------------------------------ 135 | 136 | # One entry per manual page. List of tuples 137 | # (source start file, name, description, authors, manual section). 138 | man_pages = [ 139 | (master_doc, 'whatisapi', project, [author], 1) 140 | ] 141 | 142 | 143 | # -- Options for Texinfo output ---------------------------------------------- 144 | 145 | # Grouping the document tree into Texinfo files. List of tuples 146 | # (source start file, target name, title, author, 147 | # dir menu entry, description, category) 148 | texinfo_documents = [ 149 | (master_doc, 'whatisapi', project, author, 'whatisapi', 150 | 'Learning materials to let you understand APIs', 'Miscellaneous'), 151 | ] 152 | 153 | 154 | # -- Options for Epub output ------------------------------------------------- 155 | 156 | # Bibliographic Dublin Core info. 157 | epub_title = project 158 | epub_author = author 159 | epub_publisher = author 160 | epub_copyright = copyright 161 | 162 | # The unique identifier of the text. This can be a ISBN number 163 | # or the project homepage. 164 | # 165 | # epub_identifier = '' 166 | 167 | # A unique identification for the text. 168 | # 169 | # epub_uid = '' 170 | 171 | # A list of files that should not be packed into the epub file. 172 | epub_exclude_files = ['search.html'] 173 | 174 | 175 | # -- External links check ------------------------------------------------- 176 | 177 | linkcheck_ignore = [ 178 | # Sphinx (contributing docs) 179 | 'http://127.0.0.1:8000', 180 | 181 | # Waitress & Falcon 182 | 'http://0.0.0.0:8080', 183 | 'http://127.0.0.1:8080', 184 | 'http://localhost:8080', 185 | ] 186 | 187 | 188 | # -- Extension configuration ------------------------------------------------- 189 | 190 | sphinx_tabs_valid_builders = ['linkcheck'] 191 | 192 | sphinx_tabs_nowarn = True 193 | 194 | 195 | # -- Options for todo extension ---------------------------------------------- 196 | 197 | # If true, `todo` and `todoList` produce output, else they produce nothing. 198 | todo_include_todos = True 199 | 200 | 201 | # -- Extensions -------------------------------------------------------------- 202 | 203 | def setup(app): 204 | app.add_css_file('css/cojeapi.css') 205 | -------------------------------------------------------------------------------- /cs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder 2 | 3 | import os 4 | import sys 5 | 6 | 7 | # -- Environment ------------------------------------------------------------- 8 | 9 | # Explicitly put the extensions directory to Python path 10 | sys.path.append(os.path.abspath('../_extensions')) 11 | 12 | 13 | # -- Project information ----------------------------------------------------- 14 | 15 | project = 'Co je API?' 16 | copyright = '2020, Honza Javorek' 17 | author = 'Honza Javorek' 18 | 19 | # The short X.Y version 20 | version = '' 21 | # The full version, including alpha/beta/rc tags 22 | release = '' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.todo', 36 | 'sphinx.ext.githubpages', 37 | 'sphinx_tabs.tabs', 38 | 'mdn', 39 | 'codeexample', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['../_templates'] 44 | 45 | # The suffix(es) of source filenames. 46 | # You can specify multiple suffix as a list of string: 47 | # 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = '.rst' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | # 57 | # This is also used if you do content translation via gettext catalogs. 58 | # Usually you set "language" from the command line for these cases. 59 | language = 'cs' 60 | 61 | # List of patterns, relative to source directory, that match files and 62 | # directories to ignore when looking for source files. 63 | # This pattern also affects html_static_path and html_extra_path . 64 | exclude_patterns = ['Thumbs.db', '.DS_Store'] 65 | 66 | # The name of the Pygments (syntax highlighting) style to use. 67 | pygments_style = 'monokai' 68 | 69 | 70 | # -- Options for HTML output ------------------------------------------------- 71 | 72 | # The theme to use for HTML and HTML Help pages. See the documentation for 73 | # a list of builtin themes. 74 | html_theme = 'sphinx_rtd_theme' 75 | 76 | html_logo = '../_static/images/api-i.svg' 77 | 78 | # Theme options are theme-specific and customize the look and feel of a theme 79 | # further. For a list of options available for each theme, see the 80 | # documentation. 81 | # 82 | # html_theme_options = {} 83 | 84 | # Add any paths that contain custom static files (such as style sheets) here, 85 | # relative to this directory. They are copied after the builtin static files, 86 | # so a file named "default.css" will overwrite the builtin "default.css". 87 | html_static_path = ['../_static'] 88 | 89 | # Custom sidebar templates, must be a dictionary that maps document names 90 | # to template names. 91 | # 92 | # The default sidebars (for documents that don't match any pattern) are 93 | # defined by theme itself. Builtin themes are using these templates by 94 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 95 | # 'searchbox.html']``. 96 | # 97 | # html_sidebars = {} 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'cojeapi-doc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, 128 | # author, documentclass [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'cojeapi.tex', project, author, 'manual'), 131 | ] 132 | 133 | 134 | # -- Options for manual page output ------------------------------------------ 135 | 136 | # One entry per manual page. List of tuples 137 | # (source start file, name, description, authors, manual section). 138 | man_pages = [ 139 | (master_doc, 'cojeapi', project, [author], 1) 140 | ] 141 | 142 | 143 | # -- Options for Texinfo output ---------------------------------------------- 144 | 145 | # Grouping the document tree into Texinfo files. List of tuples 146 | # (source start file, target name, title, author, 147 | # dir menu entry, description, category) 148 | texinfo_documents = [ 149 | (master_doc, 'cojeapi', project, author, 'cojeapi', 150 | 'Materiály, díky kterým pochopíte API', 'Miscellaneous'), 151 | ] 152 | 153 | 154 | # -- Options for Epub output ------------------------------------------------- 155 | 156 | # Bibliographic Dublin Core info. 157 | epub_title = project 158 | epub_author = author 159 | epub_publisher = author 160 | epub_copyright = copyright 161 | 162 | # The unique identifier of the text. This can be a ISBN number 163 | # or the project homepage. 164 | # 165 | # epub_identifier = '' 166 | 167 | # A unique identification for the text. 168 | # 169 | # epub_uid = '' 170 | 171 | # A list of files that should not be packed into the epub file. 172 | epub_exclude_files = ['search.html'] 173 | 174 | 175 | # -- External links check ------------------------------------------------- 176 | 177 | linkcheck_ignore = [ 178 | # Sphinx (contributing docs) 179 | 'http://127.0.0.1:8000', 180 | 181 | # Waitress & Falcon 182 | 'http://0.0.0.0:8080', 183 | 'http://127.0.0.1:8080', 184 | 'http://localhost:8080', 185 | 186 | # heureka.cz blocks requests from CI 187 | 'https://www.heureka.cz', 188 | 'https://sluzby.heureka.cz/napoveda/xml-feed/', 189 | ] 190 | 191 | 192 | # -- Extension configuration ------------------------------------------------- 193 | 194 | sphinx_tabs_valid_builders = ['linkcheck'] 195 | 196 | sphinx_tabs_nowarn = True 197 | 198 | 199 | # -- Options for todo extension ---------------------------------------------- 200 | 201 | # If true, `todo` and `todoList` produce output, else they produce nothing. 202 | todo_include_todos = True 203 | 204 | 205 | # -- Extensions -------------------------------------------------------------- 206 | 207 | def setup(app): 208 | app.add_css_file('css/cojeapi.css') 209 | -------------------------------------------------------------------------------- /cs/02-klient-server.rst: -------------------------------------------------------------------------------- 1 | .. _klient: 2 | .. _server: 3 | .. _klient-server: 4 | 5 | Klient a server 6 | =============== 7 | 8 | Jak jsme si vysvětlili v :ref:`předešlé kapitole `, API je dohoda mezi dvěma stranami o tom, jak si mezi sebou budou povídat. Těmto stranám se říká *klient* a *server*. 9 | 10 | **Server** je ta strana, která má zajímavé informace, nebo něco zajímavého umí, a umožňuje ostatním na internetu, aby toho využili. V našem počátečním příkladu by se v širším slova smyslu dal jako server označit :ref:`ČHMÚ `, jenž poskytuje API ke svým předpovědím počasí, nebo :ref:`ČNB `, která poskytuje API ke svému kurzovnímu lístku. Ve skutečnosti je server program, který donekonečna běží na nějakém počítači oné instituce a je připraven všem ostatním na internetu odpovídat na požadavky. 11 | 12 | **Klient** je program, který posílá požadavky na server a z odpovědí se snaží poskládat něco užitečného. Klient je tedy :ref:`mobilní aplikace s mráčky a sluníčky ` nebo náš prohlížeč, v němž jsme si :ref:`otevírali kurzovní lístek ČNB `. Je to ale i ten :ref:`robot `, který za Heureku načítá informace o zboží v e-shopech. 13 | 14 | .. todo:: 15 | obrazek server/klient, jeden server ktery poskytuje data a dva klienti, robot a clovek, jak to ctou, udelat tam jasne request response 16 | 17 | 18 | Obecný klient 19 | ------------- 20 | 21 | Mobilní aplikace na počasí je klient, který někdo vytvořil pro jeden konkrétní úkol. Takový většinou umí pracovat jen s jedním konkrétním API. To je užitečné, pokud chceme akorát vědět jaké je počasí, ale už méně, pokud si chceme zkoušet práci s více API zároveň. Proto existují obecní klienti. 22 | 23 | 24 | Prohlížeč jako obecný klient 25 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | 27 | Pokud z API chceme pouze číst a ono nevyžaduje žádné přihlašování, můžeme jej vyzkoušet i v prohlížeči, jako by to byla webová stránka. To jsme si ostatně už dříve předvedli v případě :ref:`kurzovního lístku ČNB `. Pokud v prohlížeči přejdeme na odkaz `Stažení v textovém formátu `__, uvidíme odpověď z API serveru. 28 | 29 | .. image:: ../_static/images/cnb-api.png 30 | :alt: ČNB - kurzovní lístek v textovém formátu 31 | :align: center 32 | 33 | .. _omdb-api: 34 | 35 | Zkusme jiný příklad. `OMDb `__ je API, které poskytuje informace o filmech. Po `registraci `__ nám bude na e-mail zaslán tajný klíč, se kterým můžeme na API zdarma posílat 1000 požadavků denně. 36 | 37 | .. image:: ../_static/images/omdb-api-key.png 38 | :alt: OMDb registrace 39 | :align: center 40 | 41 | Nyní zkusme v API najít seriál `Westworld `_. Podle dokumentace OMDb můžeme složit následující adresu, pokud hledáme slovo ``westworld`` v názvu titulu:: 42 | 43 | https://www.omdbapi.com/?t=westworld&apikey=abcd123 44 | 45 | Místo ``abcd123`` má být samozřejmě tajný API klíč, který nám přišel e-mailem. Zkusíme adresu otevřít v prohlížeči: 46 | 47 | .. image:: ../_static/images/omdb-westworld-browser.png 48 | :alt: OMDb - Westworld v prohlížeči 49 | :align: center 50 | 51 | Smyslem API je vracet odpovědi pro stroje, takže dostaneme změť písmenek, která se člověku nečte zrovna nejlépe. Něco v ní ale vidět lze - není těžké rozluštit, že seriál je drama a hraje v něm `Evan Rachel Wood `__. 52 | 53 | 54 | Obecný klient v příkazové řádce: curl 55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | Pokud se k API budeme potřebovat přihlásit nebo s ním zkoušet dělat složitější věci než jen čtení, nebude nám prohlížeč stačit. 58 | 59 | Proto je dobré se naučit používat program `curl `__. Spouští se v příkazové řádce a je to švýcarský nůž všech, kteří se pohybují kolem webových API. Je tak `používaný a významný `__, že za něj jeho autor `Daniel Stenberg `__ dostal v roce 2017 `ocenění z rukou švédského krále `__. 60 | 61 | Instalace curl 62 | ^^^^^^^^^^^^^^ 63 | 64 | .. warning:: 65 | Pokud materiály procházíte v rámci :ref:`workshopu ` a curl jste si už :ref:`nainstalovali v rámci přípravy `, můžete tuto sekci přeskočit. 66 | 67 | .. include:: _install_curl.rst 68 | 69 | .. _curl-examples: 70 | 71 | Příklady s curl 72 | ^^^^^^^^^^^^^^^ 73 | 74 | Nyní můžeme curl vyzkoušet: 75 | 76 | .. literalinclude:: ../code/curl_cnb.txt 77 | :language: text 78 | :lines: 1 79 | 80 | Když příkaz zadáme a spustíme, říkáme tím programu curl, že má poslat požadavek na uvedenou adresu a vypsat to, co mu ČNB pošle zpět. 81 | 82 | .. literalinclude:: ../code/curl_cnb.txt 83 | :language: text 84 | 85 | .. note:: 86 | Pokud používáte Windows, je velká šance, že se vám při zkoušení příkladu výše zobrazí špatně diakritika. Nic si z toho nedělejte, příkazová řádka na Windows má jiné kódování textu než zbytek světa. Důležité je, pokud vidíte seznam kurzů měn. 87 | 88 | Totéž můžeme udělat i s adresou, která nám vracela informace z OMDb: 89 | 90 | .. literalinclude:: ../code/curl_omdb.txt 91 | :language: text 92 | 93 | Program curl toho samozřejmě umí více a proto je tak užitečný, ale to si ukážeme později. 94 | 95 | 96 | Obecný klient jako aplikace 97 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 | 99 | Příkazová řádka je sice velmi mocný a univerzální nástroj, ale není vždy nejpříjemnější na každodenní používání. Následující programy jsou obecní klienti, na které se dá normálně klikat: 100 | 101 | - `Postman `__ - zdarma, pro všechny operační systémy 102 | - `RESTClient `__ - zdarma, pro všechny operační systémy, doplněk do prohlížeče `Firefox `__ 103 | - `Paw `__ - dražší, ale velmi vyladěný profesionální nástroj pro macOS 104 | 105 | Stejně jako v případě práce s `Gitem `__ i zde platí, že si můžeme nainstalovat sebekrásnější program, ale pokud budeme potřebovat vyřešit nějaký problém, dostaneme rady většinou v podobě curl příkazu. 106 | 107 | Stejně jako u Gitu i curl má velmi složitý systém paramterů a přepínátek, stejně jako u Gitu jim málokdo dokonale rozumí, ale stejně jako u Gitu je to přesně to, co lidé nakonec používají jako společný *jazyk*, do kterého zapisují a přes který sdílí řešení problémů - například na `StackOverflow `__. 108 | 109 | 110 | Klient pro konkrétní úkol 111 | ------------------------- 112 | 113 | Obecného klienta musí ovládat člověk. To je přesně to, co potřebujeme, když si chceme nějaké API vyzkoušet, ale celý smysl API je v tom, aby je programy mohly využívat automaticky. 114 | 115 | K tomu slouží klienti, které někdo vytvořil pro konkrétní úkol. Jak už jsme si řekli, je to třeba ona aplikace pro zobrazování počasí, která je schopna si data z API přečíst úplně sama. Aby to ale mohla udělat, musí odpověď ze serveru přijít ve formátu, kterému bude rozumět. A o tom, jak to celé funguje, bude následující kapitola. 116 | -------------------------------------------------------------------------------- /cs/05-tvorime-klienta.rst: -------------------------------------------------------------------------------- 1 | .. _creating-client: 2 | 3 | Tvoříme klienta 4 | =============== 5 | 6 | Doteď jsme používali obecného klienta v podobě prohlížeče nebo programu curl. Obecného klienta musí ovládat člověk. To je přesně to, co potřebujeme, když si chceme nějaké API vyzkoušet, ale celý smysl API je v tom, aby je programy mohly využívat automaticky. 7 | 8 | .. warning:: 9 | Na obsahu této části se stále pracuje. 10 | 11 | Základ aplikace 12 | --------------- 13 | 14 | Pokud chceme naprogramovat klienta pro konkrétní úkol, můžeme ve většině jazyků použít nějakou buďto vestavěnou, nebo doinstalovanou knihovnu. V případě jazyka Python použijeme `Requests `__. 15 | 16 | Vytvoříme si pro náš projekt nový adresář ``cojeapi-client`` a v něm `virtuální prostředí `__, které si aktivujeme. Poté nainstalujeme Requests: 17 | 18 | .. code-block:: shell 19 | 20 | (venv)$ pip install requests 21 | 22 | Pokud jste prošli :ref:`kapitolou o tvorbě API serveru `, ujistěte se, že pro klienta si vytváříte nový projekt - novou složku, nové virtuální prostředí, atd. Vytváříme novou, na serveru zcela nezávislou a samostatnou aplikaci. 23 | 24 | Nyní můžeme začít s tvorbou klienta. Jenže specializovaný klient potřebuje nějaké API, na které by se mohl specializovat. K tomu se nám náramně hodí API z předešlé kapitoly. V adresáři ``cojeapi-client`` si vytvoříme nový soubor s názvem ``client.py`` a použijeme v něm Requests pro jednoduchý požadavek na server. Funkce `requests.get `__ nám umožní poslat požadavek metodou :method:`get`. Naše je veřejně dostupné na adrese https://cojeapi.honzajavorek.now.sh, takže ji použijeme jako cíl požadavku. Následně vypíšeme detaily odpovědi, kterou dostaneme: 25 | 26 | .. literalinclude:: ../code/client/01_base/client.py 27 | :language: python 28 | 29 | Napsali jsme program, který je ekvivalentem následujícího příkazu: 30 | 31 | .. code-block:: text 32 | 33 | $ curl "https://cojeapi.honzajavorek.now.sh/" 34 | 35 | Zkusme jej spustit, zatímco nám ve vedlejším okně jede naše API: 36 | 37 | .. literalinclude:: ../code/client/01_base/test.txt 38 | :language: text 39 | 40 | A je to, udělali jsme svůj první požadavek na server! Vidíme, že se nám povedlo vypsat status kód odpovědi, hlavičky, i tělo. Hlavičky nám Requests rovnou poskytují jako Python `slovník `__. Tělo odpovědi ale máme zatím jako řetězec. 41 | 42 | Čteme JSON 43 | ---------- 44 | 45 | Pokud bychom chtěli číst tělo zprávy, narazíme na fakt, že jej máme jako řetězec: 46 | 47 | .. literalinclude:: ../code/client/02_json_error/client.py 48 | :language: python 49 | :emphasize-lines: 7 50 | 51 | .. literalinclude:: ../code/client/02_json_error/test.txt 52 | :language: text 53 | :emphasize-lines: 4-7 54 | 55 | Tělo je **text ve formátu JSON** (což nám sděluje i hlavička :header:`Content-Type`). Nešlo by jej nějak dostat jako slovník? Šlo - přesně na toto mají Requests metodu `.json() `__: 56 | 57 | .. literalinclude:: ../code/client/03_json/client.py 58 | :language: python 59 | :emphasize-lines: 7 60 | 61 | Nyní máme z textu ve formátu JSON obyčejný Python slovník: 62 | 63 | .. literalinclude:: ../code/client/03_json/test.txt 64 | :language: text 65 | :emphasize-lines: 4 66 | 67 | Zpracováváme odpověď 68 | -------------------- 69 | 70 | Program, který dělá totéž co curl, není popravdě moc užitečný program. Pojďme zkusit využít naše API k napsání programu, jenž z něj zjistí seznam filmů a vypíše je. 71 | 72 | .. tabs:: 73 | 74 | .. tab:: Cvičení 75 | 76 | Přepište program tak, aby posílal požadavek na https://cojeapi.honzajavorek.now.sh/movies, přečetl odpověď, prošel všechny filmy získané z těla odpovědi a pomocí ``print()`` vypsal název každého z nich. 77 | 78 | .. tab:: Řešení 79 | 80 | .. literalinclude:: ../code/client/04_movies/client.py 81 | :language: python 82 | 83 | Pokud program spustíme, měl by vypsat všechny filmy z dotazovaného API: 84 | 85 | .. literalinclude:: ../code/client/04_movies/test.txt 86 | :language: text 87 | 88 | .. tabs:: 89 | 90 | .. tab:: Cvičení 91 | 92 | Jestliže procházíte tento návod v rámci workshopu, například `PyWorking `__, použijte ve vašem programu místo https://cojeapi.honzajavorek.now.sh adresu API někoho jiného z účastníků. Pokud tam má i jiné filmy než byly v návodu, měl by je program vypsat. 93 | 94 | Získáváme doplňující data 95 | ------------------------- 96 | 97 | Co kdybychom chtěli ke každému filmu vypsat i rok, kdy byl uveden? Rok není v seznamu filmů k dispozici, nachází se na detailu každého filmu. Budeme tedy muset udělat jeden požadavek na seznam filmů a poté ještě požadavek na každý film ze seznamu, abychom zjistili rok. 98 | 99 | .. literalinclude:: ../code/client/05_year/client.py 100 | :language: python 101 | 102 | Vidíme, že pro každý film děláme další požadavek na API a teprve z jeho výsledku vypisujeme jméno a rok. Pokud program spustíte, bude trvat podstatně déle, než skončí. 103 | 104 | .. literalinclude:: ../code/client/05_year/test.txt 105 | :language: text 106 | 107 | To, že musíme posílat požadavek na každý film zvlášť je buď důsledkem toho, že se snažíme z API zjistit kombinaci informací, která není úplně obvyklá, nebo důsledkem toho, že někdo API špatně navrhl. Narazili jsme přesně na tu situaci, která byla popsána v sekci :ref:`apidesign` při tvorbě serveru. 108 | 109 | Agregované informace 110 | -------------------- 111 | 112 | Data z API nemusíme jen číst a vypisovat. Zajímavější je, když je využijeme ke zjištění nových, dříve netušených informací. Pojďme například zjistit, který z filmů v seznamu je nejnovější. 113 | 114 | .. literalinclude:: ../code/client/06_newest/client.py 115 | :language: python 116 | 117 | Uvedený kód upravuje předchozí příklad, ale místo vypisování názvů filmů a jejich roků vydání rovnou zjišťuje, který z nich má rok s největší hodnotou. Takový film potom na konci vypíše. 118 | 119 | .. literalinclude:: ../code/client/06_newest/test.txt 120 | :language: text 121 | 122 | Toto je jen malá ukázka toho, jak lze data z API agregovat, tzn. zjišťovat informace, jejichž výpočet protíná několik odpovědí. 123 | 124 | Chyby 125 | ----- 126 | 127 | .. warning:: 128 | Tato kapitola není ještě připravena. 129 | 130 | Zapisujeme 131 | ---------- 132 | 133 | .. warning:: 134 | Tato kapitola není ještě připravena. 135 | 136 | Mažeme 137 | ------ 138 | 139 | .. warning:: 140 | Tato kapitola není ještě připravena. 141 | 142 | Kódování parametrů 143 | ------------------ 144 | 145 | .. warning:: 146 | Tato kapitola není ještě připravena. 147 | 148 | .. todo:: 149 | co dáváme do parametrů se musí prohnat nějakym urlencoding 150 | příklady s nějakým (reverse) geocoding api (google, seznam?) 151 | 152 | Zabezpečení 153 | ----------- 154 | 155 | .. warning:: 156 | Tato kapitola není ještě připravena. 157 | 158 | .. todo:: 159 | mechanismus http/https 160 | basic auth 161 | oauth 162 | většinou nějaký token (vysvětlit token), který se narve do hlavičky 163 | auth token - něco vygenerováno jen pro nás, co je tajné a neměli bychom to nikomu dávat a ukazovat 164 | 165 | příklad s GitHubem, vygenerujeme token, dáme do ENV, nasosáme v programu a můžeme použít 166 | 167 | Pracujeme s veřejnými API 168 | ------------------------- 169 | 170 | OMDb 171 | ^^^^ 172 | 173 | GitHub 174 | ^^^^^^ 175 | 176 | Specializované knihovny (SDK) 177 | ----------------------------- 178 | 179 | .. warning:: 180 | Tato kapitola není ještě připravena. 181 | 182 | .. todo:: 183 | vysvětlit specializovaného klienta 184 | příklady 185 | 186 | .. todo:: 187 | připomenout, že než jdeme psát klienta na zelené louce, měli bychom ověřit, že už není nějaká hotová SDK knihovna (příklady z pypi) 188 | 189 | základní příklady s requests, GET, POST 190 | https://github.com/honzajavorek/cojeapi/issues/2 191 | -------------------------------------------------------------------------------- /cs/01-uvod-do-api.rst: -------------------------------------------------------------------------------- 1 | .. _uvod: 2 | 3 | Úvod do API 4 | =========== 5 | 6 | Na API si nelze sáhnout a není možné je vidět, ale přesto dnes nepřímo ovlivňují život každého z nás. Bohužel není snadné zjistit, co ona API vlastně jsou. `Článek na Wikipedii `__ začíná tím, že jde o *rozhraní pro programování aplikací*, a pokračuje odborným textem, jímž se běžný smrtelník prokouše jen těžko. Tento text se snaží API **vysvětlit na příkladu a běžnými slovy**. 7 | 8 | 9 | .. _chmu: 10 | 11 | Předpověď počasí na mobilu 12 | -------------------------- 13 | 14 | Na jakémkoliv mobilu dnes najdete předpověď počasí. Jak se tam ale dostane? Nejspíš tušíte, že předpovědi vznikají v `Českém hydrometeorologickém ústavu `__. Jak je ale možné, že jakmile se v ČHMÚ shodnou na zítřejší bouřce, objeví se vám to **během sekundy** na displeji? 15 | 16 | .. image:: ../_static/images/chmu1.png 17 | :align: center 18 | :width: 60% 19 | 20 | Ať už šlo o noviny, rozhlas nebo televizi, dříve redakcím stačilo, aby si předpověď zjistily **jednou denně**. Nevím, jak to přesně probíhalo, ale představuji si, že někdo zavolal do sídla ČHMÚ v Komořanech, kde to zvedli a nadiktovali sluníčka nebo mráčky. Dnes už by to takto fungovat nemohlo. Data o počasí, která ČHMÚ uveřejňuje, je potřeba **okamžitě zobrazovat na tisícovkách míst na internetu**. 21 | 22 | ČHMÚ má svoje webové stránky, kde předpovědi uveřejňuje. Jenže to vyžaduje, aby je na druhé straně **přečetl člověk a někam je přepsal**. Zatímco u redakcí si snad lze představit studenta žurnalistiky na brigádě, jak zoufale nonstop sleduje web ČHMÚ a opisuje povodňová varování na web zpravodajství, pro aplikaci ve vašem mobilu by toto byla nepřekonatelná komplikace. 23 | 24 | Váš mobil potřebuje mít možnost **zjistit si předpověď automaticky**. Ústav tedy ukládá informace o počasí tak, aby byly **strojově čitelné**, a zpřístupňuje je **ke stažení** na svém webu. Představte si to zhruba tak, že místo aby nakreslili mráčky na svůj web, uloží odborníci v ČHMÚ všechno do nějaké tabulky, třeba i Excelové, kde je předem dané, co znamená jaký řádek a sloupec. Navíc jasně řeknou, že tato tabulka se bude vždy nacházet na adrese ``https://chmi.cz/predpoved.xslx`` a budou v ní vždy aktuální informace. 25 | 26 | .. image:: ../_static/images/chmu2.png 27 | :align: center 28 | :width: 60% 29 | 30 | Aplikace ve vašem mobilu pak může z adresy ``https://chmi.cz/predpoved.xslx`` každou hodinu tabulku stáhnout, rozluštit její řádky a sloupce, poskládat z toho aktuální předpověď počasí a zobrazit vám ji jako mráčky. No a tomuto mechanismu, kdy **jedna strana něco na stabilní adrese poskytne ve strojově čitelné formě, a druhá je schopna to kdykoliv strojově číst a něco užitečného s tím dělat**, se říká webové API. 31 | 32 | 33 | .. _cnb: 34 | 35 | Příklad: Kurzy měn 36 | ------------------ 37 | 38 | Jako příklad API se pojďme podívat na kurzovní lístek České národní banky. Půjdeme na stránku `Kurzy devizového trhu `__, kde ČNB vypisuje tabulku kurzů. 39 | 40 | .. image:: ../_static/images/cnb-website.png 41 | :alt: ČNB - kurzovní lístek 42 | :align: center 43 | 44 | Pokud bychom chtěli mít e-shop, na kterém bude možné kromě korun platit i eurem, nebo pokud bychom tvořili mobilní appku na převod měn, bude se nám tento kurzovní lístek určitě hodit. 45 | 46 | Jenže zatímco se takovýto lístek čte velmi pěkně lidem, strojově je čitelný mizerně. Není to sice nemožné, ale není to příjemné a především to není vůbec spolehlivé. 47 | 48 | .. image:: ../_static/images/cnb.png 49 | :align: center 50 | :width: 80% 51 | 52 | ČNB proto poskytuje jednoduché API. Pod tabulkou je odkaz `Stažení v textovém formátu `__, který vede na tu samou tabulku, ale ve formátu, který lze snadněji a především spolehlivě strojově přečíst. 53 | 54 | .. image:: ../_static/images/cnb-api.png 55 | :alt: ČNB - kurzovní lístek v textovém formátu 56 | :align: center 57 | 58 | 59 | Webová API, která možná znáte 60 | ----------------------------- 61 | 62 | 63 | Mobilní aplikace 64 | ^^^^^^^^^^^^^^^^ 65 | 66 | Skoro všechny aplikace na našem mobilním telefonu za sebou mají API, pomocí kterého komunikují se službou na pozadí. Když si na mobilu spustíme přehrávač hudby `Spotify `__ a označíme si `Futuretro `__ od `Tata Bojs `__ jako oblíbené album, mobilní appka tuto informaci okamžitě pošle přes API na :ref:`server ` do centrály Spotify. Když potom půjdeme k počítači a otevřeme si na něm program Spotify, ten si zase přes API všechno zjistí a uvidíme, že Futuretro se i tam zobrazuje v našich oblíbených albech. 67 | 68 | .. image:: ../_static/images/spotify.png 69 | :align: center 70 | :width: 60% 71 | 72 | 73 | .. _heureka: 74 | 75 | Srovnávače zboží 76 | ^^^^^^^^^^^^^^^^ 77 | 78 | Pokud máme e-shop, možná bychom chtěli, aby byly naše produkty k nalezení na `Heureka.cz `__. Jak to funguje? Je potřeba informace o produktech vystavit do souboru ve formátu :ref:`XML `. Heureka přímo `předepisuje, jak má takový soubor vypadat `__. Pokud jej dáme k dispozici, vystavujeme tím API pro Heureku. Na její straně potom může nějaký robot přes takováto jednotlivá API automaticky načítat informace o produktech z e-shopů. 79 | 80 | .. image:: ../_static/images/heureka.png 81 | :align: center 82 | :width: 80% 83 | 84 | 85 | Nové články 86 | ^^^^^^^^^^^ 87 | 88 | Říká vám něco `RSS `__? Dnes je tato technologie mírně za zenitem, ale dříve jí byl plný internet. Je to způsob, jak může váš blog nebo zpravodajský server dávat ostatním najevo, že na něm vyšly nové články. 89 | 90 | .. figure:: ../_static/images/rss-icon.png 91 | :alt: Ikona RSS 92 | :scale: 10% 93 | :align: center 94 | 95 | Symbol označující RSS 96 | 97 | Funguje to tak, že váš blog vystaví do souboru ve formátu :ref:`XML ` informace o publikovaných článcích. Kdokoliv jiný pak může tento soubor stáhnout a strojově přečíst. To dělaly RSS čtečky, jako například `Google Reader `__. Dnes tak funguje `Feedly `__. Běžný uživatel se dnes již s RSS tak často nesetká, protože jeho funkci nahradily sociální sítě, ale weby jej stále poskytují - například časopis `Respekt `__ má hned `několik RSS pro každou rubriku `__. 98 | 99 | .. image:: ../_static/images/rss.png 100 | :align: center 101 | :width: 80% 102 | 103 | 104 | Existují i jiná API než webová? 105 | ------------------------------- 106 | 107 | Termín API je ve skutečnosti obecnější. Původně označuje dohodu o tom, jak si spolu mají povídat různé ucelené kusy jednoho programu. Často se v tomto smyslu používá jen slovo *interface*, česky *rozhraní*, které ve zkratce API představuje písmeno I. 108 | 109 | Webové API je také takovou dohodou, akorát ne mezi kusy jednoho programu, spíše mezi celými samostatnými programy a celými informačními systémy (firmami, institucemi), zařízeními (mobil, počítač), apod. 110 | 111 | Když se tedy lidé baví o programování, je dobré myslet i na onen širší význam a nenechat se vyvést z míry, pokud někdo jako API označí i jiné věci, než jsou webová API. Všude v tomto textu však platí to, že API vždy znamená webové API. 112 | 113 | .. figure:: ../_static/images/non-web-api.png 114 | :align: center 115 | :width: 60% 116 | 117 | Dokumentace všech veřejných rozhraní frameworku `Flask `__ také používá zkratku API 118 | 119 | 120 | Kam dál? 121 | -------- 122 | 123 | Zatímco tato kapitola by měla být srozumitelná každému, všechny následující již vyžadují základní programátorské dovednosti. Ty lze získat například na `Nauč se Python! `_ 124 | 125 | V dalších kapitolách si vysvětlíme některé základní pojmy, naučíme se jak cizí API využít v našem programu a vytvoříme si i svoje vlastní malé API. 126 | -------------------------------------------------------------------------------- /cs/03-zakladni-pojmy.rst: -------------------------------------------------------------------------------- 1 | Základní pojmy 2 | ============== 3 | 4 | Než se ponoříme do samotné tvorby klienta nebo serveru, je dobré pochopit některé základní pojmy kolem API. 5 | 6 | Protokol 7 | -------- 8 | 9 | Celé dorozumívání mezi klientem a serverem se odehrává přes tzv. protokol. To není nic jiného, než smluvený způsob, co bude kdo komu posílat a jakou strukturu to bude mít. Protokolů je v počítačovém světě spousta, ale nás bude zajímat jen `HTTP `__, protože ten využívají webová API a ostatně i web samotný. Není to náhoda, že adresa internetových stránek v prohlížeči zpravidla začíná ``http://`` (nebo ``https://``). 10 | 11 | .. _http: 12 | 13 | HTTP 14 | ~~~~ 15 | 16 | Jak jsme mohli pozorovat i na předchozích příkladech, dorozumívání mezi klientem a serverem probíhá formou požadavku (*HTTP request*), jenž posílá klient na server, a odpovědi (*HTTP response*), kterou server posílá zpět. Každá z těchto zpráv má své náležitosti. 17 | 18 | 19 | .. _http-request: 20 | 21 | Součásti požadavku 22 | ^^^^^^^^^^^^^^^^^^ 23 | 24 | Požadavek může vypadat nějak takto:: 25 | 26 | GET http://api.example.com/movies?genre=drama&duration=150 27 | 28 | Přesně takové požadavky jsme posílali v předchozích příkladech v prohlížeči nebo s curl. Požadavek ale může vypadat i takto: 29 | 30 | .. code-block:: text 31 | 32 | POST http://api.example.com/movies 33 | 34 | User-Agent: cojeapi/1.0 (+https://cojeapi.cz) 35 | Authorization: c630c64829efbd162eeb5f9e9f878e9ef3fcf757 36 | Content-Type: application/json 37 | 38 | { 39 | "title": "Ariel", 40 | "director": "Aki Kaurismäki", 41 | "year": 1988, 42 | "duration": 73 43 | } 44 | 45 | Takový požadavek bychom už nemohli poslat přes prohlížeč, protože má více částí, ne jen adresu. Šlo by jej ale poslat s pomocí curl a jeho přepínačů. Které části požadavku jsou povinné, co vše v nich lze poslat, a k čemu jednotlivé části jsou? 46 | 47 | metoda (*HTTP method*, někdy také *HTTP verb*) 48 | Protokol HTTP `přesně vysvětluje všechny metody `__ a jaké má jejich použití důsledky pro požadavek i odpověď. Například metoda :method:`get` má tu vlastnost, že provádí pouze čtení a nemůžeme s ní tedy přes API něco změnit - je tzv. *bezpečná*. Metody :method:`put` nebo :method:`delete` zase dávají záruku, že i když je pošleme několikrát za sebou, dostaneme vždy stejnou odpověď. 49 | 50 | Příklady: ``GET``, ``POST``, ``PUT``, ``DELETE``, a další 51 | 52 | adresa s parametry (*URL* s *query parameters*) 53 | S URL adresou se běžně setkáváme na internetu, takže ji asi není potřeba velmi představovat. V kontextu API je zajímavé, že na konci adresy může být otazník a za ním parametry. Pokud je parametrů víc, oddělují se znakem ``&``. 54 | 55 | Příklady: 56 | 57 | - ``http://api.example.com/movies/`` 58 | - ``http://api.example.com/movies?genre=drama&duration=150`` 59 | 60 | .. note:: 61 | Někdy můžete narazit na to, že se adresa označuje jako URI místo URL. Teoreticky mezi URI a URL existuje jemný rozdíl, ale v praxi ho nikdo nezná a obě zkratky označují totéž. 62 | 63 | hlavičky (*headers*) 64 | Hlavičky jsou vlastně jen další parametry. Liší se v tom, že je neposíláme jako součást adresy a na rozdíl od URL parametrů podléhají nějaké standardizaci a konvencím. 65 | 66 | Příklady: 67 | 68 | - ``Date: Thu, 25 Oct 2018 09:29:48 GMT`` 69 | - ``Content-Type: text/plain`` 70 | 71 | tělo (*body*) 72 | Tělo zprávy je krabice, kterou s požadavkem posíláme, a do které můžeme vložit, co chceme. Tedy nejlépe něco, čemu bude API na druhé straně rozumět. Tělo může být prázdné. V těle můžeme poslat obyčejný text, data v nějakém formátu, ale klidně i obrázek. Aby API na druhé straně vědělo, co v krabici je a jak ji má rozbalovat, je potřeba s tělem zpravidla posílat hlavičku :header:`Content-Type`. 73 | 74 | Příklady: ``Ahoj!``, ``{"title": "Ariel"}`` 75 | 76 | Když chceme poslat požadavek, musíme nejdříve vyčíst z dokumentace API, jak jej máme správně položit tak, aby API vrátilo co chceme. 77 | 78 | 79 | .. _http-response: 80 | 81 | Součásti odpovědi 82 | ^^^^^^^^^^^^^^^^^ 83 | 84 | Odpověď typicky vypadá následovně: 85 | 86 | .. code-block:: text 87 | 88 | 201 Created 89 | 90 | Content-Type: application/json 91 | Location: http://api.example.com/movies/78903 92 | 93 | { 94 | "id": 78903, 95 | "title": "Ariel", 96 | "director": "Aki Kaurismäki", 97 | "year": 1988, 98 | "duration": 73 99 | } 100 | 101 | Pojďme si opět popsat jednotlivé součásti. 102 | 103 | status kód (*status code*) 104 | Číselný kód, kterým API dává najevo, jak požadavek zpracovalo. Někdy se s ním objevuje i tzv. *reason phrase*, která kód vysvětluje slovy. Každý kód má zpravidla svou přesně danou *reason phrase*, takže ta neposkytuje žádnou informaci navíc, ale kódy se s ní lépe čtou. Protokol HTTP `přesně určuje všechny kódy `__, co znamenají, a kdy se mají použít. Podle první číslice kódu se kódy dělí na různé kategorie: 105 | 106 | - 1xx - informativní odpověď (požadavek byl přijat, ale jeho zpracování pokračuje) 107 | - 2xx - požadavek byl v pořádku přijat a zpracován 108 | - 3xx - přesměrování, klient potřebuje poslat další požadavek jinam, aby se dobral odpovědi 109 | - 4xx - chyba na straně klienta (špatně jsme poskládali dotaz) 110 | - 5xx - chyba na straně serveru (API nezvládlo odpovědět) 111 | 112 | Příklady kódů i s jejich *reason phrases*: ``404 Not Found``, ``200 OK``, ``500 Internal Server Error`` 113 | 114 | .. note:: 115 | Nejlepší způsob, jak si zapamatovat status kódy je projít si `HTTP Status Cats `__. 116 | 117 | hlavičky (*headers*) 118 | Totéž jako u :ref:`požadavku `. 119 | 120 | Příklady: 121 | 122 | - ``Date: Thu, 25 Oct 2018 09:29:48 GMT`` 123 | - ``Content-Type: text/plain`` 124 | 125 | tělo (*body*) 126 | Totéž jako u :ref:`požadavku `. 127 | 128 | Příklady: ``Ahoj!``, ``{"title": "Ariel"}`` 129 | 130 | .. _curl-lowercase-i: 131 | 132 | Posílat základní požadavky přes prohlížeč nebo curl už umíme. Z odpovědí nám ale bylo v obou případech zobrazeno jen tělo. Pokud bychom se chtěli s programem curl podívat i na ostatní části odpovědi, můžeme to udělat pomocí přepínače ``-i``: 133 | 134 | .. code-block:: text 135 | :emphasize-lines: 3-10 136 | 137 | $ curl -i "http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt" 138 | HTTP/1.1 200 OK 139 | Date: Fri, 02 Nov 2018 18:40:42 GMT 140 | Server: Apache-Coyote/1.1 141 | Last-Modified: Fri, 02 Nov 2018 18:40:00 GMT 142 | Expires: Fri, 02 Nov 2018 18:45:00 GMT 143 | Cache-Control: max-age=86400 144 | Content-Type: text/plain;charset=UTF-8 145 | Content-Length: 976 146 | X-FRAME-OPTIONS: SAMEORIGIN 147 | 148 | 02.11.2018 #212 149 | země|měna|množství|kód|kurz 150 | Austrálie|dolar|1|AUD|16,273 151 | Brazílie|real|1|BRL|6,109 152 | Bulharsko|lev|1|BGN|13,183 153 | ... 154 | 155 | Jak jde vidět, hned za verzí protokolu (HTTP/1.1) nám curl vypíše status kód a jeho slovní označení (200 OK), díky kterým víme, že se vše povedlo. Následují všelijaké hlavičky a po nich, odděleno novým řádkem, pokračuje tělo odpovědi, které už známe. 156 | 157 | 158 | HTTPS 159 | ~~~~~ 160 | 161 | Požadavek i odpověď se po internetu posílají jako obyčejný text, takže by se v nich nemělo posílat nic tajného. 162 | 163 | Ve skutečnosti ale prakticky vždy potřebujeme poslat něco tajného, ať už jsou to soukromá data uživatelů, nebo přímo nějaké heslo. Toto se řeší tak, že se textové HTTP zprávy obalí do nějaké bezpečné šifry, která funguje jako "neprůhledný obal". 164 | 165 | Kurzy ČNB jsou sice veřejná informace, ale zase chceme mít jistotu, že je publikovala opravdu ČNB, že je nikdo nepodvrhl. ČNB nám bohužel tuto jistotu neposkytuje. Vidíme, že šifrujeme a nemusíme se tedy bát posílat hesla, ale nevíme komu je posíláme: 166 | 167 | .. image:: ../_static/images/https-cnb.png 168 | :alt: HTTPS a ČNB 169 | :align: center 170 | 171 | Když jdeme na nějakou jinou známější stránku, třeba `GitHub `__, prohlížeč nám v adresním řádku zvýrazňuje, že stránky patří firmě *GitHub, Inc*. To už působí lépe! Je to možné díky ověřování přes tzv. certifikáty. 172 | 173 | .. image:: ../_static/images/https-github.png 174 | :alt: HTTPS a GitHub 175 | :align: center 176 | 177 | Šifry, certifikáty, a další bezpečnostní opatření byly do HTTP dodány dodatečně. Souhrnně se označují jako HTTPS (*S* jako *secure*). Vždy bychom se měli snažit, ať už v prohlížeči nebo při práci s API, aby naše adresy začínaly ``https://``. Jen tak zaručíme alespoň minimální bezpečnost toho, co děláme. Zároveň **nikdy** nesmíme posílat žádná hesla přes prosté HTTP! 178 | 179 | Možná snad jen pokud bychom chtěli `v televizi říct, že jsme nikdy nešifrovali `__. 180 | 181 | 182 | .. _formaty: 183 | 184 | Formáty 185 | ------- 186 | 187 | Požadavek i odpověď mohou obsahovat tělo. Toto tělo může být v libovolném formátu. Může to být text, HTML, obrázek, PDF soubor, nebo cokoliv jiného. Aby druhá strana věděla, co v těle zprávy posíláme, měli bychom jí dát formát vědět v hlavičce :header:`Content-Type`. 188 | 189 | MIME 190 | ~~~~ 191 | 192 | Hodnotě hlavičky :header:`Content-Type` se dávají různé názvy: *content type*, *media type*, *MIME type*. Nejčastěji se skládá jen z typu a podtypu, které se oddělí lomítkem (celá specifikace je k dispozici na `MND web docs `__). Několik příkladů: 193 | 194 | - ``text/plain`` - obyčejný text 195 | - ``text/html`` - HTML 196 | - ``text/csv`` - `CSV `__ 197 | - ``image/gif`` - GIF obrázek 198 | - ``image/jpeg`` - JPEG obrázek 199 | - ``image/png`` - PNG obrázek 200 | - ``application/json`` - :ref:`JSON` 201 | - ``application/xml`` nebo ``text/xml`` - :ref:`XML` 202 | 203 | Na hlavičku se můžeme snadno podívat s pomocí ``curl -i``, které :ref:`už známe `, ale to nám bude vracet i tělo odpovědi a to nás teď příliš nezajímá. Pokud místo ``-i`` použijeme ``-I``, uvidíme pouze hlavičky: 204 | 205 | .. code-block:: text 206 | :emphasize-lines: 8 207 | 208 | $ curl -I "http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt" 209 | HTTP/1.1 200 OK 210 | Date: Fri, 09 Nov 2018 15:31:46 GMT 211 | Server: Apache-Coyote/1.1 212 | Last-Modified: Fri, 09 Nov 2018 15:30:00 GMT 213 | Expires: Fri, 09 Nov 2018 15:35:00 GMT 214 | Cache-Control: max-age=86400 215 | Content-Type: text/plain;charset=UTF-8 216 | Content-Length: 976 217 | X-FRAME-OPTIONS: SAMEORIGIN 218 | 219 | Vidíme, že :ref:`API ČNB ` vrací obyčejný text, tedy ``text/plain`` (přilepeného ``;charset=UTF-8`` si teď nebudeme všímat). 220 | 221 | .. tabs:: 222 | 223 | .. tab:: Cvičení 224 | 225 | Jaký ``Content-Type`` má tělo odpovědí z následujících adres? 226 | 227 | #. ``https://www.gravatar.com/avatar/7b2e4bf7ecca28e530e1c421f0676c0b?s=120`` 228 | #. ``https://feeds.feedburner.com/respekt-clanky`` 229 | #. ``https://www.omdbapi.com/?t=westworld`` 230 | #. ``https://duckduckgo.com/`` 231 | 232 | .. tab:: Řešení 233 | 234 | Postupně spouštíme ``curl -I`` (nebo ``curl -i``) pro jednotlivé adresy a ve vypsaných hlavičkách hledáme hodnotu pro :header:`Content-Type`. Měli bychom dostat zhruba následující: 235 | 236 | #. JPEG - ``image/jpeg`` 237 | #. :ref:`XML` - ``text/xml; charset=UTF-8`` 238 | #. :ref:`JSON` - ``application/json; charset=utf-8`` 239 | #. HTML - ``text/html; charset=UTF-8`` 240 | 241 | 242 | .. _struktura: 243 | 244 | Struktura a strojová čitelnost 245 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 246 | 247 | Tělo HTTP zprávy může být v jakémkoliv formátu, ale jak jsme si :ref:`vysvětlili v úvodu `, smyslem API je, aby se jím propojené systémy obešly bez člověka. Potřebujeme tedy strojovou čitelnost. Řekněme, že budeme chtít v API poslat seznam adres. Následující řádky budou sice jasné nám, lidem, ale program si s nimi poradí jen těžko: 248 | 249 | .. code-block:: text 250 | 251 | Roadway Cafe & Beer, 144/15B Phan Văn Hân, Phường 17, Bình Thạnh, Hồ Chí Minh, Vietnam 252 | Kuma Sushi + Sake, 1040 Polk St, San Francisco, CA 94109, USA 253 | Madam Podprsenka, Hybešova 437/46, 602 00 Brno-střed, Česká republika 254 | 255 | Potřebujeme těmto datům dát nějakou strukturu. Třeba takto: 256 | 257 | .. code-block:: text 258 | 259 | Název: Roadway Cafe & Beer 260 | Ulice: 144/15B Phan Văn Hân 261 | Část: Phường 17, Bình Thạnh 262 | Město: Hồ Chí Minh 263 | Země: Vietnam 264 | 265 | Název: Kuma Sushi + Sake 266 | Ulice: 1040 Polk St 267 | Město: San Francisco 268 | Stát: CA 269 | ZIP/PSČ: 94109 270 | Země: USA 271 | 272 | Název: Madam Podprsenka 273 | Ulice: Hybešova 437/46 274 | Část: Brno-střed 275 | Město: Brno 276 | ZIP/PSČ: 60200 277 | Země: Česká republika 278 | 279 | V tomto okamžiku už by program mohl mít nějakou představu o tom, co mu posíláme a jak to má přečíst. Jenže aby to přečetl, musí jít řádek po řádku a nějak zpracovat tento náš formát, který jsme si právě vymysleli. Musí vědět, že jednotlivé adresy jsou oddělené více novými řádky, že názvy položek jsou odděleny dvojtečkou, atd. 280 | 281 | Protože by to bylo pro obě strany dost pracné, existují formáty, které slouží k přenosu obecných strukturovaných dat. Většina programovacích jazyků s nimi navíc umí pracovat bez velkých potíží. Například :ref:`JSON`: 282 | 283 | .. code-block:: json 284 | 285 | [ 286 | { 287 | "name": "Roadway Cafe & Beer", 288 | "street": "144/15B Phan Văn Hân", 289 | "district": "Phường 17, Bình Thạnh", 290 | "city": "Hồ Chí Minh", 291 | "country": "Vietnam" 292 | }, 293 | { 294 | "name": "Kuma Sushi + Sake", 295 | "street": "1040 Polk St", 296 | "city": "San Francisco", 297 | "state": "CA", 298 | "zip": 94109, 299 | "country": "USA" 300 | }, 301 | { 302 | "name": "Madam Podprsenka", 303 | "street": "Hybešova 437/46", 304 | "district": "Brno-střed", 305 | "city": "Brno", 306 | "zip": 60200, 307 | "country": "Česká republika" 308 | } 309 | ] 310 | 311 | Názvy položek jsme přeložili do angličtiny ne proto, že bychom museli, ale proto že je to zažitá konvence a zjednodušuje to přenositelnost dat mezi programy. Nyní může jakýkoliv program taková data snadno přečíst. Pojďme si to hned zkusit! 312 | 313 | Uložíme tento JSON na disk a zkusíme jej zpracovat v jazyce Python: 314 | 315 | #. Uložte JSON z příkladu do souboru ``places.json`` 316 | #. V tomtéž adresáři vytvořte program ``places.py``: 317 | 318 | .. code-block:: python 319 | 320 | import json 321 | 322 | with open('places.json', encoding='utf-8-sig') as f: 323 | places = json.load(f) 324 | 325 | for place in places: 326 | print('{name} ({country})'.format_map(place)) 327 | 328 | #. Spusťte program: 329 | 330 | .. code-block:: text 331 | 332 | $ python places.py 333 | Roadway Cafe & Beer (Vietnam) 334 | Kuma Sushi + Sake (USA) 335 | Madam Podprsenka (Česká republika) 336 | 337 | .. note:: 338 | Může se stát, že místo seznamu míst vidíte chybu 339 | 340 | .. code-block:: text 341 | 342 | TypeError: 'encoding' is an invalid keyword argument for this function 343 | 344 | Je to proto, že jste program spustili pomocí Pythonu verze 2 místo Pythonu 3. 345 | 346 | Na několika řádcích jsme byli v Pythonu schopni JSON soubor načíst a s daty v něm dále pracovat. 347 | 348 | .. note:: 349 | Koho právě napadlo, že :ref:`API ČNB ` vypadá jako adresy v druhém příkladu, dostává bludišťáka! Je to tak. Bylo by lepší, kdyby ČNB kurzy posílala jako :ref:`JSON` a ne jen jako strukturovaný text. 350 | 351 | .. _json: 352 | 353 | JSON 354 | ~~~~ 355 | 356 | Příklad dat ve formátu JSON už jsme si ukázali :ref:`výše `. JSON vznikl kolem roku 2000 a brzy se uchytil jako stručnější náhrada za :ref:`XML`, především na webu a ve webových API. Dnes je to nejspíš nejoblíbenější formát pro obecná strukturovaná data vůbec. Jeho autorem je `Douglas Crockford `__, jeden z lidí podílejících se na vývoji jazyka JavaScript. 357 | 358 | Jazyk Python (a mnoho dalších) má podporu pro práci s JSON `přímo zabudovanou `__. JSON je navržený tak, aby připomínal objekt jazyka JavaScript. Začátečníkům se pak snadno stane, že neví, co je co. 359 | 360 | V případě jazyka Python si lze JSON splést především se `slovníkem `__. Je ale potřeba si uvědomit, že **JSON je text**, který může být uložený do souboru nebo odeslaný přes HTTP, ale nelze jej přímo použít při programování. Musíme jej vždy nejdříve zpracovat na slovníky a seznamy: 361 | 362 | .. code-block:: python 363 | 364 | >>> address_json = '{"name": "Roadway Cafe & Beer", "country": "Vietnam"}' 365 | >>> address_json 366 | '{"name": "Roadway Cafe & Beer", "country": "Vietnam"}' 367 | >>> type(address_json) 368 | 369 | 370 | >>> import json 371 | >>> address_dict = json.loads(address_json) 372 | >>> address_dict 373 | {'name': 'Roadway Cafe & Beer', 'country': 'Vietnam'} 374 | >>> type(address_dict) 375 | 376 | 377 | Naopak slovníky a seznamy se hodí při programování, ale zase je nemůžeme jen tak uložit do souboru nebo odeslat přes HTTP. Je potřeba je nejdříve *serializovat* do nějakého textového formátu - což může být zrovna JSON: 378 | 379 | .. code-block:: python 380 | 381 | >>> address_dict = {'name': 'Roadway Cafe & Beer', 'country': 'Vietnam'} 382 | 383 | >>> import json 384 | >>> address_json = json.dumps(address_dict) 385 | >>> address_json 386 | '{"name": "Roadway Cafe & Beer", "country": "Vietnam"}' 387 | >>> type(address_json) 388 | 389 | 390 | 391 | .. _xml: 392 | 393 | XML 394 | ~~~ 395 | 396 | XML vzniklo kolem roku 1997. Mnoho lidí nad ním dnes ohrnuje nos, protože si jej spojuje s velkými korporacemi, jazykem Java, :ref:`SOAP`, apod., ale v době vzniku bylo XML přelomovou technologií a dodnes se na spoustě míst využívá ke spokojenosti všech zúčastněných. Je například základem pro populární formáty jako jsou `GPX `__, `KML `__, `SVG `__, `DocBook `__, a další. Zajímavostí také je, že jedna z nejvýznamnějších XML konferencí na světě se každoročně koná u nás: `XML Prague `__ 397 | 398 | XML vypadá podobně jako HTML, ale je obecnější a přísnější. Na rozdíl od HTML, které přímo popisuje význam značek (např. že ``

Ahoj!

`` má prohlížeč interpretovat jako odstavec s textem ``Ahoj!``), XML určuje pouze obecná pravidla o tvaru značek samotných a zbytek je plně na nás. :ref:`Náš příklad ` s adresami by v XML mohl vypadat třeba takto: 399 | 400 | .. code-block:: xml 401 | 402 | 403 | 404 | Roadway Cafe & Beer 405 | 144/15B Phan Văn Hân 406 | Phường 17, Bình Thạnh 407 | Hồ Chí Minh 408 | Vietnam 409 | 410 | 411 | Kuma Sushi + Sake 412 | 1040 Polk St 413 | San Francisco 414 | CA 415 | 94109 416 | USA 417 | 418 | 419 | Madam Podprsenka 420 | Hybešova 437/46 421 | Brno-střed 422 | Brno 423 | 60200 424 | Česká republika 425 | 426 | 427 | 428 | Jazyk Python (a mnoho dalších) má podporu pro práci s XML `přímo zabudovanou `__. 429 | 430 | Typy API 431 | -------- 432 | 433 | Přes HTTP je možné poslat prakticky cokoliv. Zatímco některá API se snaží maximálně využít jeho možností a vlastností, jiná jej využívají pouze jako "dopravní prostředek". Podle přístupu dělíme API na následující typy. 434 | 435 | 436 | .. _soap: 437 | 438 | SOAP 439 | ~~~~ 440 | 441 | Tato API si přes HTTP posílají zprávy zabalené do přesně specifikovaného, ale velmi nepřehledného chuchvalce :ref:`XML` obálek. SOAP se používá hlavně ve světě velkých aplikací napsaných v jazyce Java, a to především v prostředí korporací a velkých institucí. SOAP API se objevila kolem roku 1998 a byla v módě během první dekády 21. století. Blízkými příbuznými SOAP jsou API typu RPC (XML-RPC, JSON-RPC). 442 | 443 | 444 | Čtení pro pokročilé 445 | ^^^^^^^^^^^^^^^^^^^ 446 | 447 | - `Phil Sturgeon: Understanding RPC Vs REST For HTTP APIs `__ 448 | - `Leonard Richardson, Sam Ruby: RESTful Web Services `__ 449 | 450 | 451 | .. _rest: 452 | 453 | REST 454 | ~~~~ 455 | 456 | API, která lidé označují jako REST, jsou dnes nejběžnější. Snaží se co nejvíce spolehnout na schopnosti a vlastnosti samotného HTTP. Na rozdíl od :ref:`SOAP` nebo :ref:`GraphQL` není REST přesně specifikovaný, je to pouze "styl" jak API dělat. Je to jako styly v architektuře budov - stavitelé gotických kostelů neměli přesně přikázáno, jak má co vypadat, ale přesto dnes poznáme, který kostel je gotický a který barokní. REST API dnes používají především :ref:`JSON`, ale je to pouze zvyklost, nebo možná také jen náhoda (REST i JSON začaly být populární ve stejné době). 457 | 458 | 459 | Historie 460 | ^^^^^^^^ 461 | 462 | REST se poprvé objevil v roce 2000 v dizertační práci `R. Fieldinga `__ (spoluautor HTTP). Ten pozoroval jak funguje web, a snažil se přijít na to, co jej dělá tak úspěšným. Jaké má web zásadní vlastnosti a omezení, a zda jsou za tím nějaké obecné principy, které by šlo využít i jinde. Tyto principy pak popsal a přisoudil jim zkratku REST. 463 | 464 | Na REST principech se začala stavět API a ta pak ze scény vytlačovala SOAP. Jenže ze zkratky REST se stal buzzword a lidé jí začali označovat vše, co používalo HTTP a nebylo to SOAP. Samozřejmě bez ohledu na původní principy. 465 | 466 | Zastánci původních principů se nevzdávali a zkoušeli postupně prorazit s několika termíny, které měly odlišit pravověrnost: RESTful, HATEOAS, hypermedia. Dodnes je ale takovýchto pravověrných API málo. Rozjetý vlak s tím, jak si lidé REST vyložili, už se nepovedlo zastavit. 467 | 468 | 469 | Čtení pro pokročilé 470 | ^^^^^^^^^^^^^^^^^^^ 471 | 472 | - `Leonard Richardson, Sam Ruby, Mike Amundsen: RESTful Web APIs `__ 473 | 474 | 475 | .. _graphql: 476 | 477 | GraphQL 478 | ~~~~~~~ 479 | 480 | `GraphQL `__ je nejnovějším typem API a momentálně i nejžhavějším buzzwordem konferencí. Má přesně danou specifikaci a HTTP používá jenom jako "dopravní prostředek", podobně jako dříve SOAP nebo RPC. Jeho největšími fanoušky jsou vývojáři klientů, a to především v jazyce JavaScript. GraphQL nejvíce připomíná dotazovací jazyk pro databáze a je pevně spjato s formátem :ref:`JSON`. 481 | 482 | 483 | Historie 484 | ^^^^^^^^ 485 | 486 | Dá se říci, že lidé se utopili ve volnosti výkladu co je REST a nedostatku striktních doporučení jak přesně mají REST API vytvářet. Návrh REST API vyžaduje detailní znalosti HTTP, kreativitu a smysl pro architekturu. Běžný vývojář ovšem není ochotný řádně nastudovat ani jak přesně funguje HTTP, natož přemýšlet nad architekturou - raději to "prostě nějak splácá". Výsledek je většinou frustrující, především pro vývojáře klientů (frontend, mobilní aplikace, apod.), kteří s API musí pracovat. 487 | 488 | GraphQL vymyslel `Facebook `__ pro svoje potřeby a v roce 2015 jej uveřejnil jako specifikaci, kterou může využít každý. Vývojáři klientů po GraphQL lačně chňapli jako po řešení všech jejich problémů. Podle obsahu technologických konferencí by se v roce 2018 zdálo, že REST už nikoho nezajímá. GraphQL každým rokem nabírá větší a větší momentum. 489 | 490 | Podle odborníků to ale nevypadá, že by GraphQL mělo REST nahradit. Spíše se zdá, že budou existovat společně a doplňovat se. Používat nebo vyvíjet GraphQL mimo JavaScript je navíc stále zatím dost obtížné kvůli chybějícím nástrojům a knihovnám, ale to se samozřejmě může časem změnit. 491 | 492 | 493 | Čtení pro pokročilé 494 | ^^^^^^^^^^^^^^^^^^^ 495 | 496 | - `Phil Sturgeon: GraphQL vs REST: Overview `__ 497 | - `Zdeněk Němec: REST vs. GraphQL: A Critical Review `__ 498 | 499 | 500 | Která API se učíme? 501 | ------------------- 502 | 503 | **Co je API?** je primárně o těch API, která se běžně označují jako :ref:`REST`. Vzhledem k historii a komplikovanosti termínu REST API se mu ale záměrně vyhýbám a raději tato API označuji jako webová. Přijde mi to tak i přesnější a srozumitelnější. 504 | 505 | V těchto materiálech se nebudeme zabývat :ref:`SOAP` ani RPC. Zatím zde není nic ani o :ref:`GraphQL`. Pokud vás to mrzí a o GraphQL něco víte, budu rád, když do materiálů :ref:`přispějete `. 506 | 507 | 508 | Shrnutí 509 | ------- 510 | 511 | Na následujícím obrázku je shrnutí toho, co jsme se v této části návodu naučili. 512 | 513 | .. image:: ../_static/images/http.png 514 | :alt: Klient-server a HTTP 515 | :align: center 516 | --------------------------------------------------------------------------------