├── .gitignore ├── 1_D_jak_deploy ├── D_jak_Deploy.ipynb └── web_app │ ├── Procfile │ ├── main.py │ ├── requirements.txt │ ├── runtime.txt │ └── test_main.py ├── 2_A_jak_Art └── 2_A_jak_Art.ipynb ├── 3_F_jak_Fast ├── F_jak_Fast.ipynb └── app.py ├── 4_T_jak_Tabela ├── Northwind.png ├── PracaDomowa.ipynb ├── README.md ├── T_jak_Tabela.ipynb ├── app.py ├── app │ ├── Procfile │ ├── app │ │ ├── __init__.py │ │ ├── crud.py │ │ ├── database.py │ │ ├── main.py │ │ ├── models.py │ │ ├── models2.py │ │ ├── schemas.py │ │ └── views.py │ ├── pyproject.toml │ ├── requirements.txt │ ├── runtime.txt │ └── setup.cfg ├── diagram.png ├── docker-compose.yml ├── dumps │ └── northwind.sql.dump ├── migrations │ ├── 2021-05-06-northwind.postgre.sql │ ├── 2021-05-07-change-bpchar-to-character.sql │ └── 2021-05-10-add_sequences.sql ├── northwind.db └── praca_domowa │ ├── example_app.py │ ├── zadanie_4_1_test_1.py │ ├── zadanie_4_1_test_2.py │ ├── zadanie_4_2_test_1.py │ ├── zadanie_4_2_test_2.py │ ├── zadanie_4_2_test_3.py │ ├── zadanie_4_3_test_1.py │ ├── zadanie_4_3_test_2.py │ ├── zadanie_4_3_test_3.py │ ├── zadanie_4_3_test_4.py │ ├── zadanie_4_3_test_5.py │ ├── zadanie_4_3_test_6.py │ ├── zadanie_4_4_test.py │ ├── zadanie_4_5_test_1.py │ ├── zadanie_4_5_test_2.py │ ├── zadanie_4_5_test_3.py │ ├── zadanie_4_5_test_4.py │ ├── zadanie_4_6_test_1.py │ └── zadanie_4_6_test_2.py ├── 5_Asyncio └── A_jak_asyncio.ipynb └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | */.ipynb_checkpoints/* 2 | */__pycache__/* 3 | *.pyc 4 | *.back 5 | -------------------------------------------------------------------------------- /1_D_jak_deploy/D_jak_Deploy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# D jak Deploy\n", 12 | "\n", 13 | "\n", 14 | "### Marcin Jaroszewski\n", 15 | "### Python level up 2022\n", 16 | "### 10.05.2022" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": { 22 | "slideshow": { 23 | "slide_type": "slide" 24 | } 25 | }, 26 | "source": [ 27 | "# I. Cel dzisiejszego spotkania" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": { 33 | "slideshow": { 34 | "slide_type": "fragment" 35 | } 36 | }, 37 | "source": [ 38 | "Napisanie prostej aplikacji webowej w python i \"wrzucenie jej do internetu\". " 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": { 44 | "slideshow": { 45 | "slide_type": "slide" 46 | } 47 | }, 48 | "source": [ 49 | "# II. Trochę teorii" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": { 55 | "slideshow": { 56 | "slide_type": "subslide" 57 | } 58 | }, 59 | "source": [ 60 | "## Aplikacja webowa" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": { 66 | "slideshow": { 67 | "slide_type": "fragment" 68 | } 69 | }, 70 | "source": [ 71 | "- Aplikacja działająca w modelu klient-serwer;\n", 72 | "- Klient: najczęściej przeglądarka (ale może to być też np. inna aplikacja);\n", 73 | "- Serwer: serwer aplikacji webowej, który nasłuchuje na połączenia na wybranych portach (zazwyczaj 80 lub 443);\n", 74 | "- Klient z serwerem komunikują się za pomocą protokołu HTTP." 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": { 80 | "slideshow": { 81 | "slide_type": "subslide" 82 | } 83 | }, 84 | "source": [ 85 | "## Protokół HTTP" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": { 91 | "slideshow": { 92 | "slide_type": "fragment" 93 | } 94 | }, 95 | "source": [ 96 | "- Połączenie inicjowane przez klienta;\n", 97 | "- Bezstanowe;\n", 98 | "- Korzysta z protokołu TCP." 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": { 104 | "slideshow": { 105 | "slide_type": "subslide" 106 | } 107 | }, 108 | "source": [ 109 | "## Request HTTP" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": { 115 | "slideshow": { 116 | "slide_type": "fragment" 117 | } 118 | }, 119 | "source": [ 120 | "```\n", 121 | "METODA URL WERSJA_HTTP\n", 122 | "<0 lub więcej> NAGŁÓWKI \n", 123 | "\n", 124 | " TREŚĆ_ZAPYTANIA\n", 125 | "```" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": { 131 | "slideshow": { 132 | "slide_type": "subslide" 133 | } 134 | }, 135 | "source": [ 136 | "## (Najważniejsze) Metody HTTP" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": { 142 | "slideshow": { 143 | "slide_type": "fragment" 144 | } 145 | }, 146 | "source": [ 147 | "Źródło: https://tools.ietf.org/html/rfc7231\n", 148 | "\n", 149 | "- **GET** - pobranie zasobu;\n", 150 | "- **POST** - przesyłanie danych od klienta do serwera, najczęściej służy do stworzenia zasobu;\n", 151 | "- DELETE - żądanie usunięcia zasobu;\n", 152 | "- PUT - przesyłanie danych od klienta do serwera, najczęściej służy do zastąpienia w całości istniejącego zasobu;\n", 153 | "- PATCH - przesyłanie danych od klienta do serwera, najczęściej służy do częściowej aktualizacji istniejącego zasobu.\n" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": { 159 | "slideshow": { 160 | "slide_type": "subslide" 161 | } 162 | }, 163 | "source": [ 164 | "## Pozostałe metody HTTP" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": { 170 | "slideshow": { 171 | "slide_type": "fragment" 172 | } 173 | }, 174 | "source": [ 175 | "- HEAD - pobranie nagłówków dla danego zasobu\n", 176 | "- OPTIONS - żądanie informacji o metodach dostępnych dla danego zasobu\n", 177 | "- CONNECT - wykorzystane w trakcie tunelowania z użyciem serwerów pośredniczących\n", 178 | "- TRACE - służy do diagnostyki\n", 179 | "\n", 180 | "Ale to nie wszystko: http://www.iana.org/assignments/http-methods/http-methods.xhtml" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": { 186 | "slideshow": { 187 | "slide_type": "subslide" 188 | } 189 | }, 190 | "source": [ 191 | "## URL" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": { 197 | "slideshow": { 198 | "slide_type": "fragment" 199 | } 200 | }, 201 | "source": [ 202 | "Lokalizacja zasobu, w formacie:\n", 203 | "\n", 204 | "```\n", 205 | "scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]\n", 206 | "```\n", 207 | "Przykład\n", 208 | "\n", 209 | "```\n", 210 | "pełen adres: https://pl.wikipedia.org/wiki/Hypertext_Transfer_Protocol\n", 211 | "adres serwera: https://pl.wikipedia.org\n", 212 | "ścieżka do zasobu: /wiki/Hypertext_Transfer_Protocol\n", 213 | "```\n", 214 | "https://en.wikipedia.org/wiki/URL" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": { 220 | "slideshow": { 221 | "slide_type": "subslide" 222 | } 223 | }, 224 | "source": [ 225 | "## Wersja HTTP" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": { 231 | "slideshow": { 232 | "slide_type": "fragment" 233 | } 234 | }, 235 | "source": [ 236 | "- HTTP/0.9 (rok 1991)\n", 237 | "- HTTP/1.0 (rok 1996)\n", 238 | "- HTTP/1.1 (rok 1997)\n", 239 | "- HTTP/2.0 (rok 2015) https://tools.ietf.org/html/rfc7540\n", 240 | "- HTTP/3.0 (rok 2022 - draft) https://quicwg.org/base-drafts/draft-ietf-quic-http.html" 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": { 246 | "slideshow": { 247 | "slide_type": "subslide" 248 | } 249 | }, 250 | "source": [ 251 | "## Nagłówki HTTP" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": { 257 | "slideshow": { 258 | "slide_type": "fragment" 259 | } 260 | }, 261 | "source": [ 262 | "- Host\n", 263 | "- User-Agent\n", 264 | "- Content-Type\n", 265 | "- Cookie\n", 266 | "\n", 267 | "więcej: https://pl.wikipedia.org/wiki/Lista_nag%C5%82%C3%B3wk%C3%B3w_HTTP" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": { 273 | "slideshow": { 274 | "slide_type": "subslide" 275 | } 276 | }, 277 | "source": [ 278 | "## Przykładowe requesty HTTP" 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": { 284 | "slideshow": { 285 | "slide_type": "fragment" 286 | } 287 | }, 288 | "source": [ 289 | "GET:\n", 290 | "```\n", 291 | "GET /hello.htm HTTP/1.1\n", 292 | "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)\n", 293 | "Host: www.tutorialspoint.com\n", 294 | "Accept-Language: en-us\n", 295 | "Accept-Encoding: gzip, deflate\n", 296 | "Connection: Keep-Alive\n", 297 | "```\n", 298 | "POST:\n", 299 | "```\n", 300 | "POST /cgi-bin/process.cgi HTTP/1.1\n", 301 | "User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)\n", 302 | "Host: www.tutorialspoint.com\n", 303 | "Content-Type: application/x-www-form-urlencoded\n", 304 | "Content-Length: 49\n", 305 | "Accept-Language: en-us\n", 306 | "Accept-Encoding: gzip, deflate\n", 307 | "Connection: Keep-Alive\n", 308 | "\n", 309 | "licenseID=string&content=string&/paramsXML=string\n", 310 | "```" 311 | ] 312 | }, 313 | { 314 | "cell_type": "markdown", 315 | "metadata": { 316 | "slideshow": { 317 | "slide_type": "subslide" 318 | } 319 | }, 320 | "source": [ 321 | "## Response HTTP" 322 | ] 323 | }, 324 | { 325 | "cell_type": "markdown", 326 | "metadata": { 327 | "slideshow": { 328 | "slide_type": "fragment" 329 | } 330 | }, 331 | "source": [ 332 | "```\n", 333 | "WERSJA_HTTP STATUS_HTTP OPIS_STATUSU\n", 334 | "<0 lub więcej> NAGŁÓWKI \n", 335 | "\n", 336 | " TREŚĆ_ODPOWIEDZI\n", 337 | "```" 338 | ] 339 | }, 340 | { 341 | "cell_type": "markdown", 342 | "metadata": { 343 | "slideshow": { 344 | "slide_type": "slide" 345 | } 346 | }, 347 | "source": [ 348 | "### Grupy statusów HTTP z przykładami" 349 | ] 350 | }, 351 | { 352 | "cell_type": "markdown", 353 | "metadata": { 354 | "slideshow": { 355 | "slide_type": "subslide" 356 | } 357 | }, 358 | "source": [ 359 | "### 1xx - informacyjne" 360 | ] 361 | }, 362 | { 363 | "cell_type": "markdown", 364 | "metadata": { 365 | "slideshow": { 366 | "slide_type": "subslide" 367 | } 368 | }, 369 | "source": [ 370 | "![Continue](https://http.cat/100)" 371 | ] 372 | }, 373 | { 374 | "cell_type": "markdown", 375 | "metadata": { 376 | "slideshow": { 377 | "slide_type": "subslide" 378 | } 379 | }, 380 | "source": [ 381 | "### 2xx - sukces" 382 | ] 383 | }, 384 | { 385 | "cell_type": "markdown", 386 | "metadata": { 387 | "slideshow": { 388 | "slide_type": "subslide" 389 | } 390 | }, 391 | "source": [ 392 | "![OK](https://httpstatusdogs.com/img/200.jpg)" 393 | ] 394 | }, 395 | { 396 | "cell_type": "markdown", 397 | "metadata": { 398 | "slideshow": { 399 | "slide_type": "subslide" 400 | } 401 | }, 402 | "source": [ 403 | "### 3xx - przekierowania" 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "metadata": { 409 | "slideshow": { 410 | "slide_type": "subslide" 411 | } 412 | }, 413 | "source": [ 414 | "![Moved Permanently](https://http.cat/301)" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": { 420 | "slideshow": { 421 | "slide_type": "subslide" 422 | } 423 | }, 424 | "source": [ 425 | "![Found](https://httpstatusdogs.com/img/302.jpg)" 426 | ] 427 | }, 428 | { 429 | "cell_type": "markdown", 430 | "metadata": { 431 | "slideshow": { 432 | "slide_type": "subslide" 433 | } 434 | }, 435 | "source": [ 436 | "### 4xx - błąd klienta" 437 | ] 438 | }, 439 | { 440 | "cell_type": "markdown", 441 | "metadata": { 442 | "slideshow": { 443 | "slide_type": "subslide" 444 | } 445 | }, 446 | "source": [ 447 | "![Bad request](https://http.cat/400)" 448 | ] 449 | }, 450 | { 451 | "cell_type": "markdown", 452 | "metadata": { 453 | "slideshow": { 454 | "slide_type": "subslide" 455 | } 456 | }, 457 | "source": [ 458 | "![Unauthorized](https://http.cat/401)" 459 | ] 460 | }, 461 | { 462 | "cell_type": "markdown", 463 | "metadata": { 464 | "slideshow": { 465 | "slide_type": "subslide" 466 | } 467 | }, 468 | "source": [ 469 | "![Not found](https://http.cat/404)" 470 | ] 471 | }, 472 | { 473 | "cell_type": "markdown", 474 | "metadata": { 475 | "slideshow": { 476 | "slide_type": "subslide" 477 | } 478 | }, 479 | "source": [ 480 | "### 5xx - Błąd serwera" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": { 486 | "slideshow": { 487 | "slide_type": "subslide" 488 | } 489 | }, 490 | "source": [ 491 | "![Internal server error](https://http.cat/500)" 492 | ] 493 | }, 494 | { 495 | "cell_type": "markdown", 496 | "metadata": { 497 | "slideshow": { 498 | "slide_type": "subslide" 499 | } 500 | }, 501 | "source": [ 502 | "### Czasem heheszki zajdą za daleko" 503 | ] 504 | }, 505 | { 506 | "cell_type": "markdown", 507 | "metadata": { 508 | "slideshow": { 509 | "slide_type": "subslide" 510 | } 511 | }, 512 | "source": [ 513 | "![Teapot](https://http.cat/418)" 514 | ] 515 | }, 516 | { 517 | "cell_type": "markdown", 518 | "metadata": { 519 | "slideshow": { 520 | "slide_type": "subslide" 521 | } 522 | }, 523 | "source": [ 524 | "Obrazki wzięte z:\n", 525 | "- https://httpstatusdogs.com/\n", 526 | "- https://www.flickr.com/photos/girliemac/sets/72157628409467125\n", 527 | "\n", 528 | "\n", 529 | "Jest ich tam więcej :)" 530 | ] 531 | }, 532 | { 533 | "cell_type": "markdown", 534 | "metadata": { 535 | "slideshow": { 536 | "slide_type": "subslide" 537 | } 538 | }, 539 | "source": [ 540 | "### Przykładowe odpowiedzi HTTP\n", 541 | "Sukces:\n", 542 | "```\n", 543 | "HTTP/1.1 200 OK\n", 544 | "Date: Mon, 27 Jul 2009 12:28:53 GMT\n", 545 | "Server: Apache/2.2.14 (Win32)\n", 546 | "Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT\n", 547 | "Content-Length: 88\n", 548 | "Content-Type: text/html\n", 549 | "Connection: Closed\n", 550 | "\n", 551 | "\n", 552 | "\n", 553 | "

Hello, World!

\n", 554 | "\n", 555 | "\n", 556 | "```\n", 557 | "\n", 558 | "Błąd:\n", 559 | "```\n", 560 | "HTTP/1.1 404 Not Found\n", 561 | "Date: Sun, 18 Oct 2012 10:36:20 GMT\n", 562 | "Server: Apache/2.2.14 (Win32)\n", 563 | "Content-Length: 230\n", 564 | "Connection: Closed\n", 565 | "Content-Type: text/html; charset=iso-8859-1\n", 566 | "```" 567 | ] 568 | }, 569 | { 570 | "cell_type": "markdown", 571 | "metadata": { 572 | "slideshow": { 573 | "slide_type": "subslide" 574 | } 575 | }, 576 | "source": [ 577 | "# HTTPS\n", 578 | "- szyfrowana wersja HTTP\n", 579 | "- szyfruje dane przy pomocy protokołu TLS\n", 580 | "- zapobiega przechwytywaniu i zmienianiu danych\n", 581 | "- wywołanie protokołu zaczyna się od `https://`\n", 582 | "- przeglądarki pokazują nam kłódki" 583 | ] 584 | }, 585 | { 586 | "cell_type": "markdown", 587 | "metadata": { 588 | "slideshow": { 589 | "slide_type": "slide" 590 | } 591 | }, 592 | "source": [ 593 | "# III. Czego będziemy używać" 594 | ] 595 | }, 596 | { 597 | "cell_type": "markdown", 598 | "metadata": { 599 | "slideshow": { 600 | "slide_type": "subslide" 601 | } 602 | }, 603 | "source": [ 604 | "## 1. Język programowania" 605 | ] 606 | }, 607 | { 608 | "cell_type": "markdown", 609 | "metadata": { 610 | "slideshow": { 611 | "slide_type": "fragment" 612 | } 613 | }, 614 | "source": [ 615 | "To jest kurs python więc będziemy używać najnowszej wersji python :)" 616 | ] 617 | }, 618 | { 619 | "cell_type": "markdown", 620 | "metadata": { 621 | "slideshow": { 622 | "slide_type": "subslide" 623 | } 624 | }, 625 | "source": [ 626 | "## 2. Framework webowy" 627 | ] 628 | }, 629 | { 630 | "cell_type": "markdown", 631 | "metadata": { 632 | "slideshow": { 633 | "slide_type": "subslide" 634 | } 635 | }, 636 | "source": [ 637 | "Jest wiele możliwych frameworków np:\n", 638 | "- Django\n", 639 | "- Flask\n", 640 | "- Pyramid\n", 641 | "- Tornado\n", 642 | "- Bottle\n", 643 | "- Starlette\n", 644 | "- Fastapi\n" 645 | ] 646 | }, 647 | { 648 | "cell_type": "markdown", 649 | "metadata": { 650 | "slideshow": { 651 | "slide_type": "subslide" 652 | } 653 | }, 654 | "source": [ 655 | "Do tego kursu wybrałem Fastapi, ponieważ:\n", 656 | "- jest w miarę nowe\n", 657 | "- korzysta z nowych rozwiązań w python\n", 658 | "- ma jasną dokumentację z przykładami\n", 659 | "- wydaje się proste w użyciu\n", 660 | "- nie jest tak rozbudowane jak Django" 661 | ] 662 | }, 663 | { 664 | "cell_type": "markdown", 665 | "metadata": { 666 | "slideshow": { 667 | "slide_type": "subslide" 668 | } 669 | }, 670 | "source": [ 671 | "## 3. Framework testowy" 672 | ] 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "metadata": { 677 | "slideshow": { 678 | "slide_type": "fragment" 679 | } 680 | }, 681 | "source": [ 682 | "Tu nie ma wielkiego wyboru - fastapi zaleca pytest. Nie ma potrzeby wyważać otwartych drzwi.\n", 683 | "Poza tym pytest to naprawdę dobry framework do testów, znacznie przyjemnijszy od wbudowanego w bibliotekę standardową." 684 | ] 685 | }, 686 | { 687 | "cell_type": "markdown", 688 | "metadata": { 689 | "slideshow": { 690 | "slide_type": "subslide" 691 | } 692 | }, 693 | "source": [ 694 | "## 4. Hosting" 695 | ] 696 | }, 697 | { 698 | "cell_type": "markdown", 699 | "metadata": { 700 | "slideshow": { 701 | "slide_type": "fragment" 702 | } 703 | }, 704 | "source": [ 705 | "Jest wiele darmowych hostingów, ale my przez większość zajęć będziemy używać heroku, z powodów:\n", 706 | "- jest niesamowicie łatwe na początku\n", 707 | "- przy niskim użyciu jest z darmo" 708 | ] 709 | }, 710 | { 711 | "cell_type": "markdown", 712 | "metadata": { 713 | "slideshow": { 714 | "slide_type": "fragment" 715 | } 716 | }, 717 | "source": [ 718 | "Do prac domowych możecie używać dowolnych hostingów, jeśli z jakiś powodów heroku Wam się nie podoba. Ale macie ich używać poprawnie." 719 | ] 720 | }, 721 | { 722 | "cell_type": "markdown", 723 | "metadata": { 724 | "slideshow": { 725 | "slide_type": "subslide" 726 | } 727 | }, 728 | "source": [ 729 | "## 5. Repozytorium" 730 | ] 731 | }, 732 | { 733 | "cell_type": "markdown", 734 | "metadata": { 735 | "slideshow": { 736 | "slide_type": "fragment" 737 | } 738 | }, 739 | "source": [ 740 | "Jest wiele możliwośći nawet wśród darmowych. Ale heroku ma bardzo dobrą integrację z GitHub więc tego użyjemy." 741 | ] 742 | }, 743 | { 744 | "cell_type": "markdown", 745 | "metadata": { 746 | "slideshow": { 747 | "slide_type": "subslide" 748 | } 749 | }, 750 | "source": [ 751 | "## 6. Narzędzie do sprawdzania prac domowych." 752 | ] 753 | }, 754 | { 755 | "cell_type": "markdown", 756 | "metadata": { 757 | "slideshow": { 758 | "slide_type": "fragment" 759 | } 760 | }, 761 | "source": [ 762 | "Użwamy github classrooms. Po wykładzie Monika opowie jak się sprawa ma z pracami domowymi." 763 | ] 764 | }, 765 | { 766 | "cell_type": "markdown", 767 | "metadata": { 768 | "slideshow": { 769 | "slide_type": "subslide" 770 | } 771 | }, 772 | "source": [ 773 | "## 7. Podsumowanie " 774 | ] 775 | }, 776 | { 777 | "cell_type": "markdown", 778 | "metadata": { 779 | "slideshow": { 780 | "slide_type": "fragment" 781 | } 782 | }, 783 | "source": [ 784 | "1. Język programwania - Python3.10.3 https://www.python.org/downloads/release/python-3103/ (to jest najnowsza wersja wspierana przez heroku https://devcenter.heroku.com/articles/python-support#supported-runtimes)\n", 785 | "2. Framework webowy - Fastapi https://fastapi.tiangolo.com\n", 786 | "3. Framework testowy - pytest https://docs.pytest.org/en/latest\n", 787 | "4. Hosting - heroku https://heroku.com\n", 788 | "5. Repozytorium - GitHub https://github.com" 789 | ] 790 | }, 791 | { 792 | "cell_type": "markdown", 793 | "metadata": { 794 | "slideshow": { 795 | "slide_type": "slide" 796 | } 797 | }, 798 | "source": [ 799 | "# IV. Do dzieła" 800 | ] 801 | }, 802 | { 803 | "cell_type": "markdown", 804 | "metadata": { 805 | "slideshow": { 806 | "slide_type": "subslide" 807 | } 808 | }, 809 | "source": [ 810 | "## 1. Przygotowanie środowiska wirtualnego" 811 | ] 812 | }, 813 | { 814 | "cell_type": "markdown", 815 | "metadata": { 816 | "slideshow": { 817 | "slide_type": "fragment" 818 | } 819 | }, 820 | "source": [ 821 | "Ale, po co?" 822 | ] 823 | }, 824 | { 825 | "cell_type": "markdown", 826 | "metadata": { 827 | "slideshow": { 828 | "slide_type": "fragment" 829 | } 830 | }, 831 | "source": [ 832 | "Żeby, zależności jednego projektu nie przeciekały do innego projektu." 833 | ] 834 | }, 835 | { 836 | "cell_type": "markdown", 837 | "metadata": { 838 | "slideshow": { 839 | "slide_type": "subslide" 840 | } 841 | }, 842 | "source": [ 843 | "Przechodzimy do katalogu, w którym chcemy trzymać wirtualne środowisko i uruchamiamy:\n", 844 | "\n", 845 | "```bash\n", 846 | "python3.10 -m venv nazwa_virtual_env\n", 847 | "```" 848 | ] 849 | }, 850 | { 851 | "cell_type": "markdown", 852 | "metadata": { 853 | "slideshow": { 854 | "slide_type": "fragment" 855 | } 856 | }, 857 | "source": [ 858 | "Teraz w katalogu `nazwa_virtual_env` powinno pojawić się dużo podkatalogów i plików." 859 | ] 860 | }, 861 | { 862 | "cell_type": "markdown", 863 | "metadata": { 864 | "slideshow": { 865 | "slide_type": "subslide" 866 | } 867 | }, 868 | "source": [ 869 | "Żeby używać środowiska wirtualnego to musimy je \"uruchomić\" - sprawić, aby konsola, której będziemy używali korzystała z bibliotek danego środowiska." 870 | ] 871 | }, 872 | { 873 | "cell_type": "markdown", 874 | "metadata": { 875 | "slideshow": { 876 | "slide_type": "fragment" 877 | } 878 | }, 879 | "source": [ 880 | "Aktywowanie środowiska wirtualnego:\n", 881 | "\n", 882 | "Ubuntu:\n", 883 | "```bash\n", 884 | "source nazwa_virtual_env/bin/activate\n", 885 | "```\n", 886 | "\n", 887 | "Okienka:\n", 888 | "```\n", 889 | "nazwa_virtual_env\\Scripts\\activate.bat\n", 890 | "```" 891 | ] 892 | }, 893 | { 894 | "cell_type": "markdown", 895 | "metadata": { 896 | "slideshow": { 897 | "slide_type": "subslide" 898 | } 899 | }, 900 | "source": [ 901 | "Po udanej aktywacji powinniśmy zobaczyć na początku nowej linii `(nazwa_virtual_env)`. Oznacza to, że w tej konsoli możemy korzystać z bibliotek w podanym środowisku wirtualnym." 902 | ] 903 | }, 904 | { 905 | "cell_type": "markdown", 906 | "metadata": { 907 | "slideshow": { 908 | "slide_type": "subslide" 909 | } 910 | }, 911 | "source": [ 912 | "## 2. Instalacja zależności" 913 | ] 914 | }, 915 | { 916 | "cell_type": "markdown", 917 | "metadata": { 918 | "slideshow": { 919 | "slide_type": "fragment" 920 | } 921 | }, 922 | "source": [ 923 | "W konsoli z włączonym odpowiednim virtualenv:\n", 924 | "\n", 925 | "```bash\n", 926 | "pip install fastapi[all]\n", 927 | "```" 928 | ] 929 | }, 930 | { 931 | "cell_type": "markdown", 932 | "metadata": { 933 | "slideshow": { 934 | "slide_type": "fragment" 935 | } 936 | }, 937 | "source": [ 938 | "Zostanie zainstalowane fastapi ze wszystkimi zależnościami.\n", 939 | "Chwilę to zajmie." 940 | ] 941 | }, 942 | { 943 | "cell_type": "markdown", 944 | "metadata": { 945 | "slideshow": { 946 | "slide_type": "fragment" 947 | } 948 | }, 949 | "source": [ 950 | "Zobaczmy co zostało zainstalowane:\n", 951 | "```bash\n", 952 | "pip freeze\n", 953 | "```" 954 | ] 955 | }, 956 | { 957 | "cell_type": "markdown", 958 | "metadata": { 959 | "slideshow": { 960 | "slide_type": "fragment" 961 | } 962 | }, 963 | "source": [ 964 | "Przygotowanie pliku z zależnościami\n", 965 | "\n", 966 | "```bash\n", 967 | "pip freeze > requirements.txt\n", 968 | "```\n" 969 | ] 970 | }, 971 | { 972 | "cell_type": "markdown", 973 | "metadata": { 974 | "slideshow": { 975 | "slide_type": "fragment" 976 | } 977 | }, 978 | "source": [ 979 | "Plik `requirements.txt` zawiera zależności potrzebne do uruchomienia naszego projektu. Będzie nam potrzebny później. Ale póki co zobaczmy co jest w środku." 980 | ] 981 | }, 982 | { 983 | "cell_type": "markdown", 984 | "metadata": { 985 | "slideshow": { 986 | "slide_type": "subslide" 987 | } 988 | }, 989 | "source": [ 990 | "## 3. Wreszcie jakiś kod" 991 | ] 992 | }, 993 | { 994 | "cell_type": "markdown", 995 | "metadata": { 996 | "slideshow": { 997 | "slide_type": "subslide" 998 | } 999 | }, 1000 | "source": [ 1001 | "```python\n", 1002 | "# main.py\n", 1003 | "\n", 1004 | "from fastapi import FastAPI\n", 1005 | "\n", 1006 | "app = FastAPI()\n", 1007 | "\n", 1008 | "@app.get(\"/\")\n", 1009 | "def root():\n", 1010 | " return {\"message\": \"Hello World\"}\n", 1011 | "```" 1012 | ] 1013 | }, 1014 | { 1015 | "cell_type": "markdown", 1016 | "metadata": { 1017 | "slideshow": { 1018 | "slide_type": "subslide" 1019 | } 1020 | }, 1021 | "source": [ 1022 | "Uruchamiamy:\n", 1023 | "```bash\n", 1024 | "uvicorn main:app\n", 1025 | "```\n", 1026 | "\n", 1027 | "Przechodzimy pod:\n", 1028 | "```\n", 1029 | "http://127.0.0.1:8000\n", 1030 | "http://127.0.0.1:8000/docs\n", 1031 | "```\n", 1032 | "\n" 1033 | ] 1034 | }, 1035 | { 1036 | "cell_type": "markdown", 1037 | "metadata": { 1038 | "slideshow": { 1039 | "slide_type": "subslide" 1040 | } 1041 | }, 1042 | "source": [ 1043 | "Omówienie linia po linii co się dzieje" 1044 | ] 1045 | }, 1046 | { 1047 | "cell_type": "markdown", 1048 | "metadata": { 1049 | "slideshow": { 1050 | "slide_type": "subslide" 1051 | } 1052 | }, 1053 | "source": [ 1054 | "## 4. Napisanie testu do endpointu" 1055 | ] 1056 | }, 1057 | { 1058 | "cell_type": "markdown", 1059 | "metadata": { 1060 | "slideshow": { 1061 | "slide_type": "subslide" 1062 | } 1063 | }, 1064 | "source": [ 1065 | "Musimy najpierw doinstalować zależności - czyli `pytest`\n", 1066 | "```bash\n", 1067 | "pip install pytest\n", 1068 | "```" 1069 | ] 1070 | }, 1071 | { 1072 | "cell_type": "markdown", 1073 | "metadata": { 1074 | "slideshow": { 1075 | "slide_type": "subslide" 1076 | } 1077 | }, 1078 | "source": [ 1079 | "Zobaczmy co zostało zainstalowane:\n", 1080 | "```\n", 1081 | "pip freeze\n", 1082 | "```\n", 1083 | "Przygotowanie pliku z zależnościami developerskimi\n", 1084 | "```\n", 1085 | "pip freeze > requirements-dev.txt\n", 1086 | "```\n", 1087 | "I teraz usuwamy wszystko co jest w `requirements.txt` z `requirements-dev.txt`.\n", 1088 | "Jeśli chodzi o zależności projektowe to jest z nimi dużo zabawy/problemów/możliwości. Nie jest to niestety prosty temat.\n" 1089 | ] 1090 | }, 1091 | { 1092 | "cell_type": "markdown", 1093 | "metadata": { 1094 | "slideshow": { 1095 | "slide_type": "subslide" 1096 | } 1097 | }, 1098 | "source": [ 1099 | "Tak na prawdę nasza aplikacja wymaga tylko:\n", 1100 | "```\n", 1101 | "fastapi\n", 1102 | "uvicorn\n", 1103 | "```\n", 1104 | "\n", 1105 | "Cała reszta, czyli:\n", 1106 | "```\n", 1107 | "click\n", 1108 | "h11\n", 1109 | "httptools\n", 1110 | "pydantic\n", 1111 | "starlette\n", 1112 | "uvloop\n", 1113 | "websockets\n", 1114 | "```\n", 1115 | "\n", 1116 | "Zostanie zainstalowana jako zależności naszych zależności." 1117 | ] 1118 | }, 1119 | { 1120 | "cell_type": "markdown", 1121 | "metadata": { 1122 | "slideshow": { 1123 | "slide_type": "subslide" 1124 | } 1125 | }, 1126 | "source": [ 1127 | "Z jednej strony niby nie ma potrzeby podawania zależności zależności, bo pip je sam ogarnie i zainstaluje." 1128 | ] 1129 | }, 1130 | { 1131 | "cell_type": "markdown", 1132 | "metadata": { 1133 | "slideshow": { 1134 | "slide_type": "subslide" 1135 | } 1136 | }, 1137 | "source": [ 1138 | "### Mały quiz:\n", 1139 | "1. Dlaczego warto przypinać wersje zależności?\n", 1140 | "2. Czy warto przypinać wersje zależności zależności?\n", 1141 | "3. Czy można zrobić coś jeszcze lepiej? I dlaczego?" 1142 | ] 1143 | }, 1144 | { 1145 | "cell_type": "markdown", 1146 | "metadata": { 1147 | "slideshow": { 1148 | "slide_type": "subslide" 1149 | } 1150 | }, 1151 | "source": [ 1152 | "Do uruchamiania testów potrzebujemy:\n", 1153 | "```\n", 1154 | "pytest\n", 1155 | "requests\n", 1156 | "```" 1157 | ] 1158 | }, 1159 | { 1160 | "cell_type": "markdown", 1161 | "metadata": { 1162 | "slideshow": { 1163 | "slide_type": "subslide" 1164 | } 1165 | }, 1166 | "source": [ 1167 | "```python\n", 1168 | "from fastapi.testclient import TestClient\n", 1169 | "\n", 1170 | "from main import app\n", 1171 | "\n", 1172 | "client = TestClient(app)\n", 1173 | "\n", 1174 | "\n", 1175 | "def test_read_main():\n", 1176 | " response = client.get(\"/\")\n", 1177 | " assert response.status_code == 200\n", 1178 | " assert response.json() == {\"message\": \"Hello World\"}\n", 1179 | "```" 1180 | ] 1181 | }, 1182 | { 1183 | "cell_type": "markdown", 1184 | "metadata": { 1185 | "slideshow": { 1186 | "slide_type": "subslide" 1187 | } 1188 | }, 1189 | "source": [ 1190 | "Uruchomienie testów (z katalogu z aplikacją), z virtualenv z zainstalowanyi wszystkim zależnościami:\n", 1191 | "```bash\n", 1192 | "pytest\n", 1193 | "```" 1194 | ] 1195 | }, 1196 | { 1197 | "cell_type": "markdown", 1198 | "metadata": { 1199 | "slideshow": { 1200 | "slide_type": "fragment" 1201 | } 1202 | }, 1203 | "source": [ 1204 | "link do dokumentacji:\n", 1205 | "- testowanie fastapi: https://fastapi.tiangolo.com/tutorial/testing/\n", 1206 | "- przykłdy użycia pytest: https://docs.pytest.org/en/latest/example/index.html\n" 1207 | ] 1208 | }, 1209 | { 1210 | "cell_type": "markdown", 1211 | "metadata": { 1212 | "slideshow": { 1213 | "slide_type": "subslide" 1214 | } 1215 | }, 1216 | "source": [ 1217 | "## 5. Czas na diploj" 1218 | ] 1219 | }, 1220 | { 1221 | "cell_type": "markdown", 1222 | "metadata": { 1223 | "slideshow": { 1224 | "slide_type": "subslide" 1225 | } 1226 | }, 1227 | "source": [ 1228 | "Będziemy potrzebować jeszcze dwóch plików:\n", 1229 | "\n", 1230 | "`Procfile`\n", 1231 | "```\n", 1232 | "web: uvicorn main:app --host=0.0.0.0 --port=${PORT:-5000}\n", 1233 | "```\n", 1234 | "`runtime.txt`\n", 1235 | "```\n", 1236 | "python-3.10.3\n", 1237 | "```" 1238 | ] 1239 | }, 1240 | { 1241 | "cell_type": "markdown", 1242 | "metadata": { 1243 | "slideshow": { 1244 | "slide_type": "subslide" 1245 | } 1246 | }, 1247 | "source": [ 1248 | "- Tworzymy (publiczne - dla pracy domowej) repo w github: https://github.com/new\n", 1249 | "- klonujemy repozytorium\n", 1250 | "- wrzucamy nasz kod + zależności\n", 1251 | " - main.py - kod aplikacji\n", 1252 | " - requirements.txt - spis zależności\n", 1253 | " - Procfile - heroku specific, żeby heroku wiedziało jak ma uruchomić naszą aplikację\n", 1254 | " - runtime.txt - heroku specific, żeby heroku wiedziało której wersji python użyć\n", 1255 | "- zakładamy konto na heroku\n", 1256 | "- utworzenie nowej aplikacji na heroku: https://dashboard.heroku.com/new-app\n", 1257 | "- wyklikujemy projekt na heroku i podpinamy pod niego diploj\n", 1258 | " - W zakładce `Deploy` w sekcji `Deployment method` wybieramy opcję `GitHub`\n", 1259 | " - dodajemy swojego użytkownika\n", 1260 | " - wybieramy repo\n", 1261 | " - wybieramy opcję automatycznego diploju z mastera (main)\n", 1262 | " - Za pierwszym razem diplojujemy ręcznie (przycisk `Deploy now` na dole)\n", 1263 | " - patrzymy w zakładkę `Activity`\n", 1264 | " - Jak się zdiplojuje poprawnie to klikamy `Open app`\n", 1265 | " - podziwiamy jak pięknie i prosto było (jak komuś nie wyszło to debuguje XD)\n", 1266 | "- od teraz po każdym pushu do mastera (main) diploj będzie się robił sam" 1267 | ] 1268 | }, 1269 | { 1270 | "cell_type": "markdown", 1271 | "metadata": { 1272 | "slideshow": { 1273 | "slide_type": "subslide" 1274 | } 1275 | }, 1276 | "source": [ 1277 | "Jak ktoś nie ogarnia git to powinien szybko zacząć :)\n", 1278 | "- książka: https://git-scm.com/book/en/v2" 1279 | ] 1280 | }, 1281 | { 1282 | "cell_type": "markdown", 1283 | "metadata": { 1284 | "slideshow": { 1285 | "slide_type": "subslide" 1286 | } 1287 | }, 1288 | "source": [ 1289 | "## 6. Rozwój aplikacji" 1290 | ] 1291 | }, 1292 | { 1293 | "cell_type": "markdown", 1294 | "metadata": { 1295 | "slideshow": { 1296 | "slide_type": "subslide" 1297 | } 1298 | }, 1299 | "source": [ 1300 | "Chcielibyśmy, żeby aplikacji odpowiadała `Hello {name}` pod ścieżką `/hello/{name}`" 1301 | ] 1302 | }, 1303 | { 1304 | "cell_type": "markdown", 1305 | "metadata": { 1306 | "slideshow": { 1307 | "slide_type": "subslide" 1308 | } 1309 | }, 1310 | "source": [ 1311 | "Zaczniemy od napisania prostego testu" 1312 | ] 1313 | }, 1314 | { 1315 | "cell_type": "markdown", 1316 | "metadata": { 1317 | "slideshow": { 1318 | "slide_type": "subslide" 1319 | } 1320 | }, 1321 | "source": [ 1322 | "```python\n", 1323 | "def test_hello_name(name):\n", 1324 | " name = 'Kamila'\n", 1325 | " response = client.get(f\"/hello/{name}\")\n", 1326 | " assert response.status_code == 200\n", 1327 | " assert response.text == f'\"Hello {name}\"'\n", 1328 | "```" 1329 | ] 1330 | }, 1331 | { 1332 | "cell_type": "markdown", 1333 | "metadata": { 1334 | "slideshow": { 1335 | "slide_type": "fragment" 1336 | } 1337 | }, 1338 | "source": [ 1339 | "Ale możemy też znacznie ulepszyć nasz test." 1340 | ] 1341 | }, 1342 | { 1343 | "cell_type": "markdown", 1344 | "metadata": { 1345 | "slideshow": { 1346 | "slide_type": "subslide" 1347 | } 1348 | }, 1349 | "source": [ 1350 | "```python\n", 1351 | "@pytest.mark.parametrize(\"name\", ['Zenek', 'Marek', 'Alojzy Niezdąży'])\n", 1352 | "def test_hello_name(name):\n", 1353 | " response = client.get(f\"/hello/{name}\")\n", 1354 | " assert response.status_code == 200\n", 1355 | " assert response.text == f'\"Hello {name}\"'\n", 1356 | "```" 1357 | ] 1358 | }, 1359 | { 1360 | "cell_type": "markdown", 1361 | "metadata": { 1362 | "slideshow": { 1363 | "slide_type": "subslide" 1364 | } 1365 | }, 1366 | "source": [ 1367 | "Czas na implementację" 1368 | ] 1369 | }, 1370 | { 1371 | "cell_type": "markdown", 1372 | "metadata": { 1373 | "slideshow": { 1374 | "slide_type": "subslide" 1375 | } 1376 | }, 1377 | "source": [ 1378 | "```python\n", 1379 | "# main.py\n", 1380 | "\n", 1381 | "from fastapi import FastAPI\n", 1382 | "\n", 1383 | "app = FastAPI()\n", 1384 | "\n", 1385 | "@app.get(\"/\")\n", 1386 | "def root():\n", 1387 | " return {\"message\": \"Hello World\"}\n", 1388 | " \n", 1389 | "@app.get(\"/hello/{name}\")\n", 1390 | "async def read_item(name: str):\n", 1391 | " return f\"Hello {name}\"\n", 1392 | "```" 1393 | ] 1394 | }, 1395 | { 1396 | "cell_type": "markdown", 1397 | "metadata": { 1398 | "slideshow": { 1399 | "slide_type": "subslide" 1400 | } 1401 | }, 1402 | "source": [ 1403 | "Objaśnienie co się dzieje" 1404 | ] 1405 | }, 1406 | { 1407 | "cell_type": "markdown", 1408 | "metadata": { 1409 | "slideshow": { 1410 | "slide_type": "subslide" 1411 | } 1412 | }, 1413 | "source": [ 1414 | "Typowanie:\n", 1415 | "- wprowadzenie od fastapi: https://fastapi.tiangolo.com/python-types/\n", 1416 | "- dokumentacja: https://docs.python.org/3/library/typing.html\n", 1417 | "- przewodnik od realpytho (Polecam ten artykuł - Marcin): https://realpython.com/python-type-checking/" 1418 | ] 1419 | }, 1420 | { 1421 | "cell_type": "markdown", 1422 | "metadata": { 1423 | "slideshow": { 1424 | "slide_type": "subslide" 1425 | } 1426 | }, 1427 | "source": [ 1428 | "Można jeszcze lepiej" 1429 | ] 1430 | }, 1431 | { 1432 | "cell_type": "markdown", 1433 | "metadata": { 1434 | "slideshow": { 1435 | "slide_type": "subslide" 1436 | } 1437 | }, 1438 | "source": [ 1439 | "```python\n", 1440 | "from fastapi import FastAPI\n", 1441 | "\n", 1442 | "from pydantic import BaseModel\n", 1443 | "\n", 1444 | "app = FastAPI()\n", 1445 | "# to see what funny will come\n", 1446 | "app.counter = 0\n", 1447 | "\n", 1448 | "\n", 1449 | "class HelloResp(BaseModel):\n", 1450 | " msg: str\n", 1451 | "\n", 1452 | "@app.get(\"/\")\n", 1453 | "def root():\n", 1454 | " return {\"message\": \"Hello World\"}\n", 1455 | "\n", 1456 | "\n", 1457 | "@app.get('/counter')\n", 1458 | "def counter():\n", 1459 | "\tapp.counter += 1\n", 1460 | "\treturn str(app.counter)\n", 1461 | "\n", 1462 | "\n", 1463 | "@app.get(\"/hello/{name}\", response_model=HelloResp)\n", 1464 | "async def read_item(name: str):\n", 1465 | " return HelloResp(msg=f\"Hello {name}\")\n", 1466 | "```" 1467 | ] 1468 | }, 1469 | { 1470 | "cell_type": "markdown", 1471 | "metadata": { 1472 | "slideshow": { 1473 | "slide_type": "subslide" 1474 | } 1475 | }, 1476 | "source": [ 1477 | "Pydantic:\n", 1478 | "- oficjalna dokumentacja: https://pydantic-docs.helpmanual.io/\n", 1479 | "- pydantic w fastapi: https://fastapi.tiangolo.com/python-types/#pydantic-models" 1480 | ] 1481 | }, 1482 | { 1483 | "cell_type": "markdown", 1484 | "metadata": { 1485 | "slideshow": { 1486 | "slide_type": "subslide" 1487 | } 1488 | }, 1489 | "source": [ 1490 | "## 7. Inne metody niż GET" 1491 | ] 1492 | }, 1493 | { 1494 | "cell_type": "markdown", 1495 | "metadata": { 1496 | "slideshow": { 1497 | "slide_type": "subslide" 1498 | } 1499 | }, 1500 | "source": [ 1501 | "### POST" 1502 | ] 1503 | }, 1504 | { 1505 | "cell_type": "markdown", 1506 | "metadata": { 1507 | "slideshow": { 1508 | "slide_type": "subslide" 1509 | } 1510 | }, 1511 | "source": [ 1512 | "piszemy test\n", 1513 | "\n", 1514 | "```python\n", 1515 | "def test_receive_something():\n", 1516 | " response = client.post(\"/dej/mi/coś\", json={'first_key': 'some_value'})\n", 1517 | " assert response.json() == {\"received\": {'first_key': 'some_value'},\n", 1518 | " \"constant_data\": \"python jest super\"}\n", 1519 | "\n", 1520 | "```" 1521 | ] 1522 | }, 1523 | { 1524 | "cell_type": "markdown", 1525 | "metadata": { 1526 | "slideshow": { 1527 | "slide_type": "subslide" 1528 | } 1529 | }, 1530 | "source": [ 1531 | "dopisujemy endpoint\n", 1532 | "```python\n", 1533 | "class GiveMeSomethingRq(BaseModel):\n", 1534 | " first_key: str\n", 1535 | "\n", 1536 | "\n", 1537 | "class GiveMeSomethingResp(BaseModel):\n", 1538 | " received: Dict\n", 1539 | " constant_data: str = \"python jest super\"\n", 1540 | "\n", 1541 | "\n", 1542 | "@app.post(\"/dej/mi/coś\", response_model=GiveMeSomethingResp)\n", 1543 | "def receive_something(rq: GiveMeSomethingRq):\n", 1544 | " return GiveMeSomethingResp(received=rq.dict())\n", 1545 | "\n", 1546 | "```" 1547 | ] 1548 | }, 1549 | { 1550 | "cell_type": "markdown", 1551 | "metadata": { 1552 | "slideshow": { 1553 | "slide_type": "subslide" 1554 | } 1555 | }, 1556 | "source": [ 1557 | "![Dlaczego python](https://imgs.xkcd.com/comics/python.png)" 1558 | ] 1559 | }, 1560 | { 1561 | "cell_type": "markdown", 1562 | "metadata": { 1563 | "slideshow": { 1564 | "slide_type": "subslide" 1565 | } 1566 | }, 1567 | "source": [ 1568 | "Wiecej będzie na wykładzie nr 3" 1569 | ] 1570 | }, 1571 | { 1572 | "cell_type": "markdown", 1573 | "metadata": { 1574 | "slideshow": { 1575 | "slide_type": "slide" 1576 | } 1577 | }, 1578 | "source": [ 1579 | "# V. Pytania?" 1580 | ] 1581 | }, 1582 | { 1583 | "cell_type": "markdown", 1584 | "metadata": { 1585 | "slideshow": { 1586 | "slide_type": "subslide" 1587 | } 1588 | }, 1589 | "source": [ 1590 | "## Jest zadanie domowe ;)" 1591 | ] 1592 | }, 1593 | { 1594 | "cell_type": "markdown", 1595 | "metadata": { 1596 | "slideshow": { 1597 | "slide_type": "slide" 1598 | } 1599 | }, 1600 | "source": [ 1601 | "# That's all folks!" 1602 | ] 1603 | } 1604 | ], 1605 | "metadata": { 1606 | "celltoolbar": "Slideshow", 1607 | "kernelspec": { 1608 | "display_name": "Python 3", 1609 | "language": "python", 1610 | "name": "python3" 1611 | }, 1612 | "language_info": { 1613 | "codemirror_mode": { 1614 | "name": "ipython", 1615 | "version": 3 1616 | }, 1617 | "file_extension": ".py", 1618 | "mimetype": "text/x-python", 1619 | "name": "python", 1620 | "nbconvert_exporter": "python", 1621 | "pygments_lexer": "ipython3", 1622 | "version": "3.8.2" 1623 | } 1624 | }, 1625 | "nbformat": 4, 1626 | "nbformat_minor": 4 1627 | } 1628 | -------------------------------------------------------------------------------- /1_D_jak_deploy/web_app/Procfile: -------------------------------------------------------------------------------- 1 | web: uvicorn main:app --host=0.0.0.0 --port=${PORT:-5000} -------------------------------------------------------------------------------- /1_D_jak_deploy/web_app/main.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from fastapi import FastAPI 4 | 5 | from pydantic import BaseModel 6 | 7 | 8 | app = FastAPI() 9 | 10 | 11 | @app.get("/") 12 | def root(): 13 | return {"message": "Hello World"} 14 | 15 | 16 | class HelloResp(BaseModel): 17 | msg: str 18 | 19 | 20 | @app.get("/hello/{name}", response_model=HelloResp) 21 | def read_item(name: str): 22 | return HelloResp(msg=f"Hello {name}") 23 | 24 | 25 | class GiveMeSomethingRq(BaseModel): 26 | first_key: str 27 | 28 | 29 | class GiveMeSomethingResp(BaseModel): 30 | received: Dict 31 | constant_data: str = "python jest super" 32 | 33 | 34 | @app.post("/dej/mi/coś", response_model=GiveMeSomethingResp) 35 | def receive_something(rq: GiveMeSomethingRq): 36 | return GiveMeSomethingResp(received=rq.dict()) 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /1_D_jak_deploy/web_app/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles 2 | aniso8601 3 | async-exit-stack 4 | async-generator 5 | certifi 6 | chardet 7 | click 8 | dnspython 9 | email-validator 10 | fastapi 11 | graphene 12 | graphql-core 13 | graphql-relay 14 | h11 15 | httptools 16 | idna 17 | itsdangerous 18 | Jinja2 19 | MarkupSafe 20 | promise 21 | pydantic 22 | python-multipart 23 | PyYAML 24 | requests 25 | Rx 26 | six 27 | starlette 28 | ujson 29 | urllib3 30 | uvicorn 31 | uvloop 32 | websockets 33 | -------------------------------------------------------------------------------- /1_D_jak_deploy/web_app/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.3 -------------------------------------------------------------------------------- /1_D_jak_deploy/web_app/test_main.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | import pytest 3 | from main import app 4 | 5 | client = TestClient(app) 6 | 7 | 8 | def test_read_main(): 9 | response = client.get("/") 10 | assert response.status_code == 200 11 | assert response.json() == {"message": "Hello World"} 12 | 13 | 14 | @pytest.mark.parametrize("name", ['Zenek', 'Marek', 'Alojzy Niezdąży']) 15 | def test_hello_name(name): 16 | # name = 'elo' 17 | response = client.get(f"/hello/{name}") 18 | assert response.status_code == 200 19 | assert response.json() == {'msg': f"Hello {name}"} 20 | 21 | 22 | def test_receive_something(): 23 | response = client.post("/dej/mi/coś", json={'first_key': 'some_value'}) 24 | assert response.json() == {"received": {'first_key': 'some_value'}, 25 | "constant_data": "python jest super"} 26 | -------------------------------------------------------------------------------- /3_F_jak_Fast/app.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | import uuid 3 | from contextlib import contextmanager 4 | from datetime import datetime 5 | from hashlib import sha512 6 | from typing import List, Optional, Union 7 | 8 | from fastapi import APIRouter 9 | 10 | 11 | from fastapi import Cookie, Depends, FastAPI, HTTPException, Request, Response, status, Query 12 | from fastapi.responses import ( 13 | HTMLResponse, 14 | JSONResponse, 15 | PlainTextResponse, 16 | RedirectResponse, 17 | ) 18 | from fastapi.security import HTTPBasic, HTTPBasicCredentials 19 | from fastapi.templating import Jinja2Templates 20 | 21 | app = FastAPI() 22 | templates = Jinja2Templates(directory="templates") 23 | 24 | r1 = APIRouter() 25 | 26 | @app.get("/request_query_string_discovery") 27 | def read_items(u: str = "default", q: List[int] = Query(None)): 28 | query_items = {"q": q, "u": u} 29 | 30 | return askdjhsajdkhaskjd() 31 | 32 | return query_items 33 | 34 | 35 | @app.get("/askdjhsajdkhaskjd") 36 | def askdjhsajdkhaskjd(): 37 | return {"yeye": "asjkdhaskd"} 38 | 39 | 40 | 41 | @app.get("/static", response_class=HTMLResponse) 42 | @app.get("/jinja",response_class=HTMLResponse) 43 | def read_item(request: Request, my_string: str = Query("Wheeeee!"), my_list: List[str]= Query(None)): 44 | my_list = my_list or [1,2,3,4] 45 | return templates.TemplateResponse("index.html.j2", { 46 | "request": request, 47 | "my_string": my_string, 48 | "my_list": my_list 49 | }) 50 | 51 | @app.get("/simple_path_tmpl/{sample_variable}/{a}/orders") 52 | def simple_path_tmpl(sample_variable: str, a: int): 53 | print(f"{sample_variable=}") 54 | print(type(sample_variable)) 55 | return {"sample_variable": sample_variable, 56 | "a": a} 57 | 58 | @app.get("/muza/{artist}/{album}/{track_no}") 59 | def simple_path_tmpl(artist: str,album: str, track_no: int): 60 | return {"artist": artist, 61 | "album": album, 62 | "track_no": track_no} 63 | 64 | 65 | objects = { 66 | 1: {"field_a": "1a", "field_b": "1b"}, 67 | 2: {"field_a": "2a", "field_b": "2b"}, 68 | 3: {"field_a": "3a", "field_b": "3b"}, 69 | # .... # 70 | } 71 | 72 | @app.get("/db/{obj_id}/{field}") 73 | def db(obj_id: int, field: str): 74 | print(f"{obj_id=}") 75 | print(f"{field=}") 76 | return {"field": objects.get(obj_id,{}).get(f"field_{field}")} 77 | 78 | 79 | @app.delete("/db/{obj_id}/{field}") 80 | def db2(obj_id: int, field: str): 81 | print(f"{obj_id=}") 82 | print(f"{field=}") 83 | return {"field": objects.get(obj_id,{}).get(f"field_{field}")} 84 | 85 | 86 | 87 | 88 | @r1.get("/files/{file_path:path}/edit") 89 | def edit_file(file_path: str): 90 | return {"edit_stuff": file_path} 91 | 92 | @r1.get("/files/{file_path:path}") 93 | def read_file(file_path: str): 94 | return {"file_path": file_path} 95 | 96 | 97 | app.include_router(r1) 98 | app.include_router( 99 | r1, 100 | prefix="/v1", 101 | tags=["v1"], 102 | ) 103 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/Northwind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaftAcademy/da-python-level-up-dev-2022/ad7e56fbaeda28d416ac22ce63956528a94f64be/4_T_jak_Tabela/Northwind.png -------------------------------------------------------------------------------- /4_T_jak_Tabela/PracaDomowa.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "c3ed62df-d947-4ec7-b14c-72f34f599203", 6 | "metadata": {}, 7 | "source": [ 8 | "## Praca Domowa - Wykład nr 4\n", 9 | "\n", 10 | "W pracy domowej do wykładu numer cztery, nadal tworzymy aplikację internetowej. W związku z tym kod nadal wrzucamy na platformę `Heroku`, zaś do `CodingRooms` wrzucamy jedynie link do aplikacji." 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "2cde3c8f-ead4-4fd9-8867-3fe17d09eca1", 16 | "metadata": {}, 17 | "source": [ 18 | "### Zadanie 4.1\n", 19 | "\n", 20 | "Na pierwszy ogień stwórz dwa endpointy `/categories` oraz `/customers`. Oba endpointy mają być obsługiwane przez metodę `GET`, zwracać kod `HTTP 200` oraz dane mają być posortowane po kolumnie `id`." 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "id": "53c57f10-4dc3-4e80-969e-cb13afb4d400", 26 | "metadata": {}, 27 | "source": [ 28 | "Endpoint `/categories` ma zwracać rekordy z tabeli `Categories` w następującym formacie:\n", 29 | "```\n", 30 | "{\n", 31 | " \"categories\": [\n", 32 | " {\"id\": 1, \"name\": \"Beverages\"},\n", 33 | " [...]\n", 34 | " ]\n", 35 | "}\n", 36 | "```\n", 37 | "gdzie `id` to jest kolumna `CategoryID`, zaś `name` to `CategoryName`." 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "id": "2c8ec17f-2ec2-4e16-a92e-868f811c3faf", 43 | "metadata": {}, 44 | "source": [ 45 | "Endpoint `/customers` ma zwracać rekordy z tabeli `Customers` w następującym formacie:\n", 46 | "```\n", 47 | "{\n", 48 | " \"customers\": [\n", 49 | " {\n", 50 | " \"id\": \"ALFKI\",\n", 51 | " \"name\": \"Alfreds Futterkiste\",\n", 52 | " \"full_address\": \"Obere Str. 57 12209 Berlin Germany\",\n", 53 | " },\n", 54 | " [...]\n", 55 | " ]\n", 56 | "}\n", 57 | "```\n", 58 | "gdzie `id` to jest kolumna `CustomerID`, `name` to kolumna `CompanyName`, zaś `full_address` to jest są dane połączone z czterech kolumn w kolejności: `Address`, `PostalCode`, `City` oraz `Country` oddzielone spacją." 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "id": "9ed67807-8a39-4236-85fd-6d1caaf28519", 64 | "metadata": {}, 65 | "source": [ 66 | "### Zadanie 4.2\n", 67 | "\n", 68 | "Stwórz kolejny endpoint `/products/[id]`. Endpoint ma być obsługiwany przez metodę `GET`. Ma przyjmować id oraz zwracać dane rekordu z tabeli `Products` pod podanym id. Endpoint defaultowo ma zwracać kod `HTTP 200` lub `HTTP 404` gdy rekord o podanym id nie istnieje. Zwrócona odpowiedź ma być jsonem w następującym formacie:\n", 69 | "```\n", 70 | "{\"id\": 1, \"name\": \"Chai\"},\n", 71 | "```\n", 72 | "gdzie `id` to jest kolumna `ProductID`, zaś `name` to `ProductName`." 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "id": "887aa3f1-817c-4957-ab9e-b0342c9705ad", 78 | "metadata": {}, 79 | "source": [ 80 | "### Zadanie 4.3\n", 81 | "\n", 82 | "Stwórz endpoint `/employees`, obsługiwany przez metodę `GET`, ma zwracać defaultowo kod `HTTP 200` oraz ma pobierać dane z tabeli `Employees`. Dane defaultowo mają być posortowane po kolumnie `id`. Ponad to endpoint ma obsługiwać query parametry: `limit`, `offset` oraz `order`. Parametry `limit` oraz `offset` będą intami, zaś `order` będzie stringiem z nazwą kolumny po której należy posortować dane rosnąco. Dopuszczalne wartości dla parametru `order` to `first_name`, `last_name`, `city`. Gdyby parametr `order` miał inną wartość, należ zwrócić kod `HTTP 400`. Dane z ednpointu mają być zwrócone w postaci jsona w następującym formacie:\n", 83 | "\n", 84 | "```\n", 85 | "{\n", 86 | " \"employees\": [\n", 87 | " {\"id\":1,\"last_name\":\"Davolio\",\"first_name\":\"Nancy\",\"city\":\"Seattle\"},\n", 88 | " [...] \n", 89 | " ]\n", 90 | "}\n", 91 | "```\n", 92 | "\n", 93 | "gdzie `id` to jest kolumna `EmployeeID`, `last_name` to `LastName`, `first_name` to `FirstName`, zaś `city` to `City`. " 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "id": "54e5dbd2-4194-4364-b062-e2916dc1081e", 99 | "metadata": {}, 100 | "source": [ 101 | "### Zadanie 4.4\n", 102 | "\n", 103 | "Stwórz endpoint `/products_extended`, obsługiwany przez metodę `GET`, ma zwracać defaultowo kod `HTTP 200` oraz ma pobierać dane z tabeli `Products`. Dane mają być posortowane po kolumnie `id`oraz zwrócone w postaci jsona w nastepujący formacie:\n", 104 | "\n", 105 | "```\n", 106 | "{\n", 107 | " \"products_extended\": [\n", 108 | " {\n", 109 | " \"id\": 1,\n", 110 | " \"name\": \"Chai\",\n", 111 | " \"category\": \"Beverages\",\n", 112 | " \"supplier\": \"Exotic Liquids\",\n", 113 | " },\n", 114 | " [...] \n", 115 | " ]\n", 116 | "}\n", 117 | "```\n", 118 | "\n", 119 | "gdzie `id` to jest kolumna `ProductID`, `name` to `ProductName`, `category` to referencja do tabeli `Categories` i wartość z kolumny `CategoryName`, zaś `supplier` to referencja do tabeli `Suppliers` i wartość z kolumny `CompanyName`." 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "id": "e7e57d2a-18f4-4d72-84b9-92c545c06931", 125 | "metadata": {}, 126 | "source": [ 127 | "### Zadanie 4.5\n", 128 | "\n", 129 | "Stwórz endpoint `/products/[id]/orders`, obsługiwany przez metodę `GET` oraz standardowo ma zwracać kod `HTTP 200`. Kod `HTTP 404` ma zostać zwrócony gdy produkt o podanym id nie istnieje. Endpoint ma za zadanie zwrócić `Orders` w których znalazł się produkt o podanym id, posortowane po id zamówień, w następującym formacie:\n", 130 | "```\n", 131 | "{\n", 132 | " \"orders\": [\n", 133 | " {\n", 134 | " \"id\": 10273,\n", 135 | " \"customer\": \"QUICK-Stop\",\n", 136 | " \"quantity\": 24,\n", 137 | " \"total_price\": 565.44,\n", 138 | " },\n", 139 | " [...]\n", 140 | " ]\n", 141 | "}\n", 142 | "```\n", 143 | "gdzie \n", 144 | "- `id` to jest `OrderId` z tabeli `Orders`\n", 145 | "- `customer` to referencja do tabeli `Customers` i wartość z kolumny `CompanyName`\n", 146 | "- `quantity` to ilość zamowionego produktu o podanym id w danym zamówieniu (tabela `Order Details`)\n", 147 | "- `total_price` to wartość zamówionego produktu w danym zamówieniu, wyliczone według wzoru: `(UnitPrice x Quantity) - (Discount x (UnitPrice x Quantity))` (tabela `Order Details`). Otrzymaną wartość zaokrąglamy do dwóch miejsc po przecinku." 148 | ] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "id": "e5c51e13-03b1-4f44-acc2-a881f618006d", 153 | "metadata": {}, 154 | "source": [ 155 | "### Zadanie 4.6\n", 156 | "\n", 157 | "Endpointy w poprzednich zadaniach służyły do wydobywania danych z bazy,, czyli `R` w akronimie `CRUD` (Create - Read - Update - Delete). Czas zakodzić pozostałe \"litery\". W tym zadaniu należy stworzyć trzy endpointy:" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "id": "924a9a82-3c36-4402-ae6c-6d209cd07c06", 163 | "metadata": {}, 164 | "source": [ 165 | "Endpoint `/categories`, który będzie obsługowany przez metodę `POST`, zwróci kod `HTTP 201`, będzie przyjmować takiego jsona:\n", 166 | "```\n", 167 | "{\"name\": \"new test category\"}\n", 168 | "```\n", 169 | "Enpoind ma za zadanie utworzyć rekord w tabeli `Categories`, gdzie wartość `name` ma być w kolumnie `CategoryName` oraz zwróci jsona z utworzonym rekordem:\n", 170 | "```\n", 171 | "{\"id\": 12323, \"name\": \"test category\"}\n", 172 | "```\n" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": null, 178 | "id": "5025341c-381e-4717-a05b-3aaa2be4159e", 179 | "metadata": {}, 180 | "outputs": [], 181 | "source": [] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "id": "fdeb4215-212f-4f94-a805-f6f9b252794e", 186 | "metadata": {}, 187 | "source": [ 188 | "Endpoint `/categories/[id]`, który będzie obsługowany przez metodę `PUT`, zwróci kod `HTTP 200`, będzie przyjmować takiego jsona:\n", 189 | "```\n", 190 | "{\"name\": \"new test category\"}\n", 191 | "```\n", 192 | "Enpoind zmodyfikuje rekord w tabeli `Categories` o podanym id, oraz zwróci jsona ze zmodyfikowanym rekordem:\n", 193 | "```\n", 194 | "{\"id\": 12323, \"name\": \"test category\"}\n", 195 | "```\n", 196 | "W przypadku gdyby kategoria o podanym id nie istniała, endpoint ma zwrócić kod `HTTP 404`." 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": null, 202 | "id": "2fc3473f-3e17-4969-b120-c97d7150be8d", 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "id": "adb23729-3199-4aaf-83f8-2d25a8088767", 210 | "metadata": {}, 211 | "source": [ 212 | "Endpoint `/categories/[id]`, który będzie obsługowany przez metodę `DELETE`, zwróci kod `HTTP 200`. Endpoint ma za zadanie usunąć rekord z tabeli `Categories` o podanym id i zwrócić nastepującego jsona:\n", 213 | "```\n", 214 | "{\"deleted\": 1}\n", 215 | "```\n", 216 | "W przypadku gdyby kategoria o podanym id nie istniała, endopoint ma zwrócić kod `HTTP 404`." 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "id": "74bc6ff7-8e42-41a0-b030-be936dd24582", 222 | "metadata": {}, 223 | "source": [] 224 | } 225 | ], 226 | "metadata": { 227 | "kernelspec": { 228 | "display_name": "Python 3", 229 | "language": "python", 230 | "name": "python3" 231 | }, 232 | "language_info": { 233 | "codemirror_mode": { 234 | "name": "ipython", 235 | "version": 3 236 | }, 237 | "file_extension": ".py", 238 | "mimetype": "text/x-python", 239 | "name": "python", 240 | "nbconvert_exporter": "python", 241 | "pygments_lexer": "ipython3", 242 | "version": "3.8.0" 243 | } 244 | }, 245 | "nbformat": 4, 246 | "nbformat_minor": 5 247 | } 248 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/README.md: -------------------------------------------------------------------------------- 1 | # da-2022-example 2 | 3 | 4 | ## baza northwind 5 | https://code.google.com/archive/p/northwindextended/downloads 6 | 7 | 8 | ## heroku-cli 9 | https://devcenter.heroku.com/articles/heroku-cli 10 | 11 | 12 | ## Szczegóły jak zainstalować dockera 13 | https://docs.docker.com/engine/install/ 14 | 15 | 16 | ## Szczegóły jak zainstalować docker-compose (w przypadku większości systemów copose będzie już częścią dockera) 17 | https://docs.docker.com/compose/install/ 18 | 19 | 20 | ## jak uruchomić bazę lokalnie z docker-compose'a 21 | `docker compose up -d postgres` 22 | ta komenda pobierze (jeśli jeszcze nie jest pobrany obraz) i uruchomi 23 | 24 | 25 | ## Skąd wziąć DATABASE_URI 26 | DATABASE_URI autmatycznie zostanie dodane do zmiennych w aplkaji Heroku, ale jest tam niewłaściwa schema. 27 | postgres → postgresql 28 | 29 | 30 | ## dump db 31 | `pg_dump --format=c --no-owner --no-acl -U postgres > northwind.sql.dump` 32 | 33 | 34 | ## Restore db 35 | heroku pg:backups:restore 'https://github.com/DaftAcademy-Python-LevelUP-Dev-2022/da-python-level-up-dev-2022/blob/main/4_T_jak_Tabela/dumps/northwind.sql.dump' postgresql-silhouetted-07931 --app da-2021-example --confirm da-2021-example 36 | 37 | 38 | ## Generowanie modeli 39 | `sqlacodegen 'postgresql://postgres:DaftAcademy@127.0.0.1:5555' > models.py` 40 | 41 | 42 | ## Lokalne uruchomienie apki 43 | `SQLALCHEMY_DATABASE_URL="postgresql://postgres:DaftAcademy@127.0.0.1:5555/postgres" uvicorn app.main:app --host=0.0.0.0 --port=${PORT:-5000} 44 | postgresql://postgres:DaftAcademy@127.0.0.1:5555/postgres 45 | ` 46 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app.py: -------------------------------------------------------------------------------- 1 | import string 2 | import random 3 | import secrets 4 | import uuid 5 | from contextlib import contextmanager 6 | from datetime import datetime 7 | from hashlib import sha512 8 | from typing import List, Optional, Union 9 | 10 | from fastapi import APIRouter 11 | 12 | 13 | from fastapi import Cookie, Depends, FastAPI, HTTPException, Request, Response, status, Query 14 | from fastapi.responses import ( 15 | HTMLResponse, 16 | JSONResponse, 17 | PlainTextResponse, 18 | RedirectResponse, 19 | ) 20 | from fastapi.security import HTTPBasic, HTTPBasicCredentials 21 | from fastapi.templating import Jinja2Templates 22 | from pydantic import BaseModel 23 | 24 | import aiosqlite 25 | from fastapi import FastAPI 26 | 27 | app = FastAPI() 28 | 29 | 30 | 31 | 32 | class Customer(BaseModel): 33 | company_name: str 34 | 35 | 36 | @app.on_event("startup") 37 | async def startup(): 38 | app.db_connection = await aiosqlite.connect("northwind.db") 39 | app.db_connection.text_factory = lambda b: b.decode(errors="ignore") # northwind specific 40 | app.db_connection.row_factory = aiosqlite.Row 41 | 42 | 43 | @app.on_event("shutdown") 44 | async def shutdown(): 45 | await app.db_connection.close() 46 | 47 | 48 | @app.get("/data") 49 | async def root(): 50 | cursor = await app.db_connection.execute("SELECT * FROM Customers") 51 | data = await cursor.fetchall() 52 | return {"data": data} 53 | 54 | @app.get("/suppliers/{supplier_id}") 55 | async def single_supplier(supplier_id: int): 56 | data = await app.db_connection.execute( 57 | f"SELECT CompanyName, Address FROM Suppliers WHERE SupplierID = {supplier_id}") 58 | data = await data.fetchone() 59 | 60 | return data 61 | 62 | @app.get("/products") 63 | async def products(): 64 | products_query = await app.db_connection.execute("SELECT ProductName FROM Products") 65 | products = await products_query.fetchall() 66 | return { 67 | "products_counter": len(products), 68 | "products": products 69 | } 70 | 71 | @app.get("/customers") 72 | async def customers(): 73 | app.db_connection.row_factory = lambda cursor, x: x[0] 74 | artists_cursor = await app.db_connection.execute("SELECT CompanyName FROM Customers") 75 | data = await artists_cursor.fetchall() 76 | return data 77 | 78 | @app.post("/customers/add") 79 | async def customer_add(customer: Customer): 80 | new_customer_id = "".join(random.choice(string.ascii_letters) for _ in range(5)) 81 | 82 | cursor = await app.db_connection.execute( 83 | "INSERT INTO Customers (CustomerID, CompanyName) VALUES (?,?)", (new_customer_id, customer.company_name), 84 | ) 85 | await app.db_connection.commit() 86 | app.db_connection.row_factory = aiosqlite.Row 87 | customer_cursor = await app.db_connection.execute( 88 | """SELECT CustomerID AS customer_id, CompanyName AS company_name 89 | FROM Customers WHERE CustomerID = ?""", 90 | (new_customer_id, )) 91 | customer = await customer_cursor.fetchone() 92 | 93 | return customer -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/Procfile: -------------------------------------------------------------------------------- 1 | web: uvicorn app.main:app --host=0.0.0.0 --port=${PORT:-5000} 2 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/__init__.py: -------------------------------------------------------------------------------- 1 | from .main import app 2 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/crud.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | 3 | from . import models 4 | 5 | 6 | def get_shippers(db: Session): 7 | return db.query(models.Shipper).all() 8 | 9 | 10 | def get_shipper(db: Session, shipper_id: int): 11 | return ( 12 | db.query(models.Shipper).filter(models.Shipper.ShipperID == shipper_id).first() 13 | ) 14 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from sqlalchemy import create_engine 4 | from sqlalchemy.orm import sessionmaker 5 | 6 | SQLALCHEMY_DATABASE_URL = os.getenv("SQLALCHEMY_DATABASE_URL") 7 | 8 | engine = create_engine(SQLALCHEMY_DATABASE_URL) 9 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 10 | 11 | 12 | # Dependency 13 | def get_db(): 14 | try: 15 | db = SessionLocal() 16 | yield db 17 | finally: 18 | db.close() 19 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from .views import router as northwind_api_router 4 | 5 | app = FastAPI() 6 | 7 | app.include_router(northwind_api_router, tags=["northwind"]) 8 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Date, 4 | Float, 5 | Integer, 6 | LargeBinary, 7 | SmallInteger, 8 | String, 9 | Table, 10 | Text, 11 | ) 12 | from sqlalchemy.ext.declarative import declarative_base 13 | from sqlalchemy.sql.sqltypes import NullType 14 | 15 | Base = declarative_base() 16 | metadata = Base.metadata 17 | 18 | 19 | class Category(Base): 20 | __tablename__ = "categories" 21 | 22 | CategoryID = Column(SmallInteger, primary_key=True) 23 | CategoryName = Column(String(15), nullable=False) 24 | Description = Column(Text) 25 | Picture = Column(LargeBinary) 26 | 27 | 28 | class Customercustomerdemo(Base): 29 | __tablename__ = "customercustomerdemo" 30 | 31 | CustomerID = Column(NullType, primary_key=True, nullable=False) 32 | CustomerTypeID = Column(NullType, primary_key=True, nullable=False) 33 | 34 | 35 | class Customerdemographic(Base): 36 | __tablename__ = "customerdemographics" 37 | 38 | CustomerTypeID = Column(NullType, primary_key=True) 39 | CustomerDesc = Column(Text) 40 | 41 | 42 | class Customer(Base): 43 | __tablename__ = "customers" 44 | 45 | CustomerID = Column(NullType, primary_key=True) 46 | CompanyName = Column(String(40), nullable=False) 47 | ContactName = Column(String(30)) 48 | ContactTitle = Column(String(30)) 49 | Address = Column(String(60)) 50 | City = Column(String(15)) 51 | Region = Column(String(15)) 52 | PostalCode = Column(String(10)) 53 | Country = Column(String(15)) 54 | Phone = Column(String(24)) 55 | Fax = Column(String(24)) 56 | 57 | 58 | class Employee(Base): 59 | __tablename__ = "employees" 60 | 61 | EmployeeID = Column(SmallInteger, primary_key=True) 62 | LastName = Column(String(20), nullable=False) 63 | FirstName = Column(String(10), nullable=False) 64 | Title = Column(String(30)) 65 | TitleOfCourtesy = Column(String(25)) 66 | BirthDate = Column(Date) 67 | HireDate = Column(Date) 68 | Address = Column(String(60)) 69 | City = Column(String(15)) 70 | Region = Column(String(15)) 71 | PostalCode = Column(String(10)) 72 | Country = Column(String(15)) 73 | HomePhone = Column(String(24)) 74 | Extension = Column(String(4)) 75 | Photo = Column(LargeBinary) 76 | Notes = Column(Text) 77 | ReportsTo = Column(SmallInteger) 78 | PhotoPath = Column(String(255)) 79 | 80 | 81 | class Employeeterritory(Base): 82 | __tablename__ = "employeeterritories" 83 | 84 | EmployeeID = Column(SmallInteger, primary_key=True, nullable=False) 85 | TerritoryID = Column(String(20), primary_key=True, nullable=False) 86 | 87 | 88 | class OrderDetail(Base): 89 | __tablename__ = "order_details" 90 | 91 | OrderID = Column(SmallInteger, primary_key=True, nullable=False) 92 | ProductID = Column(SmallInteger, primary_key=True, nullable=False) 93 | UnitPrice = Column(Float, nullable=False) 94 | Quantity = Column(SmallInteger, nullable=False) 95 | Discount = Column(Float, nullable=False) 96 | 97 | 98 | class Order(Base): 99 | __tablename__ = "orders" 100 | 101 | OrderID = Column(SmallInteger, primary_key=True) 102 | CustomerID = Column(NullType) 103 | EmployeeID = Column(SmallInteger) 104 | OrderDate = Column(Date) 105 | RequiredDate = Column(Date) 106 | ShippedDate = Column(Date) 107 | ShipVia = Column(SmallInteger) 108 | Freight = Column(Float) 109 | ShipName = Column(String(40)) 110 | ShipAddress = Column(String(60)) 111 | ShipCity = Column(String(15)) 112 | ShipRegion = Column(String(15)) 113 | ShipPostalCode = Column(String(10)) 114 | ShipCountry = Column(String(15)) 115 | 116 | 117 | class Product(Base): 118 | __tablename__ = "products" 119 | 120 | ProductID = Column(SmallInteger, primary_key=True) 121 | ProductName = Column(String(40), nullable=False) 122 | SupplierID = Column(SmallInteger) 123 | CategoryID = Column(SmallInteger) 124 | QuantityPerUnit = Column(String(20)) 125 | UnitPrice = Column(Float) 126 | UnitsInStock = Column(SmallInteger) 127 | UnitsOnOrder = Column(SmallInteger) 128 | ReorderLevel = Column(SmallInteger) 129 | Discontinued = Column(Integer, nullable=False) 130 | 131 | 132 | class Region(Base): 133 | __tablename__ = "region" 134 | 135 | RegionID = Column(SmallInteger, primary_key=True) 136 | RegionDescription = Column(NullType, nullable=False) 137 | 138 | 139 | class Shipper(Base): 140 | __tablename__ = "shippers" 141 | 142 | ShipperID = Column(SmallInteger, primary_key=True) 143 | CompanyName = Column(String(40), nullable=False) 144 | Phone = Column(String(24)) 145 | 146 | 147 | class ShippersTmp(Base): 148 | __tablename__ = "shippers_tmp" 149 | 150 | ShipperID = Column(SmallInteger, primary_key=True) 151 | CompanyName = Column(String(40), nullable=False) 152 | Phone = Column(String(24)) 153 | 154 | 155 | class Supplier(Base): 156 | __tablename__ = "suppliers" 157 | 158 | SupplierID = Column(SmallInteger, primary_key=True) 159 | CompanyName = Column(String(40), nullable=False) 160 | ContactName = Column(String(30)) 161 | ContactTitle = Column(String(30)) 162 | Address = Column(String(60)) 163 | City = Column(String(15)) 164 | Region = Column(String(15)) 165 | PostalCode = Column(String(10)) 166 | Country = Column(String(15)) 167 | Phone = Column(String(24)) 168 | Fax = Column(String(24)) 169 | HomePage = Column(Text) 170 | 171 | 172 | class Territory(Base): 173 | __tablename__ = "territories" 174 | 175 | TerritoryID = Column(String(20), primary_key=True) 176 | TerritoryDescription = Column(NullType, nullable=False) 177 | RegionID = Column(SmallInteger, nullable=False) 178 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/models2.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | from sqlalchemy import ( 3 | CHAR, 4 | Column, 5 | Date, 6 | Float, 7 | Integer, 8 | LargeBinary, 9 | SmallInteger, 10 | String, 11 | Table, 12 | Text, 13 | ) 14 | from sqlalchemy.ext.declarative import declarative_base 15 | 16 | Base = declarative_base() 17 | metadata = Base.metadata 18 | 19 | 20 | class Category(Base): 21 | __tablename__ = "categories" 22 | 23 | CategoryID = Column(SmallInteger, primary_key=True) 24 | CategoryName = Column(String(15), nullable=False) 25 | Description = Column(Text) 26 | Picture = Column(LargeBinary) 27 | 28 | 29 | class Customercustomerdemo(Base): 30 | __tablename__ = "customercustomerdemo" 31 | 32 | CustomerID = Column(CHAR(6), primary_key=True, nullable=False) 33 | CustomerTypeID = Column(CHAR(6), primary_key=True, nullable=False) 34 | 35 | 36 | class Customerdemographic(Base): 37 | __tablename__ = "customerdemographics" 38 | 39 | CustomerTypeID = Column(CHAR(6), primary_key=True) 40 | CustomerDesc = Column(Text) 41 | 42 | 43 | class Customer(Base): 44 | __tablename__ = "customers" 45 | 46 | CustomerID = Column(CHAR(6), primary_key=True) 47 | CompanyName = Column(String(40), nullable=False) 48 | ContactName = Column(String(30)) 49 | ContactTitle = Column(String(30)) 50 | Address = Column(String(60)) 51 | City = Column(String(15)) 52 | Region = Column(String(15)) 53 | PostalCode = Column(String(10)) 54 | Country = Column(String(15)) 55 | Phone = Column(String(24)) 56 | Fax = Column(String(24)) 57 | 58 | 59 | class Employee(Base): 60 | __tablename__ = "employees" 61 | 62 | EmployeeID = Column(SmallInteger, primary_key=True) 63 | LastName = Column(String(20), nullable=False) 64 | FirstName = Column(String(10), nullable=False) 65 | Title = Column(String(30)) 66 | TitleOfCourtesy = Column(String(25)) 67 | BirthDate = Column(Date) 68 | HireDate = Column(Date) 69 | Address = Column(String(60)) 70 | City = Column(String(15)) 71 | Region = Column(String(15)) 72 | PostalCode = Column(String(10)) 73 | Country = Column(String(15)) 74 | HomePhone = Column(String(24)) 75 | Extension = Column(String(4)) 76 | Photo = Column(LargeBinary) 77 | Notes = Column(Text) 78 | ReportsTo = Column(SmallInteger) 79 | PhotoPath = Column(String(255)) 80 | 81 | 82 | class Employeeterritory(Base): 83 | __tablename__ = "employeeterritories" 84 | 85 | EmployeeID = Column(SmallInteger, primary_key=True, nullable=False) 86 | TerritoryID = Column(String(20), primary_key=True, nullable=False) 87 | 88 | 89 | class OrderDetail(Base): 90 | __tablename__ = "order_details" 91 | 92 | OrderID = Column(SmallInteger, primary_key=True, nullable=False) 93 | ProductID = Column(SmallInteger, primary_key=True, nullable=False) 94 | UnitPrice = Column(Float, nullable=False) 95 | Quantity = Column(SmallInteger, nullable=False) 96 | Discount = Column(Float, nullable=False) 97 | 98 | 99 | class Order(Base): 100 | __tablename__ = "orders" 101 | 102 | OrderID = Column(SmallInteger, primary_key=True) 103 | CustomerID = Column(CHAR(6)) 104 | EmployeeID = Column(SmallInteger) 105 | OrderDate = Column(Date) 106 | RequiredDate = Column(Date) 107 | ShippedDate = Column(Date) 108 | ShipVia = Column(SmallInteger) 109 | Freight = Column(Float) 110 | ShipName = Column(String(40)) 111 | ShipAddress = Column(String(60)) 112 | ShipCity = Column(String(15)) 113 | ShipRegion = Column(String(15)) 114 | ShipPostalCode = Column(String(10)) 115 | ShipCountry = Column(String(15)) 116 | 117 | 118 | class Product(Base): 119 | __tablename__ = "products" 120 | 121 | ProductID = Column(SmallInteger, primary_key=True) 122 | ProductName = Column(String(40), nullable=False) 123 | SupplierID = Column(SmallInteger) 124 | CategoryID = Column(SmallInteger) 125 | QuantityPerUnit = Column(String(20)) 126 | UnitPrice = Column(Float) 127 | UnitsInStock = Column(SmallInteger) 128 | UnitsOnOrder = Column(SmallInteger) 129 | ReorderLevel = Column(SmallInteger) 130 | Discontinued = Column(Integer, nullable=False) 131 | 132 | 133 | class Region(Base): 134 | __tablename__ = "region" 135 | 136 | RegionID = Column(SmallInteger, primary_key=True) 137 | RegionDescription = Column(CHAR(8), nullable=False) 138 | 139 | 140 | class Shipper(Base): 141 | __tablename__ = "shippers" 142 | 143 | ShipperID = Column(SmallInteger, primary_key=True) 144 | CompanyName = Column(String(40), nullable=False) 145 | Phone = Column(String(24)) 146 | 147 | 148 | class ShippersTmp(Base): 149 | __tablename__ = "shippers_tmp" 150 | 151 | ShipperID = Column(SmallInteger, primary_key=True) 152 | CompanyName = Column(String(40), nullable=False) 153 | Phone = Column(String(24)) 154 | 155 | 156 | class Supplier(Base): 157 | __tablename__ = "suppliers" 158 | 159 | SupplierID = Column(SmallInteger, primary_key=True) 160 | CompanyName = Column(String(40), nullable=False) 161 | ContactName = Column(String(30)) 162 | ContactTitle = Column(String(30)) 163 | Address = Column(String(60)) 164 | City = Column(String(15)) 165 | Region = Column(String(15)) 166 | PostalCode = Column(String(10)) 167 | Country = Column(String(15)) 168 | Phone = Column(String(24)) 169 | Fax = Column(String(24)) 170 | HomePage = Column(Text) 171 | 172 | 173 | class Territory(Base): 174 | __tablename__ = "territories" 175 | 176 | TerritoryID = Column(String(20), primary_key=True) 177 | TerritoryDescription = Column(CHAR(64), nullable=False) 178 | RegionID = Column(SmallInteger, nullable=False) 179 | 180 | 181 | t_usstates = Table( 182 | "usstates", 183 | metadata, 184 | Column("StateID", SmallInteger, nullable=False), 185 | Column("StateName", String(100)), 186 | Column("StateAbbr", String(2)), 187 | Column("StateRegion", String(50)), 188 | ) 189 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, PositiveInt, constr 2 | 3 | 4 | class Shipper(BaseModel): 5 | ShipperID: PositiveInt 6 | CompanyName: constr(max_length=40) 7 | Phone: constr(max_length=24) 8 | 9 | class Config: 10 | orm_mode = True 11 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/app/views.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from fastapi import APIRouter, Depends, HTTPException 4 | from pydantic import PositiveInt 5 | from sqlalchemy.orm import Session 6 | 7 | from . import crud, schemas 8 | from .database import get_db 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.get("/shippers/{shipper_id}", response_model=schemas.Shipper) 14 | async def get_shipper(shipper_id: PositiveInt, db: Session = Depends(get_db)): 15 | db_shipper = crud.get_shipper(db, shipper_id) 16 | if db_shipper is None: 17 | raise HTTPException(status_code=404, detail="Shipper not found") 18 | return db_shipper 19 | 20 | 21 | @router.get("/shippers", response_model=List[schemas.Shipper]) 22 | async def get_shippers(db: Session = Depends(get_db)): 23 | return crud.get_shippers(db) 24 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | target-version = ['py310'] 3 | exclude = ''' 4 | /( 5 | \ build 6 | | \ dist 7 | | \.cache 8 | | \.git 9 | | \.benchmarks 10 | | \.mypy_cache 11 | | \.pytest_cache 12 | )/ 13 | ''' 14 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==3.6.1 2 | asgiref==3.5.2 3 | click==8.1.3 4 | fastapi==0.78.0 5 | greenlet==1.1.2 6 | h11==0.13.0 7 | idna==3.3 8 | inflect==5.6.0 9 | psycopg2-binary==2.9.3 10 | pydantic==1.9.1 11 | sniffio==1.2.0 12 | sqlacodegen==2.3.0 13 | SQLAlchemy==1.4.36 14 | starlette==0.19.1 15 | typing_extensions==4.2.0 16 | uvicorn==0.17.6 17 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.3 -------------------------------------------------------------------------------- /4_T_jak_Tabela/app/setup.cfg: -------------------------------------------------------------------------------- 1 | [isort] 2 | # make isort settings Black-compatible 3 | multi_line_output=3 4 | include_trailing_comma=True 5 | force_grid_wrap=0 6 | use_parentheses=True 7 | line_length=88 8 | 9 | # other isort settings 10 | default_section=THIRDPARTY 11 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaftAcademy/da-python-level-up-dev-2022/ad7e56fbaeda28d416ac22ce63956528a94f64be/4_T_jak_Tabela/diagram.png -------------------------------------------------------------------------------- /4_T_jak_Tabela/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | postgres: 5 | image: postgres:13.0 6 | environment: 7 | POSTGRES_PASSWORD: DaftAcademy 8 | volumes: 9 | - postgres:/var/lib/postgresql/data 10 | - ./migrations:/docker-entrypoint-initdb.d 11 | - ./:/home/app/ 12 | ports: 13 | - "5555:5432" 14 | volumes: 15 | postgres: 16 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/dumps/northwind.sql.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaftAcademy/da-python-level-up-dev-2022/ad7e56fbaeda28d416ac22ce63956528a94f64be/4_T_jak_Tabela/dumps/northwind.sql.dump -------------------------------------------------------------------------------- /4_T_jak_Tabela/migrations/2021-05-07-change-bpchar-to-character.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE customercustomerdemo 2 | ALTER COLUMN "CustomerID" TYPE character(6), 3 | ALTER COLUMN "CustomerTypeID" TYPE character(6); 4 | 5 | ALTER TABLE customerdemographics 6 | ALTER COLUMN "CustomerTypeID" TYPE character(6); 7 | 8 | ALTER TABLE customers 9 | ALTER COLUMN "CustomerID" TYPE character(6); 10 | 11 | ALTER TABLE orders 12 | ALTER COLUMN "CustomerID" TYPE character(6); 13 | 14 | ALTER TABLE region 15 | ALTER COLUMN "RegionDescription" TYPE character(8); 16 | 17 | ALTER TABLE territories 18 | ALTER COLUMN "TerritoryDescription" TYPE character(64); 19 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/migrations/2021-05-10-add_sequences.sql: -------------------------------------------------------------------------------- 1 | CREATE SEQUENCE categories_CategoryID_seq OWNED BY categories."CategoryID"; 2 | SELECT setval('categories_CategoryID_seq', coalesce(max("CategoryID"), 0) + 1, false) FROM categories; 3 | ALTER TABLE categories ALTER COLUMN "CategoryID" SET DEFAULT nextval('categories_CategoryID_seq'); 4 | 5 | 6 | CREATE SEQUENCE employees_EmployeeID_seq OWNED BY employees."EmployeeID"; 7 | SELECT setval('employees_EmployeeID_seq', coalesce(max("EmployeeID"), 0) + 1, false) FROM employees; 8 | ALTER TABLE employees ALTER COLUMN "EmployeeID" SET DEFAULT nextval('employees_EmployeeID_seq'); 9 | 10 | 11 | ALTER TABLE customerdemographics ALTER COLUMN "CustomerTypeID" TYPE smallint USING "CustomerTypeID"::integer; 12 | CREATE SEQUENCE customerdemographics_CustomerTypeID_seq OWNED BY customerdemographics."CustomerTypeID"; 13 | SELECT setval('customerdemographics_CustomerTypeID_seq', coalesce(max("CustomerTypeID"), 0) + 1, false) FROM customerdemographics; 14 | ALTER TABLE customerdemographics ALTER COLUMN "CustomerTypeID" SET DEFAULT nextval('customerdemographics_CustomerTypeID_seq'); 15 | 16 | 17 | 18 | ALTER TABLE territories ALTER COLUMN "TerritoryID" TYPE int USING "TerritoryID"::integer ; 19 | ALTER TABLE employeeterritories ALTER COLUMN "TerritoryID" TYPE int USING "TerritoryID"::integer ; 20 | CREATE SEQUENCE territories_TerritoryID_seq OWNED BY territories."TerritoryID"; 21 | SELECT setval('territories_TerritoryID_seq', coalesce(max("TerritoryID"), 0) + 1, false) FROM territories; 22 | ALTER TABLE territories ALTER COLUMN "TerritoryID" SET DEFAULT nextval('territories_TerritoryID_seq'); 23 | 24 | 25 | CREATE SEQUENCE orders_OrderID_seq OWNED BY orders."OrderID"; 26 | SELECT setval('orders_OrderID_seq', coalesce(max("OrderID"), 0) + 1, false) FROM orders; 27 | ALTER TABLE orders ALTER COLUMN "OrderID" SET DEFAULT nextval('orders_OrderID_seq'); 28 | 29 | 30 | CREATE SEQUENCE products_ProductID_seq OWNED BY products."ProductID"; 31 | SELECT setval('products_ProductID_seq', coalesce(max("ProductID"), 0) + 1, false) FROM products; 32 | ALTER TABLE products ALTER COLUMN "ProductID" SET DEFAULT nextval('products_ProductID_seq'); 33 | 34 | 35 | CREATE SEQUENCE region_RegionID_seq OWNED BY region."RegionID"; 36 | SELECT setval('region_RegionID_seq', coalesce(max("RegionID"), 0) + 1, false) FROM region; 37 | ALTER TABLE region ALTER COLUMN "RegionID" SET DEFAULT nextval('region_RegionID_seq'); 38 | 39 | 40 | CREATE SEQUENCE shippers_ShipperID_seq OWNED BY shippers."ShipperID"; 41 | SELECT setval('shippers_ShipperID_seq', coalesce(max("ShipperID"), 0) + 1, false) FROM shippers; 42 | ALTER TABLE shippers ALTER COLUMN "ShipperID" SET DEFAULT nextval('shippers_ShipperID_seq'); 43 | 44 | 45 | CREATE SEQUENCE shippers_tmp_ShipperID_seq OWNED BY shippers_tmp."ShipperID"; 46 | SELECT setval('shippers_tmp_ShipperID_seq', coalesce(max("ShipperID"), 0) + 1, false) FROM shippers_tmp; 47 | ALTER TABLE shippers_tmp ALTER COLUMN "ShipperID" SET DEFAULT nextval('shippers_tmp_ShipperID_seq'); 48 | 49 | 50 | 51 | CREATE SEQUENCE suppliers_SupplierID_seq OWNED BY suppliers."SupplierID"; 52 | SELECT setval('suppliers_SupplierID_seq', coalesce(max("SupplierID"), 0) + 1, false) FROM suppliers; 53 | ALTER TABLE suppliers ALTER COLUMN "SupplierID" SET DEFAULT nextval('suppliers_SupplierID_seq'); 54 | 55 | 56 | CREATE SEQUENCE usstates_StateID_seq OWNED BY usstates."StateID"; 57 | SELECT setval('usstates_StateID_seq', coalesce(max("StateID"), 0) + 1, false) FROM usstates; 58 | ALTER TABLE usstates ALTER COLUMN "StateID" SET DEFAULT nextval('usstates_StateID_seq'); 59 | 60 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/northwind.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaftAcademy/da-python-level-up-dev-2022/ad7e56fbaeda28d416ac22ce63956528a94f64be/4_T_jak_Tabela/northwind.db -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/example_app.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from contextlib import contextmanager 3 | 4 | from fastapi import FastAPI, HTTPException 5 | from pydantic import BaseModel 6 | 7 | app = FastAPI() 8 | 9 | 10 | class Category(BaseModel): 11 | name: str 12 | 13 | 14 | @contextmanager 15 | def fetch_single_product(product_id: int): 16 | app.db_connection.row_factory = sqlite3.Row 17 | product = app.db_connection.execute( 18 | "SELECT ProductID as id, ProductName as name FROM products WHERE ProductID = :product_id", 19 | {"product_id": product_id}, 20 | ).fetchone() 21 | yield product 22 | 23 | 24 | @contextmanager 25 | def fetch_single_category(category_id: int): 26 | app.db_connection.row_factory = sqlite3.Row 27 | category = app.db_connection.execute( 28 | "SELECT CategoryID, CategoryName FROM categories WHERE CategoryID = :category_id", 29 | {"category_id": category_id}, 30 | ).fetchone() 31 | yield category 32 | 33 | 34 | @app.on_event("startup") 35 | async def startup(): 36 | app.db_connection = sqlite3.connect("northwind.db") 37 | app.db_connection.text_factory = lambda b: b.decode(errors="ignore") 38 | 39 | 40 | @app.on_event("shutdown") 41 | async def shutdown(): 42 | app.db_connection.close() 43 | 44 | 45 | @app.get("/categories") 46 | async def categories(limit: int = None, offset: int = None): 47 | app.db_connection.row_factory = sqlite3.Row 48 | categories = app.db_connection.execute( 49 | "SELECT CategoryID, CategoryName FROM categories" 50 | ).fetchall() 51 | return { 52 | "categories": [ 53 | {"id": c["CategoryID"], "name": c["CategoryName"]} for c in categories 54 | ] 55 | } 56 | 57 | 58 | @app.get("/categories/{category_id}") 59 | async def single_category(category_id: int): 60 | with fetch_single_category(category_id) as category: 61 | if not category: 62 | raise HTTPException(status_code=404, detail="Not found") 63 | return {"id": category["CategoryID"], "name": category["CategoryName"]} 64 | 65 | 66 | @app.post("/categories", status_code=201) 67 | async def add_category(category: Category): 68 | cursor = app.db_connection.execute( 69 | "INSERT INTO categories (CategoryName) VALUES (?)", (category.name,) 70 | ) 71 | app.db_connection.commit() 72 | new_category_id = cursor.lastrowid 73 | 74 | with fetch_single_category(new_category_id) as fetched_category: 75 | return { 76 | "id": fetched_category["CategoryID"], 77 | "name": fetched_category["CategoryName"], 78 | } 79 | 80 | 81 | @app.put("/categories/{category_id}") 82 | async def edit_category(category_id: int, category: Category): 83 | with fetch_single_category(category_id) as fetched_category: 84 | if not fetched_category: 85 | raise HTTPException(status_code=404, detail="Not found") 86 | 87 | cursor = app.db_connection.execute( 88 | "UPDATE categories SET CategoryName = ? WHERE CategoryID = ?", 89 | (category.name, category_id), 90 | ) 91 | app.db_connection.commit() 92 | 93 | with fetch_single_category(category_id) as fetched_category: 94 | return { 95 | "id": fetched_category["CategoryID"], 96 | "name": fetched_category["CategoryName"], 97 | } 98 | 99 | 100 | @app.delete("/categories/{category_id}") 101 | async def delete_category(category_id: int): 102 | with fetch_single_category(category_id) as fetched_category: 103 | if not fetched_category: 104 | raise HTTPException(status_code=404, detail="Not found") 105 | 106 | cursor = app.db_connection.execute( 107 | "DELETE FROM categories WHERE CategoryID = ?", (category_id,) 108 | ) 109 | app.db_connection.commit() 110 | return {"deleted": cursor.rowcount} 111 | 112 | 113 | @app.get("/employees") 114 | async def employees(limit: int = None, offset: int = None, order: str = None): 115 | if order and order not in ["last_name", "first_name", "city"]: 116 | raise HTTPException(status_code=400, detail="Invalid order") 117 | 118 | sql_query = "SELECT EmployeeID as id, LastName as last_name, FirstName as first_name, City as city FROM Employees" 119 | if order: 120 | sql_query = f"{sql_query} ORDER BY {order} ASC" 121 | if limit and offset: 122 | sql_query = f"{sql_query} LIMIT :limit OFFSET :offset" 123 | elif limit: 124 | sql_query = f"{sql_query} LIMIT :limit" 125 | 126 | app.db_connection.row_factory = sqlite3.Row 127 | employees = app.db_connection.execute( 128 | sql_query, {"order": order, "limit": limit, "offset": offset} 129 | ).fetchall() 130 | return {"employees": employees} 131 | 132 | 133 | @app.get("/products") 134 | async def products(limit: int = None, offset: int = None): 135 | app.db_connection.row_factory = sqlite3.Row 136 | products = app.db_connection.execute( 137 | "SELECT ProductID as id, ProductName as name FROM products" 138 | ).fetchall() 139 | return {"products": products} 140 | 141 | 142 | @app.get("/products/{product_id}") 143 | async def single_product(product_id: int): 144 | with fetch_single_product(product_id) as product: 145 | if not product: 146 | raise HTTPException(status_code=404, detail="Not found") 147 | return product 148 | 149 | 150 | @app.get("/products/{product_id}/orders") 151 | async def single_product_orders(product_id: int): 152 | with fetch_single_product(product_id) as product: 153 | if not product: 154 | raise HTTPException(status_code=404, detail="Not found") 155 | 156 | orders = app.db_connection.execute( 157 | """ 158 | Select 159 | orders.OrderID as id, 160 | customers.CompanyName as customer, 161 | orddet.Quantity as quantity , 162 | ROUND(((orddet.Quantity * orddet.UnitPrice) - (orddet.Quantity * orddet.UnitPrice * orddet.Discount)), 2) as total_price 163 | from Orders 164 | join customers on orders.CustomerID = customers.CustomerID 165 | join 'Order Details' as orddet on orders.OrderID = orddet.OrderID and orddet.ProductID = :product_id 166 | """, 167 | {"product_id": product_id}, 168 | ).fetchall() 169 | return {"orders": orders} 170 | 171 | 172 | @app.get("/products_extended") 173 | async def products_categories(): 174 | app.db_connection.row_factory = sqlite3.Row 175 | data = app.db_connection.execute( 176 | """ 177 | SELECT ProductID as id, ProductName as name, categories.CategoryName as category, suppliers.CompanyName as supplier FROM products 178 | JOIN categories ON products.CategoryID = categories.CategoryID 179 | JOIN suppliers on products.SupplierID = suppliers.SupplierID 180 | """ 181 | ).fetchall() 182 | return {"products_extended": data} 183 | 184 | 185 | @app.get("/customers") 186 | async def customers(limit: int = None, offset: int = None): 187 | sql_query = "SELECT CustomerID as id, CompanyName as name, (Address || ' ' || PostalCode || ' ' || City || ' ' || Country) as full_address FROM customers" 188 | if limit and offset: 189 | sql_query = f"{sql_query} LIMIT :limit OFFSET :offset" 190 | elif limit: 191 | sql_query = f"{sql_query} LIMIT :limit" 192 | 193 | app.db_connection.row_factory = sqlite3.Row 194 | customers = app.db_connection.execute( 195 | sql_query, {"limit": limit, "offset": offset} 196 | ).fetchall() 197 | return {"customers": customers} 198 | 199 | 200 | @app.get("/customers/{customer_id}") 201 | async def single_customer(customer_id: str): 202 | app.db_connection.row_factory = sqlite3.Row 203 | customer = app.db_connection.execute( 204 | """ 205 | SELECT CustomerID as id, CompanyName as name, (City || ' ' || PostalCode || ' ' || Country) as address FROM customers 206 | WHERE CustomerID = :customer_id 207 | """, 208 | {"customer_id": customer_id}, 209 | ).fetchone() 210 | if not customer: 211 | raise HTTPException(status_code=404, detail="Not found") 212 | return customer 213 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_1_test_1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/categories") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "categories": [ 15 | {"id": 1, "name": "Beverages"}, 16 | {"id": 2, "name": "Condiments"}, 17 | {"id": 3, "name": "Confections"}, 18 | {"id": 4, "name": "Dairy Products"}, 19 | {"id": 5, "name": "Grains/Cereals"}, 20 | {"id": 6, "name": "Meat/Poultry"}, 21 | {"id": 7, "name": "Produce"}, 22 | {"id": 8, "name": "Seafood"}, 23 | ] 24 | } 25 | 26 | def test_status_code(self): 27 | self.assertEqual(self._response.status_code, 200) 28 | 29 | def test_response(self): 30 | self.assertEqual( 31 | self._response.json(), 32 | self._expected_response, 33 | ) 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_1_test_2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/customers") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "customers": [ 15 | { 16 | "id": "ALFKI", 17 | "name": "Alfreds Futterkiste", 18 | "full_address": "Obere Str. 57 12209 Berlin Germany", 19 | }, 20 | { 21 | "id": "ANATR", 22 | "name": "Ana Trujillo Emparedados y helados", 23 | "full_address": "Avda. de la Constitucin 2222 05021 Mxico D.F. Mexico", 24 | }, 25 | { 26 | "id": "ANTON", 27 | "name": "Antonio Moreno Taquera", 28 | "full_address": "Mataderos 2312 05023 Mxico D.F. Mexico", 29 | }, 30 | { 31 | "id": "AROUT", 32 | "name": "Around the Horn", 33 | "full_address": "120 Hanover Sq. WA1 1DP London UK", 34 | }, 35 | { 36 | "id": "BERGS", 37 | "name": "Berglunds snabbkp", 38 | "full_address": "Berguvsvgen 8 S-958 22 Lule Sweden", 39 | }, 40 | { 41 | "id": "BLAUS", 42 | "name": "Blauer See Delikatessen", 43 | "full_address": "Forsterstr. 57 68306 Mannheim Germany", 44 | }, 45 | { 46 | "id": "BLONP", 47 | "name": "Blondesddsl pre et fils", 48 | "full_address": "24, place Klber 67000 Strasbourg France", 49 | }, 50 | { 51 | "id": "BOLID", 52 | "name": "Blido Comidas preparadas", 53 | "full_address": "C/ Araquil, 67 28023 Madrid Spain", 54 | }, 55 | { 56 | "id": "BONAP", 57 | "name": "Bon app'", 58 | "full_address": "12, rue des Bouchers 13008 Marseille France", 59 | }, 60 | { 61 | "id": "BOTTM", 62 | "name": "Bottom-Dollar Markets", 63 | "full_address": "23 Tsawassen Blvd. T2F 8M4 Tsawassen Canada", 64 | }, 65 | { 66 | "id": "BSBEV", 67 | "name": "B's Beverages", 68 | "full_address": "Fauntleroy Circus EC2 5NT London UK", 69 | }, 70 | { 71 | "id": "CACTU", 72 | "name": "Cactus Comidas para llevar", 73 | "full_address": "Cerrito 333 1010 Buenos Aires Argentina", 74 | }, 75 | { 76 | "id": "CENTC", 77 | "name": "Centro comercial Moctezuma", 78 | "full_address": "Sierras de Granada 9993 05022 Mxico D.F. Mexico", 79 | }, 80 | { 81 | "id": "CHOPS", 82 | "name": "Chop-suey Chinese", 83 | "full_address": "Hauptstr. 29 3012 Bern Switzerland", 84 | }, 85 | { 86 | "id": "COMMI", 87 | "name": "Comrcio Mineiro", 88 | "full_address": "Av. dos Lusadas, 23 05432-043 Sao Paulo Brazil", 89 | }, 90 | { 91 | "id": "CONSH", 92 | "name": "Consolidated Holdings", 93 | "full_address": "Berkeley Gardens 12 Brewery WX1 6LT London UK", 94 | }, 95 | { 96 | "id": "DRACD", 97 | "name": "Drachenblut Delikatessen", 98 | "full_address": "Walserweg 21 52066 Aachen Germany", 99 | }, 100 | { 101 | "id": "DUMON", 102 | "name": "Du monde entier", 103 | "full_address": "67, rue des Cinquante Otages 44000 Nantes France", 104 | }, 105 | { 106 | "id": "EASTC", 107 | "name": "Eastern Connection", 108 | "full_address": "35 King George WX3 6FW London UK", 109 | }, 110 | { 111 | "id": "ERNSH", 112 | "name": "Ernst Handel", 113 | "full_address": "Kirchgasse 6 8010 Graz Austria", 114 | }, 115 | { 116 | "id": "FAMIA", 117 | "name": "Familia Arquibaldo", 118 | "full_address": "Rua Ors, 92 05442-030 Sao Paulo Brazil", 119 | }, 120 | { 121 | "id": "FISSA", 122 | "name": "FISSA Fabrica Inter. Salchichas S.A.", 123 | "full_address": "C/ Moralzarzal, 86 28034 Madrid Spain", 124 | }, 125 | { 126 | "id": "FOLIG", 127 | "name": "Folies gourmandes", 128 | "full_address": "184, chausse de Tournai 59000 Lille France", 129 | }, 130 | { 131 | "id": "FOLKO", 132 | "name": "Folk och f HB", 133 | "full_address": "kergatan 24 S-844 67 Brcke Sweden", 134 | }, 135 | { 136 | "id": "FRANK", 137 | "name": "Frankenversand", 138 | "full_address": "Berliner Platz 43 80805 Mnchen Germany", 139 | }, 140 | { 141 | "id": "FRANR", 142 | "name": "France restauration", 143 | "full_address": "54, rue Royale 44000 Nantes France", 144 | }, 145 | { 146 | "id": "FRANS", 147 | "name": "Franchi S.p.A.", 148 | "full_address": "Via Monte Bianco 34 10100 Torino Italy", 149 | }, 150 | { 151 | "id": "FURIB", 152 | "name": "Furia Bacalhau e Frutos do Mar", 153 | "full_address": "Jardim das rosas n. 32 1675 Lisboa Portugal", 154 | }, 155 | { 156 | "id": "GALED", 157 | "name": "Galera del gastrnomo", 158 | "full_address": "Rambla de Catalua, 23 08022 Barcelona Spain", 159 | }, 160 | { 161 | "id": "GODOS", 162 | "name": "Godos Cocina Tpica", 163 | "full_address": "C/ Romero, 33 41101 Sevilla Spain", 164 | }, 165 | { 166 | "id": "GOURL", 167 | "name": "Gourmet Lanchonetes", 168 | "full_address": "Av. Brasil, 442 04876-786 Campinas Brazil", 169 | }, 170 | { 171 | "id": "GREAL", 172 | "name": "Great Lakes Food Market", 173 | "full_address": "2732 Baker Blvd. 97403 Eugene USA", 174 | }, 175 | { 176 | "id": "GROSR", 177 | "name": "GROSELLA-Restaurante", 178 | "full_address": "5 Ave. Los Palos Grandes 1081 Caracas Venezuela", 179 | }, 180 | { 181 | "id": "HANAR", 182 | "name": "Hanari Carnes", 183 | "full_address": "Rua do Pao, 67 05454-876 Rio de Janeiro Brazil", 184 | }, 185 | { 186 | "id": "HILAA", 187 | "name": "HILARION-Abastos", 188 | "full_address": "Carrera 22 con Ave. Carlos Soublette #8-35 5022 San Cristbal Venezuela", 189 | }, 190 | { 191 | "id": "HUNGC", 192 | "name": "Hungry Coyote Import Store", 193 | "full_address": "City Center Plaza 516 Main St. 97827 Elgin USA", 194 | }, 195 | { 196 | "id": "HUNGO", 197 | "name": "Hungry Owl All-Night Grocers", 198 | "full_address": None, 199 | }, 200 | { 201 | "id": "ISLAT", 202 | "name": "Island Trading", 203 | "full_address": "Garden House Crowther Way PO31 7PJ Cowes UK", 204 | }, 205 | { 206 | "id": "KOENE", 207 | "name": "Kniglich Essen", 208 | "full_address": "Maubelstr. 90 14776 Brandenburg Germany", 209 | }, 210 | { 211 | "id": "LACOR", 212 | "name": "La corne d'abondance", 213 | "full_address": "67, avenue de l'Europe 78000 Versailles France", 214 | }, 215 | { 216 | "id": "LAMAI", 217 | "name": "La maison d'Asie", 218 | "full_address": "1 rue Alsace-Lorraine 31000 Toulouse France", 219 | }, 220 | { 221 | "id": "LAUGB", 222 | "name": "Laughing Bacchus Wine Cellars", 223 | "full_address": "1900 Oak St. V3F 2K1 Vancouver Canada", 224 | }, 225 | { 226 | "id": "LAZYK", 227 | "name": "Lazy K Kountry Store", 228 | "full_address": "12 Orchestra Terrace 99362 Walla Walla USA", 229 | }, 230 | { 231 | "id": "LEHMS", 232 | "name": "Lehmanns Marktstand", 233 | "full_address": "Magazinweg 7 60528 Frankfurt a.M. Germany", 234 | }, 235 | { 236 | "id": "LETSS", 237 | "name": "Let's Stop N Shop", 238 | "full_address": "87 Polk St. Suite 5 94117 San Francisco USA", 239 | }, 240 | { 241 | "id": "LILAS", 242 | "name": "LILA-Supermercado", 243 | "full_address": "Carrera 52 con Ave. Bolvar #65-98 Llano Largo 3508 Barquisimeto Venezuela", 244 | }, 245 | { 246 | "id": "LINOD", 247 | "name": "LINO-Delicateses", 248 | "full_address": "Ave. 5 de Mayo Porlamar 4980 I. de Margarita Venezuela", 249 | }, 250 | { 251 | "id": "LONEP", 252 | "name": "Lonesome Pine Restaurant", 253 | "full_address": "89 Chiaroscuro Rd. 97219 Portland USA", 254 | }, 255 | { 256 | "id": "MAGAA", 257 | "name": "Magazzini Alimentari Riuniti", 258 | "full_address": "Via Ludovico il Moro 22 24100 Bergamo Italy", 259 | }, 260 | { 261 | "id": "MAISD", 262 | "name": "Maison Dewey", 263 | "full_address": "Rue Joseph-Bens 532 B-1180 Bruxelles Belgium", 264 | }, 265 | { 266 | "id": "MEREP", 267 | "name": "Mre Paillarde", 268 | "full_address": "43 rue St. Laurent H1J 1C3 Montral Canada", 269 | }, 270 | { 271 | "id": "MORGK", 272 | "name": "Morgenstern Gesundkost", 273 | "full_address": "Heerstr. 22 04179 Leipzig Germany", 274 | }, 275 | { 276 | "id": "NORTS", 277 | "name": "North/South", 278 | "full_address": "South House 300 Queensbridge SW7 1RZ London UK", 279 | }, 280 | { 281 | "id": "OCEAN", 282 | "name": "Ocano Atlntico Ltda.", 283 | "full_address": "Ing. Gustavo Moncada 8585 Piso 20-A 1010 Buenos Aires Argentina", 284 | }, 285 | { 286 | "id": "OLDWO", 287 | "name": "Old World Delicatessen", 288 | "full_address": "2743 Bering St. 99508 Anchorage USA", 289 | }, 290 | { 291 | "id": "OTTIK", 292 | "name": "Ottilies Kseladen", 293 | "full_address": "Mehrheimerstr. 369 50739 Kln Germany", 294 | }, 295 | { 296 | "id": "PARIS", 297 | "name": "Paris spcialits", 298 | "full_address": "265, boulevard Charonne 75012 Paris France", 299 | }, 300 | { 301 | "id": "PERIC", 302 | "name": "Pericles Comidas clsicas", 303 | "full_address": "Calle Dr. Jorge Cash 321 05033 Mxico D.F. Mexico", 304 | }, 305 | { 306 | "id": "PICCO", 307 | "name": "Piccolo und mehr", 308 | "full_address": "Geislweg 14 5020 Salzburg Austria", 309 | }, 310 | { 311 | "id": "PRINI", 312 | "name": "Princesa Isabel Vinhos", 313 | "full_address": "Estrada da sade n. 58 1756 Lisboa Portugal", 314 | }, 315 | { 316 | "id": "QUEDE", 317 | "name": "Que Delcia", 318 | "full_address": "Rua da Panificadora, 12 02389-673 Rio de Janeiro Brazil", 319 | }, 320 | { 321 | "id": "QUEEN", 322 | "name": "Queen Cozinha", 323 | "full_address": "Alameda dos Canrios, 891 05487-020 Sao Paulo Brazil", 324 | }, 325 | { 326 | "id": "QUICK", 327 | "name": "QUICK-Stop", 328 | "full_address": "Taucherstrae 10 01307 Cunewalde Germany", 329 | }, 330 | { 331 | "id": "RANCH", 332 | "name": "Rancho grande", 333 | "full_address": "Av. del Libertador 900 1010 Buenos Aires Argentina", 334 | }, 335 | { 336 | "id": "RATTC", 337 | "name": "Rattlesnake Canyon Grocery", 338 | "full_address": "2817 Milton Dr. 87110 Albuquerque USA", 339 | }, 340 | { 341 | "id": "REGGC", 342 | "name": "Reggiani Caseifici", 343 | "full_address": "Strada Provinciale 124 42100 Reggio Emilia Italy", 344 | }, 345 | { 346 | "id": "RICAR", 347 | "name": "Ricardo Adocicados", 348 | "full_address": "Av. Copacabana, 267 02389-890 Rio de Janeiro Brazil", 349 | }, 350 | { 351 | "id": "RICSU", 352 | "name": "Richter Supermarkt", 353 | "full_address": "Grenzacherweg 237 1203 Genve Switzerland", 354 | }, 355 | { 356 | "id": "ROMEY", 357 | "name": "Romero y tomillo", 358 | "full_address": "Gran Va, 1 28001 Madrid Spain", 359 | }, 360 | { 361 | "id": "SANTG", 362 | "name": "Sant Gourmet", 363 | "full_address": "Erling Skakkes gate 78 4110 Stavern Norway", 364 | }, 365 | { 366 | "id": "SAVEA", 367 | "name": "Save-a-lot Markets", 368 | "full_address": "187 Suffolk Ln. 83720 Boise USA", 369 | }, 370 | { 371 | "id": "SEVES", 372 | "name": "Seven Seas Imports", 373 | "full_address": "90 Wadhurst Rd. OX15 4NB London UK", 374 | }, 375 | { 376 | "id": "SIMOB", 377 | "name": "Simons bistro", 378 | "full_address": "Vinbltet 34 1734 Kobenhavn Denmark", 379 | }, 380 | { 381 | "id": "SPECD", 382 | "name": "Spcialits du monde", 383 | "full_address": "25, rue Lauriston 75016 Paris France", 384 | }, 385 | { 386 | "id": "SPLIR", 387 | "name": "Split Rail Beer & Ale", 388 | "full_address": "P.O. Box 555 82520 Lander USA", 389 | }, 390 | { 391 | "id": "SUPRD", 392 | "name": "Suprmes dlices", 393 | "full_address": "Boulevard Tirou, 255 B-6000 Charleroi Belgium", 394 | }, 395 | { 396 | "id": "THEBI", 397 | "name": "The Big Cheese", 398 | "full_address": "89 Jefferson Way Suite 2 97201 Portland USA", 399 | }, 400 | { 401 | "id": "THECR", 402 | "name": "The Cracker Box", 403 | "full_address": "55 Grizzly Peak Rd. 59801 Butte USA", 404 | }, 405 | { 406 | "id": "TOMSP", 407 | "name": "Toms Spezialitten", 408 | "full_address": "Luisenstr. 48 44087 Mnster Germany", 409 | }, 410 | { 411 | "id": "TORTU", 412 | "name": "Tortuga Restaurante", 413 | "full_address": "Avda. Azteca 123 05033 Mxico D.F. Mexico", 414 | }, 415 | { 416 | "id": "TRADH", 417 | "name": "Tradio Hipermercados", 418 | "full_address": "Av. Ins de Castro, 414 05634-030 Sao Paulo Brazil", 419 | }, 420 | { 421 | "id": "TRAIH", 422 | "name": "Trail's Head Gourmet Provisioners", 423 | "full_address": "722 DaVinci Blvd. 98034 Kirkland USA", 424 | }, 425 | { 426 | "id": "VAFFE", 427 | "name": "Vaffeljernet", 428 | "full_address": "Smagsloget 45 8200 rhus Denmark", 429 | }, 430 | {"id": "Val2 ", "name": "IT", "full_address": None}, 431 | {"id": "VALON", "name": "IT", "full_address": None}, 432 | { 433 | "id": "VICTE", 434 | "name": "Victuailles en stock", 435 | "full_address": "2, rue du Commerce 69004 Lyon France", 436 | }, 437 | { 438 | "id": "VINET", 439 | "name": "Vins et alcools Chevalier", 440 | "full_address": "59 rue de l'Abbaye 51100 Reims France", 441 | }, 442 | { 443 | "id": "WANDK", 444 | "name": "Die Wandernde Kuh", 445 | "full_address": "Adenauerallee 900 70563 Stuttgart Germany", 446 | }, 447 | { 448 | "id": "WARTH", 449 | "name": "Wartian Herkku", 450 | "full_address": "Torikatu 38 90110 Oulu Finland", 451 | }, 452 | { 453 | "id": "WELLI", 454 | "name": "Wellington Importadora", 455 | "full_address": "Rua do Mercado, 12 08737-363 Resende Brazil", 456 | }, 457 | { 458 | "id": "WHITC", 459 | "name": "White Clover Markets", 460 | "full_address": "305 - 14th Ave. S. Suite 3B 98128 Seattle USA", 461 | }, 462 | { 463 | "id": "WILMK", 464 | "name": "Wilman Kala", 465 | "full_address": "Keskuskatu 45 21240 Helsinki Finland", 466 | }, 467 | { 468 | "id": "WOLZA", 469 | "name": "Wolski Zajazd", 470 | "full_address": "ul. Filtrowa 68 01-012 Warszawa Poland", 471 | }, 472 | ] 473 | } 474 | 475 | def test_status_code(self): 476 | self.assertEqual(self._response.status_code, 200) 477 | 478 | def test_response(self): 479 | self.assertEqual( 480 | self._response.json(), 481 | self._expected_response, 482 | ) 483 | 484 | 485 | if __name__ == "__main__": 486 | unittest.main() 487 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_2_test_1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/1") 12 | self._response = requests.get(get_path) 13 | self._expected_response = {"id": 1, "name": "Chai"} 14 | 15 | def test_status_code(self): 16 | self.assertEqual(self._response.status_code, 200) 17 | 18 | def test_response(self): 19 | self.assertEqual( 20 | self._response.json(), 21 | self._expected_response, 22 | ) 23 | 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_2_test_2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/4") 12 | self._response = requests.get(get_path) 13 | self._expected_response = {"id": 4, "name": "Chef Anton's Cajun Seasoning"} 14 | 15 | def test_status_code(self): 16 | self.assertEqual(self._response.status_code, 200) 17 | 18 | def test_response(self): 19 | self.assertEqual( 20 | self._response.json(), 21 | self._expected_response, 22 | ) 23 | 24 | 25 | if __name__ == "__main__": 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_2_test_3.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/4444") 12 | self._response = requests.get(get_path) 13 | 14 | def test_status_code(self): 15 | self.assertEqual(self._response.status_code, 404) 16 | 17 | 18 | if __name__ == "__main__": 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_3_test_1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/employees?limit=3") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "employees": [ 15 | { 16 | "id": 1, 17 | "last_name": "Davolio", 18 | "first_name": "Nancy", 19 | "city": "Seattle", 20 | }, 21 | { 22 | "id": 2, 23 | "last_name": "Fuller", 24 | "first_name": "Andrew", 25 | "city": "Tacoma", 26 | }, 27 | { 28 | "id": 3, 29 | "last_name": "Leverling", 30 | "first_name": "Janet", 31 | "city": "Kirkland", 32 | }, 33 | ] 34 | } 35 | 36 | def test_status_code(self): 37 | self.assertEqual(self._response.status_code, 200) 38 | 39 | def test_response(self): 40 | self.assertEqual( 41 | self._response.json(), 42 | self._expected_response, 43 | ) 44 | 45 | 46 | if __name__ == "__main__": 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_3_test_2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/employees?limit=3&offset=2") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "employees": [ 15 | { 16 | "id": 3, 17 | "last_name": "Leverling", 18 | "first_name": "Janet", 19 | "city": "Kirkland", 20 | }, 21 | { 22 | "id": 4, 23 | "last_name": "Peacock", 24 | "first_name": "Margaret", 25 | "city": "Redmond", 26 | }, 27 | { 28 | "id": 5, 29 | "last_name": "Buchanan", 30 | "first_name": "Steven", 31 | "city": "London", 32 | }, 33 | ] 34 | } 35 | 36 | def test_status_code(self): 37 | self.assertEqual(self._response.status_code, 200) 38 | 39 | def test_response(self): 40 | self.assertEqual( 41 | self._response.json(), 42 | self._expected_response, 43 | ) 44 | 45 | 46 | if __name__ == "__main__": 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_3_test_3.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin( 12 | AppClass.APP_URL, "/employees?limit=4&offset=2&order=city" 13 | ) 14 | self._response = requests.get(get_path) 15 | self._expected_response = { 16 | "employees": [ 17 | { 18 | "id": 6, 19 | "last_name": "Suyama", 20 | "first_name": "Michael", 21 | "city": "London", 22 | }, 23 | { 24 | "id": 7, 25 | "last_name": "King", 26 | "first_name": "Robert", 27 | "city": "London", 28 | }, 29 | { 30 | "id": 9, 31 | "last_name": "Dodsworth", 32 | "first_name": "Anne", 33 | "city": "London", 34 | }, 35 | { 36 | "id": 4, 37 | "last_name": "Peacock", 38 | "first_name": "Margaret", 39 | "city": "Redmond", 40 | }, 41 | ] 42 | } 43 | 44 | def test_status_code(self): 45 | self.assertEqual(self._response.status_code, 200) 46 | 47 | def test_response(self): 48 | self.assertEqual( 49 | self._response.json(), 50 | self._expected_response, 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_3_test_4.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin( 12 | AppClass.APP_URL, "/employees?limit=3&offset=3&order=first_name" 13 | ) 14 | self._response = requests.get(get_path) 15 | self._expected_response = { 16 | "employees": [ 17 | { 18 | "id": 8, 19 | "last_name": "Callahan", 20 | "first_name": "Laura", 21 | "city": "Seattle", 22 | }, 23 | { 24 | "id": 4, 25 | "last_name": "Peacock", 26 | "first_name": "Margaret", 27 | "city": "Redmond", 28 | }, 29 | { 30 | "id": 6, 31 | "last_name": "Suyama", 32 | "first_name": "Michael", 33 | "city": "London", 34 | }, 35 | ] 36 | } 37 | 38 | def test_status_code(self): 39 | self.assertEqual(self._response.status_code, 200) 40 | 41 | def test_response(self): 42 | self.assertEqual( 43 | self._response.json(), 44 | self._expected_response, 45 | ) 46 | 47 | 48 | if __name__ == "__main__": 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_3_test_5.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/employees?order=invalid") 12 | self._response = requests.get(get_path) 13 | 14 | def test_status_code(self): 15 | self.assertEqual(self._response.status_code, 400) 16 | 17 | 18 | if __name__ == "__main__": 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_3_test_6.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin( 12 | AppClass.APP_URL, "/employees?limit=4&offset=4&order=last_name" 13 | ) 14 | self._response = requests.get(get_path) 15 | self._expected_response = { 16 | "employees": [ 17 | { 18 | "id": 2, 19 | "last_name": "Fuller", 20 | "first_name": "Andrew", 21 | "city": "Tacoma", 22 | }, 23 | { 24 | "id": 7, 25 | "last_name": "King", 26 | "first_name": "Robert", 27 | "city": "London", 28 | }, 29 | { 30 | "id": 3, 31 | "last_name": "Leverling", 32 | "first_name": "Janet", 33 | "city": "Kirkland", 34 | }, 35 | { 36 | "id": 4, 37 | "last_name": "Peacock", 38 | "first_name": "Margaret", 39 | "city": "Redmond", 40 | }, 41 | ] 42 | } 43 | 44 | def test_status_code(self): 45 | self.assertEqual(self._response.status_code, 200) 46 | 47 | def test_response(self): 48 | self.assertEqual( 49 | self._response.json(), 50 | self._expected_response, 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_4_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products_extended") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "products_extended": [ 15 | { 16 | "id": 1, 17 | "name": "Chai", 18 | "category": "Beverages", 19 | "supplier": "Exotic Liquids", 20 | }, 21 | { 22 | "id": 2, 23 | "name": "Chang", 24 | "category": "Beverages", 25 | "supplier": "Exotic Liquids", 26 | }, 27 | { 28 | "id": 3, 29 | "name": "Aniseed Syrup", 30 | "category": "Condiments", 31 | "supplier": "Exotic Liquids", 32 | }, 33 | { 34 | "id": 4, 35 | "name": "Chef Anton's Cajun Seasoning", 36 | "category": "Condiments", 37 | "supplier": "New Orleans Cajun Delights", 38 | }, 39 | { 40 | "id": 5, 41 | "name": "Chef Anton's Gumbo Mix", 42 | "category": "Condiments", 43 | "supplier": "New Orleans Cajun Delights", 44 | }, 45 | { 46 | "id": 6, 47 | "name": "Grandma's Boysenberry Spread", 48 | "category": "Condiments", 49 | "supplier": "Grandma Kelly's Homestead", 50 | }, 51 | { 52 | "id": 7, 53 | "name": "Uncle Bob's Organic Dried Pears", 54 | "category": "Produce", 55 | "supplier": "Grandma Kelly's Homestead", 56 | }, 57 | { 58 | "id": 8, 59 | "name": "Northwoods Cranberry Sauce", 60 | "category": "Condiments", 61 | "supplier": "Grandma Kelly's Homestead", 62 | }, 63 | { 64 | "id": 9, 65 | "name": "Mishi Kobe Niku", 66 | "category": "Meat/Poultry", 67 | "supplier": "Tokyo Traders", 68 | }, 69 | { 70 | "id": 10, 71 | "name": "Ikura", 72 | "category": "Seafood", 73 | "supplier": "Tokyo Traders", 74 | }, 75 | { 76 | "id": 11, 77 | "name": "Queso Cabrales", 78 | "category": "Dairy Products", 79 | "supplier": "Cooperativa de Quesos 'Las Cabras'", 80 | }, 81 | { 82 | "id": 12, 83 | "name": "Queso Manchego La Pastora", 84 | "category": "Dairy Products", 85 | "supplier": "Cooperativa de Quesos 'Las Cabras'", 86 | }, 87 | { 88 | "id": 13, 89 | "name": "Konbu", 90 | "category": "Seafood", 91 | "supplier": "Mayumi's", 92 | }, 93 | { 94 | "id": 14, 95 | "name": "Tofu", 96 | "category": "Produce", 97 | "supplier": "Mayumi's", 98 | }, 99 | { 100 | "id": 15, 101 | "name": "Genen Shouyu", 102 | "category": "Condiments", 103 | "supplier": "Mayumi's", 104 | }, 105 | { 106 | "id": 16, 107 | "name": "Pavlova", 108 | "category": "Confections", 109 | "supplier": "Pavlova, Ltd.", 110 | }, 111 | { 112 | "id": 17, 113 | "name": "Alice Mutton", 114 | "category": "Meat/Poultry", 115 | "supplier": "Pavlova, Ltd.", 116 | }, 117 | { 118 | "id": 18, 119 | "name": "Carnarvon Tigers", 120 | "category": "Seafood", 121 | "supplier": "Pavlova, Ltd.", 122 | }, 123 | { 124 | "id": 19, 125 | "name": "Teatime Chocolate Biscuits", 126 | "category": "Confections", 127 | "supplier": "Specialty Biscuits, Ltd.", 128 | }, 129 | { 130 | "id": 20, 131 | "name": "Sir Rodney's Marmalade", 132 | "category": "Confections", 133 | "supplier": "Specialty Biscuits, Ltd.", 134 | }, 135 | { 136 | "id": 21, 137 | "name": "Sir Rodney's Scones", 138 | "category": "Confections", 139 | "supplier": "Specialty Biscuits, Ltd.", 140 | }, 141 | { 142 | "id": 22, 143 | "name": "Gustaf's Knckebrd", 144 | "category": "Grains/Cereals", 145 | "supplier": "PB Knckebrd AB", 146 | }, 147 | { 148 | "id": 23, 149 | "name": "Tunnbrd", 150 | "category": "Grains/Cereals", 151 | "supplier": "PB Knckebrd AB", 152 | }, 153 | { 154 | "id": 24, 155 | "name": "Guaran Fantstica", 156 | "category": "Beverages", 157 | "supplier": "Refrescos Americanas LTDA", 158 | }, 159 | { 160 | "id": 25, 161 | "name": "NuNuCa Nu-Nougat-Creme", 162 | "category": "Confections", 163 | "supplier": "Heli Swaren GmbH & Co. KG", 164 | }, 165 | { 166 | "id": 26, 167 | "name": "Gumbr Gummibrchen", 168 | "category": "Confections", 169 | "supplier": "Heli Swaren GmbH & Co. KG", 170 | }, 171 | { 172 | "id": 27, 173 | "name": "Schoggi Schokolade", 174 | "category": "Confections", 175 | "supplier": "Heli Swaren GmbH & Co. KG", 176 | }, 177 | { 178 | "id": 28, 179 | "name": "Rssle Sauerkraut", 180 | "category": "Produce", 181 | "supplier": "Plutzer Lebensmittelgromrkte AG", 182 | }, 183 | { 184 | "id": 29, 185 | "name": "Thringer Rostbratwurst", 186 | "category": "Meat/Poultry", 187 | "supplier": "Plutzer Lebensmittelgromrkte AG", 188 | }, 189 | { 190 | "id": 30, 191 | "name": "Nord-Ost Matjeshering", 192 | "category": "Seafood", 193 | "supplier": "Nord-Ost-Fisch Handelsgesellschaft mbH", 194 | }, 195 | { 196 | "id": 31, 197 | "name": "Gorgonzola Telino", 198 | "category": "Dairy Products", 199 | "supplier": "Formaggi Fortini s.r.l.", 200 | }, 201 | { 202 | "id": 32, 203 | "name": "Mascarpone Fabioli", 204 | "category": "Dairy Products", 205 | "supplier": "Formaggi Fortini s.r.l.", 206 | }, 207 | { 208 | "id": 33, 209 | "name": "Geitost", 210 | "category": "Dairy Products", 211 | "supplier": "Norske Meierier", 212 | }, 213 | { 214 | "id": 34, 215 | "name": "Sasquatch Ale", 216 | "category": "Beverages", 217 | "supplier": "Bigfoot Breweries", 218 | }, 219 | { 220 | "id": 35, 221 | "name": "Steeleye Stout", 222 | "category": "Beverages", 223 | "supplier": "Bigfoot Breweries", 224 | }, 225 | { 226 | "id": 36, 227 | "name": "Inlagd Sill", 228 | "category": "Seafood", 229 | "supplier": "Svensk Sjfda AB", 230 | }, 231 | { 232 | "id": 37, 233 | "name": "Gravad lax", 234 | "category": "Seafood", 235 | "supplier": "Svensk Sjfda AB", 236 | }, 237 | { 238 | "id": 38, 239 | "name": "Cte de Blaye", 240 | "category": "Beverages", 241 | "supplier": "Aux joyeux ecclsiastiques", 242 | }, 243 | { 244 | "id": 39, 245 | "name": "Chartreuse verte", 246 | "category": "Beverages", 247 | "supplier": "Aux joyeux ecclsiastiques", 248 | }, 249 | { 250 | "id": 40, 251 | "name": "Boston Crab Meat", 252 | "category": "Seafood", 253 | "supplier": "New England Seafood Cannery", 254 | }, 255 | { 256 | "id": 41, 257 | "name": "Jack's New England Clam Chowder", 258 | "category": "Seafood", 259 | "supplier": "New England Seafood Cannery", 260 | }, 261 | { 262 | "id": 42, 263 | "name": "Singaporean Hokkien Fried Mee", 264 | "category": "Grains/Cereals", 265 | "supplier": "Leka Trading", 266 | }, 267 | { 268 | "id": 43, 269 | "name": "Ipoh Coffee", 270 | "category": "Beverages", 271 | "supplier": "Leka Trading", 272 | }, 273 | { 274 | "id": 44, 275 | "name": "Gula Malacca", 276 | "category": "Condiments", 277 | "supplier": "Leka Trading", 278 | }, 279 | { 280 | "id": 45, 281 | "name": "Rogede sild", 282 | "category": "Seafood", 283 | "supplier": "Lyngbysild", 284 | }, 285 | { 286 | "id": 46, 287 | "name": "Spegesild", 288 | "category": "Seafood", 289 | "supplier": "Lyngbysild", 290 | }, 291 | { 292 | "id": 47, 293 | "name": "Zaanse koeken", 294 | "category": "Confections", 295 | "supplier": "Zaanse Snoepfabriek", 296 | }, 297 | { 298 | "id": 48, 299 | "name": "Chocolade", 300 | "category": "Confections", 301 | "supplier": "Zaanse Snoepfabriek", 302 | }, 303 | { 304 | "id": 49, 305 | "name": "Maxilaku", 306 | "category": "Confections", 307 | "supplier": "Karkki Oy", 308 | }, 309 | { 310 | "id": 50, 311 | "name": "Valkoinen suklaa", 312 | "category": "Confections", 313 | "supplier": "Karkki Oy", 314 | }, 315 | { 316 | "id": 51, 317 | "name": "Manjimup Dried Apples", 318 | "category": "Produce", 319 | "supplier": "G'day, Mate", 320 | }, 321 | { 322 | "id": 52, 323 | "name": "Filo Mix", 324 | "category": "Grains/Cereals", 325 | "supplier": "G'day, Mate", 326 | }, 327 | { 328 | "id": 53, 329 | "name": "Perth Pasties", 330 | "category": "Meat/Poultry", 331 | "supplier": "G'day, Mate", 332 | }, 333 | { 334 | "id": 54, 335 | "name": "Tourtire", 336 | "category": "Meat/Poultry", 337 | "supplier": "Ma Maison", 338 | }, 339 | { 340 | "id": 55, 341 | "name": "Pt chinois", 342 | "category": "Meat/Poultry", 343 | "supplier": "Ma Maison", 344 | }, 345 | { 346 | "id": 56, 347 | "name": "Gnocchi di nonna Alice", 348 | "category": "Grains/Cereals", 349 | "supplier": "Pasta Buttini s.r.l.", 350 | }, 351 | { 352 | "id": 57, 353 | "name": "Ravioli Angelo", 354 | "category": "Grains/Cereals", 355 | "supplier": "Pasta Buttini s.r.l.", 356 | }, 357 | { 358 | "id": 58, 359 | "name": "Escargots de Bourgogne", 360 | "category": "Seafood", 361 | "supplier": "Escargots Nouveaux", 362 | }, 363 | { 364 | "id": 59, 365 | "name": "Raclette Courdavault", 366 | "category": "Dairy Products", 367 | "supplier": "Gai pturage", 368 | }, 369 | { 370 | "id": 60, 371 | "name": "Camembert Pierrot", 372 | "category": "Dairy Products", 373 | "supplier": "Gai pturage", 374 | }, 375 | { 376 | "id": 61, 377 | "name": "Sirop d'rable", 378 | "category": "Condiments", 379 | "supplier": "Forts d'rables", 380 | }, 381 | { 382 | "id": 62, 383 | "name": "Tarte au sucre", 384 | "category": "Confections", 385 | "supplier": "Forts d'rables", 386 | }, 387 | { 388 | "id": 63, 389 | "name": "Vegie-spread", 390 | "category": "Condiments", 391 | "supplier": "Pavlova, Ltd.", 392 | }, 393 | { 394 | "id": 64, 395 | "name": "Wimmers gute Semmelkndel", 396 | "category": "Grains/Cereals", 397 | "supplier": "Plutzer Lebensmittelgromrkte AG", 398 | }, 399 | { 400 | "id": 65, 401 | "name": "Louisiana Fiery Hot Pepper Sauce", 402 | "category": "Condiments", 403 | "supplier": "New Orleans Cajun Delights", 404 | }, 405 | { 406 | "id": 66, 407 | "name": "Louisiana Hot Spiced Okra", 408 | "category": "Condiments", 409 | "supplier": "New Orleans Cajun Delights", 410 | }, 411 | { 412 | "id": 67, 413 | "name": "Laughing Lumberjack Lager", 414 | "category": "Beverages", 415 | "supplier": "Bigfoot Breweries", 416 | }, 417 | { 418 | "id": 68, 419 | "name": "Scottish Longbreads", 420 | "category": "Confections", 421 | "supplier": "Specialty Biscuits, Ltd.", 422 | }, 423 | { 424 | "id": 69, 425 | "name": "Gudbrandsdalsost", 426 | "category": "Dairy Products", 427 | "supplier": "Norske Meierier", 428 | }, 429 | { 430 | "id": 70, 431 | "name": "Outback Lager", 432 | "category": "Beverages", 433 | "supplier": "Pavlova, Ltd.", 434 | }, 435 | { 436 | "id": 71, 437 | "name": "Flotemysost", 438 | "category": "Dairy Products", 439 | "supplier": "Norske Meierier", 440 | }, 441 | { 442 | "id": 72, 443 | "name": "Mozzarella di Giovanni", 444 | "category": "Dairy Products", 445 | "supplier": "Formaggi Fortini s.r.l.", 446 | }, 447 | { 448 | "id": 73, 449 | "name": "Rd Kaviar", 450 | "category": "Seafood", 451 | "supplier": "Svensk Sjfda AB", 452 | }, 453 | { 454 | "id": 74, 455 | "name": "Longlife Tofu", 456 | "category": "Produce", 457 | "supplier": "Tokyo Traders", 458 | }, 459 | { 460 | "id": 75, 461 | "name": "Rhnbru Klosterbier", 462 | "category": "Beverages", 463 | "supplier": "Plutzer Lebensmittelgromrkte AG", 464 | }, 465 | { 466 | "id": 76, 467 | "name": "Lakkalikri", 468 | "category": "Beverages", 469 | "supplier": "Karkki Oy", 470 | }, 471 | { 472 | "id": 77, 473 | "name": "Original Frankfurter grne Soe", 474 | "category": "Condiments", 475 | "supplier": "Plutzer Lebensmittelgromrkte AG", 476 | }, 477 | ] 478 | } 479 | 480 | def test_status_code(self): 481 | self.assertEqual(self._response.status_code, 200) 482 | 483 | def test_response(self): 484 | self.assertEqual( 485 | self._response.json(), 486 | self._expected_response, 487 | ) 488 | 489 | 490 | if __name__ == "__main__": 491 | unittest.main() 492 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_5_test_1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/2/orders") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "orders": [ 15 | { 16 | "id": 10255, 17 | "customer": "Richter Supermarkt", 18 | "quantity": 20, 19 | "total_price": 304.0, 20 | }, 21 | { 22 | "id": 10258, 23 | "customer": "Ernst Handel", 24 | "quantity": 50, 25 | "total_price": 608.0, 26 | }, 27 | { 28 | "id": 10264, 29 | "customer": "Folk och f HB", 30 | "quantity": 35, 31 | "total_price": 532.0, 32 | }, 33 | { 34 | "id": 10298, 35 | "customer": "Hungry Owl All-Night Grocers", 36 | "quantity": 40, 37 | "total_price": 608.0, 38 | }, 39 | { 40 | "id": 10327, 41 | "customer": "Folk och f HB", 42 | "quantity": 25, 43 | "total_price": 304.0, 44 | }, 45 | { 46 | "id": 10335, 47 | "customer": "Hungry Owl All-Night Grocers", 48 | "quantity": 7, 49 | "total_price": 85.12, 50 | }, 51 | { 52 | "id": 10342, 53 | "customer": "Frankenversand", 54 | "quantity": 24, 55 | "total_price": 291.84, 56 | }, 57 | { 58 | "id": 10393, 59 | "customer": "Save-a-lot Markets", 60 | "quantity": 25, 61 | "total_price": 285.0, 62 | }, 63 | { 64 | "id": 10418, 65 | "customer": "QUICK-Stop", 66 | "quantity": 60, 67 | "total_price": 912.0, 68 | }, 69 | { 70 | "id": 10435, 71 | "customer": "Consolidated Holdings", 72 | "quantity": 10, 73 | "total_price": 152.0, 74 | }, 75 | { 76 | "id": 10440, 77 | "customer": "Save-a-lot Markets", 78 | "quantity": 45, 79 | "total_price": 581.4, 80 | }, 81 | { 82 | "id": 10469, 83 | "customer": "White Clover Markets", 84 | "quantity": 40, 85 | "total_price": 516.8, 86 | }, 87 | { 88 | "id": 10485, 89 | "customer": "LINO-Delicateses", 90 | "quantity": 20, 91 | "total_price": 273.6, 92 | }, 93 | { 94 | "id": 10504, 95 | "customer": "White Clover Markets", 96 | "quantity": 12, 97 | "total_price": 228.0, 98 | }, 99 | { 100 | "id": 10611, 101 | "customer": "Wolski Zajazd", 102 | "quantity": 10, 103 | "total_price": 190.0, 104 | }, 105 | { 106 | "id": 10622, 107 | "customer": "Ricardo Adocicados", 108 | "quantity": 20, 109 | "total_price": 380.0, 110 | }, 111 | { 112 | "id": 10632, 113 | "customer": "Die Wandernde Kuh", 114 | "quantity": 30, 115 | "total_price": 541.5, 116 | }, 117 | { 118 | "id": 10641, 119 | "customer": "HILARION-Abastos", 120 | "quantity": 50, 121 | "total_price": 950.0, 122 | }, 123 | { 124 | "id": 10703, 125 | "customer": "Folk och f HB", 126 | "quantity": 5, 127 | "total_price": 95.0, 128 | }, 129 | { 130 | "id": 10714, 131 | "customer": "Save-a-lot Markets", 132 | "quantity": 30, 133 | "total_price": 427.5, 134 | }, 135 | { 136 | "id": 10722, 137 | "customer": "Save-a-lot Markets", 138 | "quantity": 3, 139 | "total_price": 57.0, 140 | }, 141 | { 142 | "id": 10741, 143 | "customer": "Around the Horn", 144 | "quantity": 15, 145 | "total_price": 228.0, 146 | }, 147 | { 148 | "id": 10766, 149 | "customer": "Ottilies Kseladen", 150 | "quantity": 40, 151 | "total_price": 760.0, 152 | }, 153 | { 154 | "id": 10787, 155 | "customer": "La maison d'Asie", 156 | "quantity": 15, 157 | "total_price": 270.75, 158 | }, 159 | { 160 | "id": 10792, 161 | "customer": "Wolski Zajazd", 162 | "quantity": 10, 163 | "total_price": 190.0, 164 | }, 165 | { 166 | "id": 10806, 167 | "customer": "Victuailles en stock", 168 | "quantity": 20, 169 | "total_price": 285.0, 170 | }, 171 | { 172 | "id": 10813, 173 | "customer": "Ricardo Adocicados", 174 | "quantity": 12, 175 | "total_price": 182.4, 176 | }, 177 | { 178 | "id": 10829, 179 | "customer": "Island Trading", 180 | "quantity": 10, 181 | "total_price": 190.0, 182 | }, 183 | { 184 | "id": 10851, 185 | "customer": "Ricardo Adocicados", 186 | "quantity": 5, 187 | "total_price": 90.25, 188 | }, 189 | { 190 | "id": 10852, 191 | "customer": "Rattlesnake Canyon Grocery", 192 | "quantity": 15, 193 | "total_price": 285.0, 194 | }, 195 | { 196 | "id": 10856, 197 | "customer": "Antonio Moreno Taquera", 198 | "quantity": 20, 199 | "total_price": 380.0, 200 | }, 201 | { 202 | "id": 10866, 203 | "customer": "Berglunds snabbkp", 204 | "quantity": 21, 205 | "total_price": 299.25, 206 | }, 207 | { 208 | "id": 10885, 209 | "customer": "Suprmes dlices", 210 | "quantity": 20, 211 | "total_price": 380.0, 212 | }, 213 | { 214 | "id": 10888, 215 | "customer": "Godos Cocina Tpica", 216 | "quantity": 20, 217 | "total_price": 380.0, 218 | }, 219 | { 220 | "id": 10939, 221 | "customer": "Magazzini Alimentari Riuniti", 222 | "quantity": 10, 223 | "total_price": 161.5, 224 | }, 225 | { 226 | "id": 10991, 227 | "customer": "QUICK-Stop", 228 | "quantity": 50, 229 | "total_price": 760.0, 230 | }, 231 | { 232 | "id": 11021, 233 | "customer": "QUICK-Stop", 234 | "quantity": 11, 235 | "total_price": 156.75, 236 | }, 237 | { 238 | "id": 11030, 239 | "customer": "Save-a-lot Markets", 240 | "quantity": 100, 241 | "total_price": 1425.0, 242 | }, 243 | { 244 | "id": 11041, 245 | "customer": "Chop-suey Chinese", 246 | "quantity": 30, 247 | "total_price": 456.0, 248 | }, 249 | { 250 | "id": 11049, 251 | "customer": "Gourmet Lanchonetes", 252 | "quantity": 10, 253 | "total_price": 152.0, 254 | }, 255 | { 256 | "id": 11070, 257 | "customer": "Lehmanns Marktstand", 258 | "quantity": 20, 259 | "total_price": 323.0, 260 | }, 261 | { 262 | "id": 11072, 263 | "customer": "Ernst Handel", 264 | "quantity": 8, 265 | "total_price": 152.0, 266 | }, 267 | { 268 | "id": 11075, 269 | "customer": "Richter Supermarkt", 270 | "quantity": 10, 271 | "total_price": 161.5, 272 | }, 273 | { 274 | "id": 11077, 275 | "customer": "Rattlesnake Canyon Grocery", 276 | "quantity": 24, 277 | "total_price": 364.8, 278 | }, 279 | ] 280 | } 281 | 282 | def test_status_code(self): 283 | self.assertEqual(self._response.status_code, 200) 284 | 285 | def test_response(self): 286 | self.assertEqual( 287 | self._response.json(), 288 | self._expected_response, 289 | ) 290 | 291 | 292 | if __name__ == "__main__": 293 | unittest.main() 294 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_5_test_2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/10/orders") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "orders": [ 15 | { 16 | "id": 10273, 17 | "customer": "QUICK-Stop", 18 | "quantity": 24, 19 | "total_price": 565.44, 20 | }, 21 | { 22 | "id": 10276, 23 | "customer": "Tortuga Restaurante", 24 | "quantity": 15, 25 | "total_price": 372.0, 26 | }, 27 | { 28 | "id": 10357, 29 | "customer": "LILA-Supermercado", 30 | "quantity": 30, 31 | "total_price": 595.2, 32 | }, 33 | { 34 | "id": 10389, 35 | "customer": "Bottom-Dollar Markets", 36 | "quantity": 16, 37 | "total_price": 396.8, 38 | }, 39 | { 40 | "id": 10449, 41 | "customer": "Blondesddsl pre et fils", 42 | "quantity": 14, 43 | "total_price": 347.2, 44 | }, 45 | { 46 | "id": 10450, 47 | "customer": "Victuailles en stock", 48 | "quantity": 20, 49 | "total_price": 396.8, 50 | }, 51 | { 52 | "id": 10478, 53 | "customer": "Victuailles en stock", 54 | "quantity": 20, 55 | "total_price": 471.2, 56 | }, 57 | { 58 | "id": 10519, 59 | "customer": "Chop-suey Chinese", 60 | "quantity": 16, 61 | "total_price": 471.2, 62 | }, 63 | { 64 | "id": 10524, 65 | "customer": "Berglunds snabbkp", 66 | "quantity": 2, 67 | "total_price": 62.0, 68 | }, 69 | { 70 | "id": 10568, 71 | "customer": "Galera del gastrnomo", 72 | "quantity": 5, 73 | "total_price": 155.0, 74 | }, 75 | { 76 | "id": 10609, 77 | "customer": "Du monde entier", 78 | "quantity": 10, 79 | "total_price": 310.0, 80 | }, 81 | { 82 | "id": 10612, 83 | "customer": "Save-a-lot Markets", 84 | "quantity": 70, 85 | "total_price": 2170.0, 86 | }, 87 | { 88 | "id": 10646, 89 | "customer": "Hungry Owl All-Night Grocers", 90 | "quantity": 18, 91 | "total_price": 418.5, 92 | }, 93 | { 94 | "id": 10664, 95 | "customer": "Furia Bacalhau e Frutos do Mar", 96 | "quantity": 24, 97 | "total_price": 632.4, 98 | }, 99 | { 100 | "id": 10676, 101 | "customer": "Tortuga Restaurante", 102 | "quantity": 2, 103 | "total_price": 62.0, 104 | }, 105 | { 106 | "id": 10685, 107 | "customer": "Gourmet Lanchonetes", 108 | "quantity": 20, 109 | "total_price": 620.0, 110 | }, 111 | { 112 | "id": 10688, 113 | "customer": "Vaffeljernet", 114 | "quantity": 18, 115 | "total_price": 502.2, 116 | }, 117 | { 118 | "id": 10713, 119 | "customer": "Save-a-lot Markets", 120 | "quantity": 18, 121 | "total_price": 558.0, 122 | }, 123 | { 124 | "id": 10715, 125 | "customer": "Bon app'", 126 | "quantity": 21, 127 | "total_price": 651.0, 128 | }, 129 | { 130 | "id": 10724, 131 | "customer": "Mre Paillarde", 132 | "quantity": 16, 133 | "total_price": 496.0, 134 | }, 135 | { 136 | "id": 10775, 137 | "customer": "The Cracker Box", 138 | "quantity": 6, 139 | "total_price": 186.0, 140 | }, 141 | { 142 | "id": 10785, 143 | "customer": "GROSELLA-Restaurante", 144 | "quantity": 10, 145 | "total_price": 310.0, 146 | }, 147 | { 148 | "id": 10804, 149 | "customer": "Seven Seas Imports", 150 | "quantity": 36, 151 | "total_price": 1116.0, 152 | }, 153 | { 154 | "id": 10827, 155 | "customer": "Bon app'", 156 | "quantity": 15, 157 | "total_price": 465.0, 158 | }, 159 | { 160 | "id": 10841, 161 | "customer": "Suprmes dlices", 162 | "quantity": 16, 163 | "total_price": 496.0, 164 | }, 165 | { 166 | "id": 10854, 167 | "customer": "Ernst Handel", 168 | "quantity": 100, 169 | "total_price": 2635.0, 170 | }, 171 | { 172 | "id": 10874, 173 | "customer": "Godos Cocina Tpica", 174 | "quantity": 10, 175 | "total_price": 310.0, 176 | }, 177 | { 178 | "id": 10886, 179 | "customer": "Hanari Carnes", 180 | "quantity": 70, 181 | "total_price": 2170.0, 182 | }, 183 | { 184 | "id": 10924, 185 | "customer": "Berglunds snabbkp", 186 | "quantity": 20, 187 | "total_price": 558.0, 188 | }, 189 | { 190 | "id": 10946, 191 | "customer": "Vaffeljernet", 192 | "quantity": 25, 193 | "total_price": 775.0, 194 | }, 195 | { 196 | "id": 10949, 197 | "customer": "Bottom-Dollar Markets", 198 | "quantity": 30, 199 | "total_price": 930.0, 200 | }, 201 | { 202 | "id": 11020, 203 | "customer": "Ottilies Kseladen", 204 | "quantity": 24, 205 | "total_price": 632.4, 206 | }, 207 | { 208 | "id": 11077, 209 | "customer": "Rattlesnake Canyon Grocery", 210 | "quantity": 1, 211 | "total_price": 31.0, 212 | }, 213 | ] 214 | } 215 | 216 | def test_status_code(self): 217 | self.assertEqual(self._response.status_code, 200) 218 | 219 | def test_response(self): 220 | self.assertEqual( 221 | self._response.json(), 222 | self._expected_response, 223 | ) 224 | 225 | 226 | if __name__ == "__main__": 227 | unittest.main() 228 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_5_test_3.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/11111/orders") 12 | self._response = requests.get(get_path) 13 | 14 | def test_status_code(self): 15 | self.assertEqual(self._response.status_code, 404) 16 | 17 | 18 | if __name__ == "__main__": 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_5_test_4.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | get_path = urllib.parse.urljoin(AppClass.APP_URL, "/products/48/orders") 12 | self._response = requests.get(get_path) 13 | self._expected_response = { 14 | "orders": [ 15 | { 16 | "id": 10403, 17 | "customer": "Ernst Handel", 18 | "quantity": 70, 19 | "total_price": 606.9, 20 | }, 21 | { 22 | "id": 10453, 23 | "customer": "Around the Horn", 24 | "quantity": 15, 25 | "total_price": 137.7, 26 | }, 27 | { 28 | "id": 10507, 29 | "customer": "Antonio Moreno Taquera", 30 | "quantity": 15, 31 | "total_price": 162.56, 32 | }, 33 | { 34 | "id": 10604, 35 | "customer": "Furia Bacalhau e Frutos do Mar", 36 | "quantity": 6, 37 | "total_price": 68.85, 38 | }, 39 | { 40 | "id": 10704, 41 | "customer": "Queen Cozinha", 42 | "quantity": 24, 43 | "total_price": 306.0, 44 | }, 45 | { 46 | "id": 10814, 47 | "customer": "Victuailles en stock", 48 | "quantity": 8, 49 | "total_price": 86.7, 50 | }, 51 | ] 52 | } 53 | 54 | def test_status_code(self): 55 | self.assertEqual(self._response.status_code, 200) 56 | 57 | def test_response(self): 58 | self.assertEqual( 59 | self._response.json(), 60 | self._expected_response, 61 | ) 62 | 63 | 64 | if __name__ == "__main__": 65 | unittest.main() 66 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_6_test_1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | self.input_data = {"name": "Test Category"} 12 | self.update_data = {"name": "Test Another Category"} 13 | self.fail_get_path = urllib.parse.urljoin( 14 | AppClass.APP_URL, f"/categories/123123123" 15 | ) 16 | 17 | def test_crud_flow(self): 18 | post_path = urllib.parse.urljoin(AppClass.APP_URL, "/categories") 19 | post_response = requests.post(post_path, json=self.input_data) 20 | post_response_json = post_response.json() 21 | self.assertEqual(post_response.status_code, 201) 22 | self.assertEqual(post_response_json["name"], self.input_data["name"]) 23 | self.assertTrue(post_response_json["id"]) 24 | 25 | get_path = urllib.parse.urljoin( 26 | AppClass.APP_URL, f"/categories/{post_response_json['id']}" 27 | ) 28 | 29 | put_response = requests.put(get_path, json=self.update_data) 30 | put_response_json = put_response.json() 31 | self.assertEqual(put_response.status_code, 200) 32 | self.assertEqual(put_response_json["id"], post_response_json["id"]) 33 | self.assertEqual(put_response_json["name"], self.update_data["name"]) 34 | 35 | delete_response = requests.delete(get_path) 36 | delete_response_json = delete_response.json() 37 | self.assertEqual(delete_response.status_code, 200) 38 | self.assertEqual(delete_response_json, {"deleted": 1}) 39 | 40 | delete_response = requests.delete(get_path) 41 | delete_response_json = delete_response.json() 42 | self.assertEqual(delete_response.status_code, 404) 43 | 44 | def test_fail_put(self): 45 | put_response = requests.put(self.fail_get_path, json=self.update_data) 46 | self.assertEqual(put_response.status_code, 404) 47 | 48 | def test_fail_delete(self): 49 | delete_response = requests.delete(self.fail_get_path) 50 | self.assertEqual(delete_response.status_code, 404) 51 | 52 | 53 | if __name__ == "__main__": 54 | unittest.main() 55 | -------------------------------------------------------------------------------- /4_T_jak_Tabela/praca_domowa/zadanie_4_6_test_2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | import requests 5 | 6 | from file import AppClass 7 | 8 | 9 | class CodingRoomsUnitTests(unittest.TestCase): 10 | def setUp(self): 11 | self.input_data = {"name": "Test NewCategory"} 12 | self.update_data = {"name": "Test Another NewCategory"} 13 | 14 | def test_cud_flow(self): 15 | post_path = urllib.parse.urljoin(AppClass.APP_URL, "/categories") 16 | post_response = requests.post(post_path, json=self.input_data) 17 | post_response_json = post_response.json() 18 | self.assertEqual(post_response.status_code, 201) 19 | self.assertEqual(post_response_json["name"], self.input_data["name"]) 20 | self.assertTrue(post_response_json["id"]) 21 | 22 | index_path = urllib.parse.urljoin(AppClass.APP_URL, f"/categories") 23 | 24 | get_path = urllib.parse.urljoin( 25 | AppClass.APP_URL, f"/categories/{post_response_json['id']}" 26 | ) 27 | 28 | index_response = requests.get(index_path) 29 | index_response_json = index_response.json() 30 | last_record = index_response_json["categories"][-1] 31 | self.assertEqual(last_record["name"], self.input_data["name"]) 32 | self.assertEqual(last_record["id"], post_response_json["id"]) 33 | 34 | put_response = requests.put(get_path, json=self.update_data) 35 | put_response_json = put_response.json() 36 | self.assertEqual(put_response.status_code, 200) 37 | self.assertEqual(put_response_json["id"], post_response_json["id"]) 38 | self.assertEqual(put_response_json["name"], self.update_data["name"]) 39 | 40 | index_response = requests.get(index_path) 41 | index_response_json = index_response.json() 42 | last_record = index_response_json["categories"][-1] 43 | self.assertEqual(last_record["name"], self.update_data["name"]) 44 | self.assertEqual(last_record["id"], post_response_json["id"]) 45 | 46 | delete_response = requests.delete(get_path) 47 | delete_response_json = delete_response.json() 48 | self.assertEqual(delete_response.status_code, 200) 49 | self.assertEqual(delete_response_json, {"deleted": 1}) 50 | 51 | delete_response = requests.delete(get_path) 52 | delete_response_json = delete_response.json() 53 | self.assertEqual(delete_response.status_code, 404) 54 | 55 | 56 | if __name__ == "__main__": 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /5_Asyncio/A_jak_asyncio.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# A jak Asyncio\n", 12 | "\n", 13 | "\n", 14 | "### Marcin Jaroszewski\n", 15 | "### Python level up 2022\n", 16 | "### 07.06.2022" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": { 22 | "slideshow": { 23 | "slide_type": "subslide" 24 | } 25 | }, 26 | "source": [ 27 | "# 1. Wstęp teoretyczny" 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": { 33 | "slideshow": { 34 | "slide_type": "fragment" 35 | } 36 | }, 37 | "source": [ 38 | "Bardzo polecam książkę: https://www.amazon.com/Modern-Operating-Systems-Andrew-Tanenbaum/dp/013359162X\n", 39 | "Jest już nowe wydanie: https://www.amazon.com/Modern-Operating-Systems-Andrew-Tanenbaum-dp-1292061421/dp/1292061421" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": { 45 | "slideshow": { 46 | "slide_type": "subslide" 47 | } 48 | }, 49 | "source": [ 50 | "## 1.1 Ein procesor, ein rdzeń" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": { 56 | "slideshow": { 57 | "slide_type": "fragment" 58 | } 59 | }, 60 | "source": [ 61 | "Jak to było kiedy komputery miały jeden procesor, jakie były ograniczenia, problemy i wyzwania (np `time slicing`).\n", 62 | "- dawne czasy, kiedy prawie nie było systemów operacyjnych\n", 63 | "- trochę nowsze czasy, co jak chcieliśmy 2 programy uruchomić \"jednocześnie\" na tym samym komputerze (`time-slicing`)?" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": { 69 | "slideshow": { 70 | "slide_type": "subslide" 71 | } 72 | }, 73 | "source": [ 74 | "## 1.2 Ein procesor, viel rdzeni " 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": { 80 | "slideshow": { 81 | "slide_type": "fragment" 82 | } 83 | }, 84 | "source": [ 85 | "Miało być tak pięknie - ale rzeczywistość nas dogoniła." 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": { 91 | "slideshow": { 92 | "slide_type": "subslide" 93 | } 94 | }, 95 | "source": [ 96 | "## 1.3 Proces" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": { 102 | "slideshow": { 103 | "slide_type": "fragment" 104 | } 105 | }, 106 | "source": [ 107 | "W zasadzie: wykonywany program (jeden), można w wielu procesach uruchomić wiele \"kopii\" tego samego programu. Kontrolowany prez syste operacyjny. " 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": { 113 | "slideshow": { 114 | "slide_type": "fragment" 115 | } 116 | }, 117 | "source": [ 118 | "- pamięć\n", 119 | "- czas procesora\n", 120 | "- IPC" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": { 126 | "slideshow": { 127 | "slide_type": "fragment" 128 | } 129 | }, 130 | "source": [ 131 | "https://pl.wikipedia.org/wiki/Proces_(informatyka)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "metadata": { 137 | "slideshow": { 138 | "slide_type": "subslide" 139 | } 140 | }, 141 | "source": [ 142 | "## 1.4 Wątek" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": { 148 | "slideshow": { 149 | "slide_type": "fragment" 150 | } 151 | }, 152 | "source": [ 153 | "Są przynajmniej 2 rodzaje:\n", 154 | "- systemu operacyjnego\n", 155 | "- zielone" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": { 161 | "slideshow": { 162 | "slide_type": "fragment" 163 | } 164 | }, 165 | "source": [ 166 | "Różnice względem procesu:\n", 167 | "- pamięć\n", 168 | "- czas procesora\n", 169 | "- IPC" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": { 175 | "slideshow": { 176 | "slide_type": "fragment" 177 | } 178 | }, 179 | "source": [ 180 | "https://pl.wikipedia.org/wiki/W%C4%85tek_(informatyka)" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": { 186 | "slideshow": { 187 | "slide_type": "subslide" 188 | } 189 | }, 190 | "source": [ 191 | "## 1.5 Concurency" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": { 197 | "slideshow": { 198 | "slide_type": "fragment" 199 | } 200 | }, 201 | "source": [ 202 | "`Concurrency means multiple computations are happening at the same time. Concurrency is everywhere in modern programming` - za https://web.mit.edu/6.005/www/fa14/classes/17-concurrency/" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": { 208 | "slideshow": { 209 | "slide_type": "fragment" 210 | } 211 | }, 212 | "source": [ 213 | "Zauważmy, że w \"definicji\" nie jest limitowane, gdzie się dzieją \"obliczenia\"." 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": { 219 | "slideshow": { 220 | "slide_type": "subslide" 221 | } 222 | }, 223 | "source": [ 224 | "## 1.6 Paralellism" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": { 230 | "slideshow": { 231 | "slide_type": "fragment" 232 | } 233 | }, 234 | "source": [ 235 | "Współbieżność" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "metadata": { 241 | "slideshow": { 242 | "slide_type": "fragment" 243 | } 244 | }, 245 | "source": [ 246 | "```\n", 247 | "Concurrency is when two or more tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean they'll ever both be running at the same instant. For example, multitasking on a single-core machine.\n", 248 | "\n", 249 | "Parallelism is when tasks literally run at the same time, e.g., on a multicore processor.\n", 250 | "```\n", 251 | "za https://stackoverflow.com/a/1050257" 252 | ] 253 | }, 254 | { 255 | "cell_type": "markdown", 256 | "metadata": { 257 | "slideshow": { 258 | "slide_type": "subslide" 259 | } 260 | }, 261 | "source": [ 262 | "## 1.8 Synchronizacja" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": { 268 | "slideshow": { 269 | "slide_type": "fragment" 270 | } 271 | }, 272 | "source": [ 273 | "- sekcje krytyczne\n", 274 | "- locki\n", 275 | "- mutexy\n", 276 | "- semafory\n", 277 | "- inne" 278 | ] 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "metadata": { 283 | "slideshow": { 284 | "slide_type": "subslide" 285 | } 286 | }, 287 | "source": [ 288 | "## 1.9 Rodzaje obciążeń" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": { 294 | "slideshow": { 295 | "slide_type": "fragment" 296 | } 297 | }, 298 | "source": [ 299 | "- CPU\n", 300 | "- IO" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "metadata": { 306 | "slideshow": { 307 | "slide_type": "subslide" 308 | } 309 | }, 310 | "source": [ 311 | "# 2. A co na to Python?" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": { 317 | "slideshow": { 318 | "slide_type": "subslide" 319 | } 320 | }, 321 | "source": [ 322 | "## 2.1 `multiprocessing`" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "metadata": { 328 | "slideshow": { 329 | "slide_type": "fragment" 330 | } 331 | }, 332 | "source": [ 333 | "https://docs.python.org/3.8/library/multiprocessing.html" 334 | ] 335 | }, 336 | { 337 | "cell_type": "markdown", 338 | "metadata": { 339 | "slideshow": { 340 | "slide_type": "subslide" 341 | } 342 | }, 343 | "source": [ 344 | "## 2.2 `subprocess`" 345 | ] 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "metadata": { 350 | "slideshow": { 351 | "slide_type": "fragment" 352 | } 353 | }, 354 | "source": [ 355 | "https://docs.python.org/3/library/subprocess.html" 356 | ] 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "metadata": { 361 | "slideshow": { 362 | "slide_type": "subslide" 363 | } 364 | }, 365 | "source": [ 366 | "## 2.3 `threading`" 367 | ] 368 | }, 369 | { 370 | "cell_type": "markdown", 371 | "metadata": { 372 | "slideshow": { 373 | "slide_type": "fragment" 374 | } 375 | }, 376 | "source": [ 377 | "https://docs.python.org/3/library/threading.html" 378 | ] 379 | }, 380 | { 381 | "cell_type": "markdown", 382 | "metadata": { 383 | "slideshow": { 384 | "slide_type": "subslide" 385 | } 386 | }, 387 | "source": [ 388 | "## 2.4 GIL - Global Interpreter Lock" 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "metadata": { 394 | "slideshow": { 395 | "slide_type": "fragment" 396 | } 397 | }, 398 | "source": [ 399 | "https://wiki.python.org/moin/GlobalInterpreterLock" 400 | ] 401 | }, 402 | { 403 | "cell_type": "markdown", 404 | "metadata": { 405 | "slideshow": { 406 | "slide_type": "fragment" 407 | } 408 | }, 409 | "source": [ 410 | "jak się ma GIL do:\n", 411 | "- threading w Python\n", 412 | "- C pod spodem niektórych modułów\n", 413 | "- zewnętrznych bibliotek np `numpy`" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "metadata": { 419 | "slideshow": { 420 | "slide_type": "subslide" 421 | } 422 | }, 423 | "source": [ 424 | "## 2.5 `asyncio`" 425 | ] 426 | }, 427 | { 428 | "cell_type": "markdown", 429 | "metadata": { 430 | "slideshow": { 431 | "slide_type": "fragment" 432 | } 433 | }, 434 | "source": [ 435 | "https://docs.python.org/3/library/asyncio.html" 436 | ] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "metadata": { 441 | "slideshow": { 442 | "slide_type": "fragment" 443 | } 444 | }, 445 | "source": [ 446 | "`Cooperative multitasking` - dzisiejsza nazwa wytrych. " 447 | ] 448 | }, 449 | { 450 | "cell_type": "markdown", 451 | "metadata": { 452 | "slideshow": { 453 | "slide_type": "fragment" 454 | } 455 | }, 456 | "source": [ 457 | "Przykład z szachami." 458 | ] 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "metadata": { 463 | "slideshow": { 464 | "slide_type": "fragment" 465 | } 466 | }, 467 | "source": [ 468 | "Operacje blokujące - co to jest?" 469 | ] 470 | }, 471 | { 472 | "cell_type": "markdown", 473 | "metadata": { 474 | "slideshow": { 475 | "slide_type": "slide" 476 | } 477 | }, 478 | "source": [ 479 | "# 3. Słowniczek" 480 | ] 481 | }, 482 | { 483 | "cell_type": "markdown", 484 | "metadata": { 485 | "slideshow": { 486 | "slide_type": "fragment" 487 | } 488 | }, 489 | "source": [ 490 | "- event loop\n", 491 | "- awaitable\n", 492 | "- coroutine\n", 493 | "- `async`\n", 494 | "- `await`" 495 | ] 496 | }, 497 | { 498 | "cell_type": "markdown", 499 | "metadata": { 500 | "slideshow": { 501 | "slide_type": "slide" 502 | } 503 | }, 504 | "source": [ 505 | "# 4. Przykłady" 506 | ] 507 | }, 508 | { 509 | "cell_type": "markdown", 510 | "metadata": { 511 | "slideshow": { 512 | "slide_type": "fragment" 513 | } 514 | }, 515 | "source": [ 516 | "Będziemy odpytywać urle z dwóch plików" 517 | ] 518 | }, 519 | { 520 | "cell_type": "markdown", 521 | "metadata": { 522 | "slideshow": { 523 | "slide_type": "subslide" 524 | } 525 | }, 526 | "source": [ 527 | "urls\n", 528 | "```\n", 529 | "http://httpbin.org/delay/9\n", 530 | "http://httpbin.org/delay/8\n", 531 | "http://httpbin.org/delay/7\n", 532 | "http://httpbin.org/delay/6\n", 533 | "http://httpbin.org/delay/5\n", 534 | "http://httpbin.org/delay/4\n", 535 | "http://httpbin.org/delay/3\n", 536 | "http://httpbin.org/delay/2\n", 537 | "http://httpbin.org/delay/1\n", 538 | "```" 539 | ] 540 | }, 541 | { 542 | "cell_type": "markdown", 543 | "metadata": { 544 | "slideshow": { 545 | "slide_type": "subslide" 546 | } 547 | }, 548 | "source": [ 549 | "urls_2\n", 550 | "```\n", 551 | "http://httpbin.org/delay/1\n", 552 | "http://httpbin.org/delay/1\n", 553 | "http://httpbin.org/delay/1\n", 554 | "http://httpbin.org/delay/1\n", 555 | "http://httpbin.org/delay/1\n", 556 | "http://httpbin.org/delay/1\n", 557 | "http://httpbin.org/delay/1\n", 558 | "http://httpbin.org/delay/1\n", 559 | "http://httpbin.org/delay/1\n", 560 | "http://httpbin.org/delay/1\n", 561 | "http://httpbin.org/delay/1\n", 562 | "http://httpbin.org/delay/1\n", 563 | "http://httpbin.org/delay/1\n", 564 | "http://httpbin.org/delay/1\n", 565 | "http://httpbin.org/delay/1\n", 566 | "http://httpbin.org/delay/1\n", 567 | "http://httpbin.org/delay/1\n", 568 | "http://httpbin.org/delay/1\n", 569 | "http://httpbin.org/delay/1\n", 570 | "http://httpbin.org/delay/1\n", 571 | "```" 572 | ] 573 | }, 574 | { 575 | "cell_type": "markdown", 576 | "metadata": { 577 | "slideshow": { 578 | "slide_type": "subslide" 579 | } 580 | }, 581 | "source": [ 582 | "## 4.1 Synchroniczne odpytywanie z requests" 583 | ] 584 | }, 585 | { 586 | "cell_type": "markdown", 587 | "metadata": { 588 | "slideshow": { 589 | "slide_type": "subslide" 590 | } 591 | }, 592 | "source": [ 593 | "```python\n", 594 | "import time\n", 595 | "\n", 596 | "import requests\n", 597 | "\n", 598 | "\n", 599 | "responses = []\n", 600 | "\n", 601 | "urls_f_name = 'urls'\n", 602 | "# urls_f_name = 'urls_2'\n", 603 | "start = time.time()\n", 604 | "with open(urls_f_name, 'r') as f:\n", 605 | " for line in f.readlines():\n", 606 | " print(f'Checking {line}')\n", 607 | " resp = requests.get(line)\n", 608 | " responses.append(resp)\n", 609 | "end = time.time()\n", 610 | "\n", 611 | "print(f'elapsed: {end - start} seconds')\n", 612 | "```" 613 | ] 614 | }, 615 | { 616 | "cell_type": "markdown", 617 | "metadata": { 618 | "slideshow": { 619 | "slide_type": "subslide" 620 | } 621 | }, 622 | "source": [ 623 | "## 4.2 Asynchrnoniczne odptywanie z requests" 624 | ] 625 | }, 626 | { 627 | "cell_type": "markdown", 628 | "metadata": { 629 | "slideshow": { 630 | "slide_type": "fragment" 631 | } 632 | }, 633 | "source": [ 634 | "Nie da się - bo `requests` jest \"blokujące\"." 635 | ] 636 | }, 637 | { 638 | "cell_type": "markdown", 639 | "metadata": { 640 | "slideshow": { 641 | "slide_type": "fragment" 642 | } 643 | }, 644 | "source": [ 645 | "O co chodzi z blokowaniem w kontekście biblitek/modułów **WAŻNE**" 646 | ] 647 | }, 648 | { 649 | "cell_type": "markdown", 650 | "metadata": { 651 | "slideshow": { 652 | "slide_type": "subslide" 653 | } 654 | }, 655 | "source": [ 656 | "## 4.3 Asynchroniczne odpytywanie z `httpx` lub `aiohttp`" 657 | ] 658 | }, 659 | { 660 | "cell_type": "markdown", 661 | "metadata": { 662 | "slideshow": { 663 | "slide_type": "fragment" 664 | } 665 | }, 666 | "source": [ 667 | "- httpx: https://github.com/encode/httpx\n", 668 | "- aiohttp: https://docs.aiohttp.org/en/stable/" 669 | ] 670 | }, 671 | { 672 | "cell_type": "markdown", 673 | "metadata": { 674 | "slideshow": { 675 | "slide_type": "subslide" 676 | } 677 | }, 678 | "source": [ 679 | "v. 1\n", 680 | "```python\n", 681 | "import asyncio\n", 682 | "import time\n", 683 | "\n", 684 | "import httpx\n", 685 | "\n", 686 | "\n", 687 | "responses = []\n", 688 | "urls_f_name = 'urls'\n", 689 | "\n", 690 | "\n", 691 | "async def main():\n", 692 | " with open(urls_f_name, 'r') as f:\n", 693 | " async with httpx.AsyncClient(timeout=15) as client:\n", 694 | " for line in f.readlines():\n", 695 | " print(f'Checking {line}')\n", 696 | " resp = await client.get(line)\n", 697 | " responses.append(resp) \n", 698 | "\n", 699 | "\n", 700 | "if __name__ == '__main__':\n", 701 | " start = time.time()\n", 702 | " asyncio.run(main())\n", 703 | " end = time.time()\n", 704 | " print(f'elapsed: {end - start} seconds')\n", 705 | "\n", 706 | "```" 707 | ] 708 | }, 709 | { 710 | "cell_type": "markdown", 711 | "metadata": { 712 | "slideshow": { 713 | "slide_type": "subslide" 714 | } 715 | }, 716 | "source": [ 717 | "v. 2\n", 718 | "```python\n", 719 | "import asyncio\n", 720 | "import time\n", 721 | "\n", 722 | "import httpx\n", 723 | "\n", 724 | "\n", 725 | "responses = []\n", 726 | "urls_f_name = 'urls'\n", 727 | "urls_f_name = 'urls_2'\n", 728 | "\n", 729 | "partials = []\n", 730 | "async def get(url, client):\n", 731 | " print(f'Checking {url}')\n", 732 | " start = time.time()\n", 733 | " resp = await client.get(url)\n", 734 | " end = time.time()\n", 735 | " partials.append(end-start)\n", 736 | " responses.append(resp)\n", 737 | "\n", 738 | "async def main():\n", 739 | " with open(urls_f_name, 'r') as f:\n", 740 | " async with httpx.AsyncClient(timeout=15) as client:\n", 741 | " coroutines = []\n", 742 | " for line in f.readlines():\n", 743 | " coroutines.append(get(line, client))\n", 744 | " await asyncio.gather(*coroutines)\n", 745 | "\n", 746 | "\n", 747 | "if __name__ == '__main__':\n", 748 | " start = time.time()\n", 749 | " asyncio.run(main())\n", 750 | " end = time.time()\n", 751 | " print(f'elapsed: {end - start} seconds')\n", 752 | " print(f'total partials: {sum(partials)}')\n", 753 | "```" 754 | ] 755 | }, 756 | { 757 | "cell_type": "markdown", 758 | "metadata": { 759 | "slideshow": { 760 | "slide_type": "subslide" 761 | } 762 | }, 763 | "source": [ 764 | "## 4.4 Wyjaśnienie różnic w przykładach i wynikach" 765 | ] 766 | }, 767 | { 768 | "cell_type": "markdown", 769 | "metadata": { 770 | "slideshow": { 771 | "slide_type": "fragment" 772 | } 773 | }, 774 | "source": [ 775 | "https://docs.python.org/3/library/asyncio-task.html#asyncio.run\n" 776 | ] 777 | }, 778 | { 779 | "cell_type": "markdown", 780 | "metadata": { 781 | "slideshow": { 782 | "slide_type": "slide" 783 | } 784 | }, 785 | "source": [ 786 | "# 5. Jak to się ma do apek webowych?" 787 | ] 788 | }, 789 | { 790 | "cell_type": "markdown", 791 | "metadata": { 792 | "slideshow": { 793 | "slide_type": "fragment" 794 | } 795 | }, 796 | "source": [ 797 | "https://12factor.net/" 798 | ] 799 | }, 800 | { 801 | "cell_type": "markdown", 802 | "metadata": { 803 | "slideshow": { 804 | "slide_type": "subslide" 805 | } 806 | }, 807 | "source": [ 808 | "## 5.1 Synchoroniczna apka z jednym workerem " 809 | ] 810 | }, 811 | { 812 | "cell_type": "markdown", 813 | "metadata": { 814 | "slideshow": { 815 | "slide_type": "subslide" 816 | } 817 | }, 818 | "source": [ 819 | "## 5.2 Asynchroniczna apka z jednym workerem" 820 | ] 821 | }, 822 | { 823 | "cell_type": "markdown", 824 | "metadata": { 825 | "slideshow": { 826 | "slide_type": "slide" 827 | } 828 | }, 829 | "source": [ 830 | "# 6. Uwagi końcowe" 831 | ] 832 | }, 833 | { 834 | "cell_type": "markdown", 835 | "metadata": { 836 | "slideshow": { 837 | "slide_type": "subslide" 838 | } 839 | }, 840 | "source": [ 841 | "## 6.1 czemu `asleep(0)` bywa dobrym pomysłem, ale nie zawsze" 842 | ] 843 | }, 844 | { 845 | "cell_type": "markdown", 846 | "metadata": { 847 | "slideshow": { 848 | "slide_type": "fragment" 849 | } 850 | }, 851 | "source": [ 852 | "https://stackoverflow.com/questions/55857581/converting-small-functions-to-coroutines/55866425#55866425" 853 | ] 854 | }, 855 | { 856 | "cell_type": "markdown", 857 | "metadata": { 858 | "slideshow": { 859 | "slide_type": "fragment" 860 | } 861 | }, 862 | "source": [ 863 | "https://stackoverflow.com/questions/41932359/timeout-for-python-coroutines/48816319#48816319" 864 | ] 865 | }, 866 | { 867 | "cell_type": "markdown", 868 | "metadata": { 869 | "slideshow": { 870 | "slide_type": "fragment" 871 | } 872 | }, 873 | "source": [ 874 | "```\n", 875 | "It means \"request value from the provided awaitable object, yielding control to the event loop if the object indicates that it does not have a value ready.\" The if is crucial: if the object does have a value ready, this value will be used immediately without ever deferring to the event loop. In other words, await doesn't guarantee that the event loop will get a chance to run.\n", 876 | "```" 877 | ] 878 | }, 879 | { 880 | "cell_type": "markdown", 881 | "metadata": { 882 | "slideshow": { 883 | "slide_type": "slide" 884 | } 885 | }, 886 | "source": [ 887 | "# 7. Pytania?" 888 | ] 889 | }, 890 | { 891 | "cell_type": "markdown", 892 | "metadata": { 893 | "slideshow": { 894 | "slide_type": "slide" 895 | } 896 | }, 897 | "source": [ 898 | "# 8. Wincyjj materiałuff" 899 | ] 900 | }, 901 | { 902 | "cell_type": "markdown", 903 | "metadata": { 904 | "slideshow": { 905 | "slide_type": "subslide" 906 | } 907 | }, 908 | "source": [ 909 | "Dokumentacja (warto):\n", 910 | "- https://docs.python.org/3/library/asyncio.html\n", 911 | "\n", 912 | "Real Python:\n", 913 | "- https://realpython.com/async-io-python/\n", 914 | "\n", 915 | "Łukasz Langa (**bardzo** polecam):\n", 916 | "1. https://www.youtube.com/watch?v=Xbl7XjFYsN4\n", 917 | "2. https://www.youtube.com/watch?v=E7Yn5biBZ58\n", 918 | "3. https://www.youtube.com/watch?v=-CzqsgaXUM8\n", 919 | "4. https://www.youtube.com/watch?v=1LTHbmed3D4\n", 920 | "5. https://www.youtube.com/watch?v=SyiTd4rLb2s\n", 921 | "6. https://www.youtube.com/watch?v=dnjm-sx7b8k\n", 922 | "\n", 923 | "Trochę krytyki:\n", 924 | "- https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/" 925 | ] 926 | }, 927 | { 928 | "cell_type": "markdown", 929 | "metadata": { 930 | "slideshow": { 931 | "slide_type": "slide" 932 | } 933 | }, 934 | "source": [ 935 | "# 8. That's all folks" 936 | ] 937 | } 938 | ], 939 | "metadata": { 940 | "celltoolbar": "Slideshow", 941 | "kernelspec": { 942 | "display_name": "Python 3", 943 | "language": "python", 944 | "name": "python3" 945 | }, 946 | "language_info": { 947 | "codemirror_mode": { 948 | "name": "ipython", 949 | "version": 3 950 | }, 951 | "file_extension": ".py", 952 | "mimetype": "text/x-python", 953 | "name": "python", 954 | "nbconvert_exporter": "python", 955 | "pygments_lexer": "ipython3", 956 | "version": "3.8.2" 957 | } 958 | }, 959 | "nbformat": 4, 960 | "nbformat_minor": 4 961 | } 962 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # da-python-level-up-dev-2022 2 | 3 | ## Ważne linki 4 | 5 | Materiały z zajęć: 6 | [https://github.com/DaftAcademy-Python-LevelUP-Dev-2022/da-python-level-up-dev-2022/](https://github.com/DaftAcademy-Python-LevelUP-Dev-2022/da-python-level-up-dev-2022/) 7 | ## Kontakt 8 | [python@daftacademy.pl](python@daftacademy.pl) 9 | Slack DaftAcademy 10 | ## Przygotowanie środowiska pracy przed zajęciami 11 | ### Instalacja Python 3.10 12 | Żeby nie tracić czasu w trakcie warsztatu, zależałoby nam żebyście przyszli na zajęcia z zainstalowaną odpowiednią wersją Pythona. Poniżej opisana jest krótka instrukcja instalacji dla najpopularniejszych systemów operacyjnych. 13 | 14 | Prosimy o nie używanie menadżera pakietów takich jak Conda czy Anaconda itp. 15 | #### Windows 16 | Wejdź na stronę [https://www.python.org/downloads/release/python-3103/](https://www.python.org/downloads/release/python-3103/) i pobierz odpowiedni instalator z sekcji `Files` - `Windows installer (64-bit)` dla systemu 64-bitowego lub `Windows installer (32-bit)` dla systemu 32-bitowego. 17 | 18 | Uruchom pobrany instalator. Zaznacz opcję `Add Python 3.10 to PATH`, a następnie kliknij `Install Now`. 19 | #### macOS & macOS @ Apple Silicone M1 20 | Wejdź na stronę [https://www.python.org/downloads/release/python-3103/](https://www.python.org/downloads/release/python-3103/) i pobierz odpowiedni instalator z sekcji `Files` - `macOS 64-bit Intel installer`. Uruchom pobrany plik i dokończ instalację. 21 | #### Linux 22 | Istnieje duża szansa, że masz już zainstalowanego pythona na swoim komputerze. W celu sprawdzenia jaka wersja jest zainstalowana, wpisz w terminalu: 23 | ``` 24 | python3 --version 25 | ``` 26 | Jeżeli uzyskasz wynik `Python 3.10.x` - jesteś gotowy na zajęcia. W przypadku, gdy nie zostanie odnaleziona komenda `python3` lub zainstalowana będzie niższa wersja niż `Python 3.10`, należy podążać za kolejnymi krokami, zależnymi od systemu, który posiadasz. 27 | ##### Debian lub Ubuntu 28 | Użyj w terminalu następującej komendy: 29 | ``` 30 | sudo apt install python3.10 31 | ``` 32 | Dla wersji Ubuntu starszych niż 16.10 powyższa komenda może nie zadziałać. W takiej sytuacji należy skorzystać z deadsnakes PPA: 33 | ``` 34 | sudo apt update 35 | sudo apt install software-properties-common 36 | sudo add-apt-repository ppa:deadsnakes/ppa 37 | sudo apt update 38 | sudo apt install python3.10 39 | sudo apt install python3.10-distutils python3.10-dev python3.10-venv wget 40 | sudo wget https://bootstrap.pypa.io/get-pip.py 41 | python3.10 get-pip.py 42 | ``` 43 | ##### Fedora (29+) 44 | 45 | ``` 46 | sudo dnf -y install python310 python3-pip python3-virtualenv 47 | ``` 48 | Dla starszych wersji Fedory powyższa komenda może nie zadziałać. W takiej sytuacji należy wykonać następującej komendy: 49 | ``` 50 | sudo dnf -y install gcc gcc-c++ zlib zlib-devel libffi-devel openssl-devel openssl-static wget tar make 51 | cd /tmp/; 52 | wget https://www.python.org/ftp/python/3.10.3/Python-3.10.3.tgz; 53 | tar xzf Python-3.10.3.tgz; 54 | cd Python-3.10.3; 55 | sudo ./configure --prefix=/opt/python310 --enable-optimizations --with-lto --with-system-ffi --with-computed-gotos --enable-loadable-sqlite-extensions; 56 | sudo make -j $(nproc); 57 | sudo make altinstall; 58 | sudo rm /tmp/Python-3.10.3.tgz; 59 | sudo ln -s /opt/python310/bin/python3.10 /opt/python310/bin/python3; 60 | sudo ln -s /opt/python310/bin/python3.10 /opt/python310/bin/python; 61 | sudo ln -s /opt/python310/bin/python3.10-config /opt/python310/bin/python-config; 62 | sudo ln -s /opt/python310/bin/pydoc3.10 /opt/python310/bin/pydoc; 63 | sudo ln -s /opt/python310/bin/idle3.10 /opt/python310/bin/idle; 64 | sudo ln -s /opt/python310/bin/python3.10 /usr/bin/python310; 65 | sudo ln -s /opt/python310/bin/pip3.10 /opt/python310/bin/pip3; 66 | sudo ln -s /opt/python310/bin/pip3.10 /opt/python310/bin/pip; 67 | ``` 68 | Dla starszych wersji Fedory możesz dostać błąd mówiący o tym, że komenda `dnf` nie została znaleziona. W takiej sytuacji należy skorzystać z komendy `yum`. 69 | ##### openSUSE 70 | Użyj w terminalu następujące komendy: 71 | ``` 72 | sudo zypper in python3 python3-pip python3-virtualenv 73 | ``` 74 | Dla starszych wersji openSUSE powyższa komenda może nie zadziałać. W takiej sytuacji należy wykonać następującej komendy: 75 | ``` 76 | sudo zypper in gcc gcc-c++ zlib-devel bzip2 libbz2-devel libffi-devel libopenssl-devel readline-devel sqlite3 sqlite3-devel xz xz-devel wget tar make; 77 | cd /tmp/; 78 | sudo wget https://www.python.org/ftp/python/3.10.3/Python-3.10.3.tar.xz; 79 | tar xf Python-3.10.3.tar.xz; 80 | cd Python-3.10.3; 81 | sudo ./configure --prefix=/opt/python310 --enable-optimizations --with-lto --with-system-ffi --with-computed-gotos --enable-loadable-sqlite-extensions; 82 | sudo make -j $(nproc); 83 | sudo make altinstall; 84 | sudo rm /tmp/Python-3.10.3.tgz; 85 | sudo ln -s /opt/python310/bin/python3.10 /opt/python310/bin/python3; 86 | sudo ln -s /opt/python310/bin/python3.10 /opt/python310/bin/python; 87 | sudo ln -s /opt/python310/bin/python3.10-config /opt/python310/bin/python-config; 88 | sudo ln -s /opt/python310/bin/pydoc3.10 /opt/python310/bin/pydoc; 89 | sudo ln -s /opt/python310/bin/idle3.10 /opt/python310/bin/idle; 90 | sudo ln -s /opt/python310/bin/python3.10 /usr/bin/python310; 91 | sudo ln -s /opt/python310/bin/pip3.10 /opt/python310/bin/pip3; 92 | sudo ln -s /opt/python310/bin/pip3.10 /opt/python310/bin/pip; 93 | ``` 94 | ### Sprawdzenie, czy Python 3.10 jest zainstalowany 95 | Wpisz w terminalu następującą komendę: 96 | ``` 97 | python3.10 --version 98 | ``` 99 | Jeżeli powyższa komenda zwróci wynik `Python 3.10.x` oznacza to, że masz zainstalowaną odpowiednią wersję Pythona. 100 | 101 | Na Windowsie powyższa komenda może nie zadziałać. Wtedy należy użyć w `Wierszu polecenia`: 102 | ``` 103 | python --version 104 | ``` 105 | Powinno ono zwrócić wynik `Python 3.10.x`. 106 | ### Wybór edytora tekstu 107 | Programowanie w Pythonie nie wymaga żadnych specjalistycznych narzędzi - wystarczy korzystać z edytora tekstu. Na zajęciach możesz korzystać z dowolnego edytora. Jeżeli nie wiesz co wybrać, polecamy Sublime Text [https://www.sublimetext.com/](https://www.sublimetext.com/). 108 | --------------------------------------------------------------------------------