├── .gitignore
├── README.md
└── open-lessons
├── abc-and-proto-13.05.2025
├── .python-version
├── README.md
├── callback_protocol.py
├── file.txt
├── interfaces
│ ├── __init__.py
│ ├── __main__.py
│ ├── fan.py
│ ├── light_bulb.py
│ ├── power_switch.py
│ └── switchable.py
├── main.py
├── protocols
│ ├── __init__.py
│ └── example_readable.py
├── pyproject.toml
└── uv.lock
├── decorators.06.03.2024
├── decorators.ipynb
└── requirements.txt
├── decorators.15.02.2022
├── main.ipynb
└── requirements.txt
├── decorators.25.01.2023
├── main.ipynb
└── requirements.txt
├── decorators.27.07.2023
├── main.ipynb
└── requirements.txt
├── decorators.27.12.2023
├── main.ipynb
└── requirements.txt
├── decorators.30.10.2024
├── example_0.py
├── main.ipynb
├── main.py
├── pyproject.toml
└── requirements.txt
├── django-cbv-25.09
├── README.md
├── poetry.lock
├── pyproject.toml
└── task_manager
│ ├── manage.py
│ ├── task_manager
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
│ ├── tasks
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_task_description.py
│ │ └── __init__.py
│ ├── models.py
│ ├── templates
│ │ └── tasks
│ │ │ ├── base.html
│ │ │ ├── task_detail.html
│ │ │ ├── task_form.html
│ │ │ └── task_list.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
│ └── templates
│ ├── about.html
│ ├── base.html
│ └── index.html
├── fastapi-and-jinja-27.06
├── README.md
├── create_fastapi_app.py
├── main.py
├── requirements.txt
├── run_main.py
├── templates
│ ├── base.html
│ ├── index.html
│ └── products
│ │ ├── details.html
│ │ └── list.html
├── utils
│ ├── __init__.py
│ └── templating.py
└── views
│ ├── __init__.py
│ ├── index.py
│ └── products
│ ├── __init__.py
│ ├── crud.py
│ ├── schemas.py
│ └── views.py
├── fastapi-bg-tasks-20.05.2025
├── .python-version
├── README.md
├── api
│ ├── __init__.py
│ └── api_v1
│ │ ├── __init__.py
│ │ └── users
│ │ ├── __init__.py
│ │ ├── schemas.py
│ │ ├── utils
│ │ ├── __init__.py
│ │ ├── email_senders.py
│ │ └── welcome_email_sender.py
│ │ └── views.py
├── app.py
├── config.py
├── docker-compose.yaml
├── pyproject.toml
└── uv.lock
├── fastapi-intro-27.02.2025
├── main.py
├── requirements.txt
└── views
│ ├── __init__.py
│ └── movies
│ ├── __init__.py
│ ├── crud.py
│ ├── schema.py
│ └── views.py
├── fastapi-jsonapi.03.06.2024
├── config.py
├── db.py
├── main.py
├── models
│ ├── __init__.py
│ ├── database.py
│ ├── user.py
│ └── user_bio.py
├── requirements.txt
├── schemas
│ ├── __init__.py
│ ├── user.py
│ └── user_bio.py
└── urls.py
├── fastapi.24.10.2023
├── main.py
├── requirements.txt
└── views
│ ├── __init__.py
│ ├── calc
│ ├── __init__.py
│ ├── crud.py
│ ├── schemas.py
│ └── views.py
│ └── items
│ ├── __init__.py
│ ├── schemas.py
│ └── views.py
├── flask-.21.02.2023
├── main.py
├── requirements.txt
├── templates
│ ├── base.html
│ ├── hello.html
│ ├── index.html
│ └── products
│ │ ├── create.html
│ │ ├── details.html
│ │ └── list.html
├── views
│ ├── __init__.py
│ └── products.py
└── wsgi.py
├── flask-jsonapi.19.08.2021
├── app.py
├── config.py
├── models
│ ├── __init__.py
│ ├── computer.py
│ ├── database.py
│ └── person.py
├── requirements.txt
├── schemas
│ ├── __init__.py
│ ├── computer.py
│ └── person.py
└── views
│ ├── __init__.py
│ ├── computer.py
│ └── person.py
├── flask-jsonapi.20.08.2021
├── app.py
├── config.py
├── models
│ ├── __init__.py
│ ├── computer.py
│ ├── database.py
│ └── person.py
├── requirements.txt
├── schemas
│ ├── __init__.py
│ ├── computer.py
│ └── person.py
└── views
│ ├── __init__.py
│ ├── computer.py
│ ├── permissions
│ ├── __init__.py
│ ├── base.py
│ ├── computer.py
│ └── person.py
│ └── person.py
├── flask.23.05.2022
├── main.py
├── requirements.txt
├── templates
│ ├── base.html
│ └── products
│ │ ├── add.html
│ │ ├── details.html
│ │ └── list.html
└── views
│ ├── __init__.py
│ └── products.py
├── helper-funcs.09.02.2023
├── main.ipynb
└── requirements.txt
├── helper-funcs.21.02.2022
├── main.ipynb
└── requirements.txt
├── interfaces-and-protocols-27.08
├── file.txt
├── interfaces
│ ├── __init__.py
│ ├── light_bulb.py
│ ├── power_switch.py
│ ├── switchable.py
│ └── wireless_mouse.py
├── main.py
├── protocols
│ ├── __init__.py
│ ├── example_message_receiver.py
│ ├── example_readable.py
│ └── example_runtime_checkable_protocol.py
└── requirements.txt
├── oop.18.08.2022
├── step_1.py
├── step_2.py
├── step_3.py
├── step_4.py
└── step_5.py
├── oop.25.04.2023
├── step0.py
├── step1.py
├── step2.py
└── step3.py
├── oop.25.08.2022
├── step_1.py
├── step_2.py
├── step_3.py
├── step_4.py
├── step_5.py
└── step_6.py
├── oop.base.11.06.2020
├── multi_inheritance.py
├── people.py
├── shapes.py
└── vehicles.py
├── pb-and-grpc.07.11.2024
├── README.md
├── address-book.txt
├── client_hello.py
├── common.py
├── demo_pb_address_book.py
├── demo_pb_hello.py
├── hello.txt
├── pb
│ ├── __init__.py
│ ├── address_book_pb2.py
│ ├── address_book_pb2.pyi
│ ├── hello_pb2.py
│ ├── hello_pb2.pyi
│ └── hello_pb2_grpc.py
├── poetry.lock
├── protos
│ ├── address-book.proto
│ └── hello.proto
├── pyproject.toml
└── server_hello.py
└── pytest.18.11.2020
├── api.py
├── solver.py
├── test_api.py
└── test_solver.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
140 | .idea/
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Base Python
2 |
3 | ### Открытые уроки:
4 | - [Основы ООП от 11.06.2020](open-lessons/oop.base.11.06.2020/)
5 | - [Введение в автотесты, pytest от 18.11.2020](open-lessons/pytest.18.11.2020/)
6 | - [Быстрая разработка JSON API приложений на Flask (день 1) от 19.08.2021](open-lessons/flask-jsonapi.19.08.2021/)
7 | - [Быстрая разработка JSON API приложений на Flask (день 2) от 20.08.2021](open-lessons/flask-jsonapi.20.08.2021/)
8 | - [Декораторы в Python от 15.02.2022](open-lessons/decorators.15.02.2022/)
9 | - [Функции-помощники: map, filter, reduce от 21.02.2022](open-lessons/helper-funcs.21.02.2022/)
10 | - [Знакомство с веб-разработкой на Flask от 23.05.2022](open-lessons/flask.23.05.2022/)
11 | - [Основы ООП на Python от 18.08.2022](open-lessons/oop.18.08.2022/)
12 | - [Продвинутый ООП и исключения в Python от 25.08.2022](open-lessons/oop.25.08.2022/)
13 | - [Декораторы в Python от 25.01.2023](open-lessons/decorators.25.01.2023/)
14 | - [Функции-помощники: map, filter, reduce от 09.02.2023](open-lessons/helper-funcs.09.02.2023/)
15 | - [Знакомство с веб разработкой на Flask 21.02.2023](open-lessons/flask-.21.02.2023/)
16 | - [Основы ООП в Python от 25.04.2023](open-lessons/oop.25.04.2023/)
17 | - [Знакомство с декораторами, декораторы для классов, декораторы методов. Занятие от 27.07.2023](open-lessons/decorators.27.07.2023/)
18 | - [Знакомство с FastAPI. Занятие от 24.10.2023](open-lessons/fastapi.24.10.2023/)
19 | - [Знакомство с декораторами. Занятие от 27.12.2023](open-lessons/decorators.27.12.2023/)
20 | - [Декораторы в Python. Занятие от 06.03.2024](open-lessons/decorators.06.03.2024/)
21 | - [FastAPI-JSON:API. Занятие от 03.06.2024](open-lessons/fastapi-jsonapi.03.06.2024/)
22 | - [Шаблоны страниц Jinja в FastAPI приложении. Занятие от 27.06.2024](open-lessons/fastapi-and-jinja-27.06/)
23 | - [Интерфейсы и протоколы в Python. Занятие от 27.08.2024](open-lessons/interfaces-and-protocols-27.08/)
24 | - [Django Class-Based Views. Занятие от 25.09.2024](open-lessons/django-cbv-25.09/)
25 | - [Декораторы в Python и как их аннотировать. Занятие от 30.10.2024](open-lessons/decorators.30.10.2024/)
26 | - [Знакомство с Protobuf и gRPC. Работа с gRPC на Python. Занятие от 07.11.2024](open-lessons/pb-and-grpc.07.11.2024/)
27 | - [Знакомство с FastAPI. Занятие от 27.02.2025](open-lessons/fastapi-intro-27.02.2025/)
28 | - [Абстрактные классы и протоколы в Python. Занятие от 13.05.2025](open-lessons/abc-and-proto-13.05.2025/)
29 | - [Фоновые задачи в FastAPI — Background Tasks. Занятие от 20.05.2025](open-lessons/fastapi-bg-tasks-20.05.2025/)
30 |
31 |
32 | ### Курсы Python Basic:
33 |
34 | - [PythonBasic.2020-07](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2020-07)
35 | - [PythonBasic.2020-11](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2020-11)
36 | - [PythonBasic.2021-02](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2021-02)
37 | - [PythonBasic.2021-05](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2021-05)
38 | - [PythonBasic.2021-08](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2021-08)
39 | - [PythonBasic.2021-11](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2021-11)
40 | - [PythonBasic.2022-02](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2022-02)
41 | - [PythonBasic.2022-05](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2022-05)
42 | - [PythonBasic.2022-08](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2022-08)
43 | - [PythonBasic.2022-11](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2022-11)
44 | - [PythonBasic.2023-02](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2023-02)
45 | - [PythonBasic.2023-05](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2023-05)
46 | - [PythonBasic.2023-08](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2023-08)
47 | - [PythonBasic.2023-11](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2023-11)
48 | - [PythonBasic.2024-01](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2024-01)
49 | - [PythonBasic.2024-03](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2024-03)
50 | - [PythonBasic.2024-05](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2024-05)
51 | - [PythonBasic.2024-07](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2024-07)
52 | - [PythonBasic.2024-09](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2024-09)
53 | - [PythonBasic.2024-11](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2024-11)
54 | - [PythonBasic.2025-01](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2025-01)
55 | - [PythonBasic.2025-03](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2025-03)
56 | - [PythonBasic.2025-05](https://github.com/OtusTeam/BasePython/tree/PythonBasic.2025-05)
57 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/abc-and-proto-13.05.2025/README.md
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/callback_protocol.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import Any, Callable, Protocol
3 |
4 |
5 | def configure_logging(
6 | level: int = logging.INFO,
7 | ) -> None:
8 | logging.basicConfig(
9 | level=level,
10 | datefmt="%Y-%m-%d %H:%M:%S",
11 | format="%(module)10s:%(lineno)-3d %(levelname)-7s - %(message)s",
12 | # format="[%(asctime)s.%(msecs)03d] %(module)10s:%(lineno)-3d %(levelname)-7s - %(message)s",
13 | )
14 |
15 |
16 | log = logging.getLogger(__name__)
17 |
18 |
19 | class MessageCallbackProtocol(Protocol):
20 | __name__: str
21 |
22 | def __call__(self, message: str, sender_id: int, **kwargs: str) -> None: ...
23 |
24 |
25 | def process_message(
26 | message: str,
27 | sender_id: int,
28 | meta: dict[str, str],
29 | callback: MessageCallbackProtocol,
30 | ) -> None:
31 | log.info(
32 | "processing message: %s from sender #%d with meta %s",
33 | message,
34 | sender_id,
35 | meta,
36 | )
37 | ...
38 | log.info("Calling callback %s", callback.__name__)
39 | callback(message, sender_id, **meta)
40 |
41 |
42 | def handle_message(
43 | message: str,
44 | sender_id: int,
45 | **kwargs: str,
46 | ) -> None:
47 | log.info("processing message: %s from sender #%d", message, sender_id)
48 |
49 |
50 | def main() -> None:
51 | configure_logging()
52 | process_message(
53 | "some message",
54 | sender_id=1,
55 | meta={"foo": "bar"},
56 | callback=handle_message,
57 | )
58 |
59 |
60 | if __name__ == "__main__":
61 | main()
62 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/file.txt:
--------------------------------------------------------------------------------
1 | example text file
2 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/interfaces/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/abc-and-proto-13.05.2025/interfaces/__init__.py
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/interfaces/__main__.py:
--------------------------------------------------------------------------------
1 | from interfaces.light_bulb import LightBulb
2 | from interfaces.fan import Fan
3 | from interfaces.power_switch import PowerSwitch
4 |
5 |
6 | def main() -> None:
7 | fan_switch = PowerSwitch(Fan())
8 | fan_switch.toggle()
9 | light_switch = PowerSwitch(LightBulb())
10 | light_switch.toggle()
11 | light_switch.toggle()
12 | fan_switch.toggle()
13 | light_switch.toggle()
14 |
15 |
16 | if __name__ == "__main__":
17 | main()
18 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/interfaces/fan.py:
--------------------------------------------------------------------------------
1 | from interfaces.switchable import Switchable
2 |
3 |
4 | class Fan(Switchable):
5 | def on(self) -> None:
6 | print("turned on fan", self)
7 |
8 | def off(self) -> None:
9 | print("turned off fan", self)
10 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/interfaces/light_bulb.py:
--------------------------------------------------------------------------------
1 | from interfaces.switchable import Switchable
2 |
3 |
4 | class LightBulb(Switchable):
5 | def on(self) -> None:
6 | print("turned on light bulb", self)
7 |
8 | def off(self) -> None:
9 | print("turned off light bulb", self)
10 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/interfaces/power_switch.py:
--------------------------------------------------------------------------------
1 | from interfaces.switchable import Switchable
2 |
3 |
4 | class PowerSwitch:
5 | def __init__(self, client: Switchable) -> None:
6 | self.client = client
7 | self.on = False
8 | self.client.off()
9 |
10 | def toggle(self) -> None:
11 | if self.on:
12 | self.client.off()
13 | else:
14 | self.client.on()
15 |
16 | self.on = not self.on
17 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/interfaces/switchable.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 |
3 |
4 | class Switchable(ABC):
5 | @abstractmethod
6 | def on(self) -> None:
7 | raise NotImplementedError
8 |
9 | @abstractmethod
10 | def off(self) -> None:
11 | raise NotImplementedError
12 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/main.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Protocol
3 |
4 | import requests
5 |
6 |
7 | class AbstractUser(Protocol):
8 | username: str
9 | email: str | None
10 | age: int
11 |
12 | def increase_age(self) -> None: ...
13 |
14 |
15 | class User(AbstractUser):
16 |
17 | def __init__(self, username: str, email: str | None, age: int) -> None:
18 | self.username = username
19 | self.email = email
20 | self.age = age
21 |
22 | def increase_age(self) -> None:
23 | self.age += 1
24 |
25 |
26 | class UserFetcher(ABC):
27 | @abstractmethod
28 | def fetch_users(self, count: int = 1) -> list[AbstractUser]: ...
29 |
30 |
31 | class PlainUserFetcher(UserFetcher):
32 | def fetch_users(self, count: int = 1) -> list[AbstractUser]:
33 | return [
34 | User(
35 | username="username",
36 | email="user@email.com",
37 | age=18,
38 | )
39 | for _ in range(count)
40 | ]
41 |
42 |
43 | class RandomUserFetcher(UserFetcher):
44 | API_URL = "https://randomuser.me/api/"
45 |
46 | def get_random_user(self) -> AbstractUser:
47 | data = requests.get(self.API_URL).json()
48 | user_data = data["results"][0]
49 | return User(
50 | username=user_data["login"]["username"],
51 | email=user_data["email"],
52 | age=user_data["dob"]["age"],
53 | )
54 |
55 | def fetch_users(self, count: int = 1) -> list[AbstractUser]:
56 | return [self.get_random_user() for _ in range(count)]
57 |
58 |
59 | def get_and_show_users(fetcher: UserFetcher, count: int) -> None:
60 | users = fetcher.fetch_users(count=count)
61 | for user in users:
62 | print("@", user.username, user.email, user.age)
63 | user.increase_age()
64 |
65 |
66 | def main() -> None:
67 | # user = User(
68 | # username="username",
69 | # email="user@email.com",
70 | # age=18,
71 | # )
72 | # print(user)
73 | plain_user_fetcher = PlainUserFetcher()
74 | get_and_show_users(plain_user_fetcher, count=2)
75 | random_user_fetcher = RandomUserFetcher()
76 | get_and_show_users(random_user_fetcher, count=3)
77 |
78 |
79 | if __name__ == "__main__":
80 | main()
81 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/protocols/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/abc-and-proto-13.05.2025/protocols/__init__.py
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/protocols/example_readable.py:
--------------------------------------------------------------------------------
1 | import random
2 | import string
3 | from typing import Protocol
4 |
5 | FILE_PATH = "/Users/suren/MyFiles/edu/OTUS/open-lessons/pb-abc-and-proto-13.05/file.txt"
6 |
7 |
8 | class Readable(Protocol):
9 | def read(self) -> bytes: ...
10 |
11 |
12 | class ApiClient(Readable):
13 | def __init__(self, url: str) -> None:
14 | self.url = url
15 |
16 | def api_call(self) -> None:
17 | print("calling api self", self)
18 |
19 | def read(self) -> bytes:
20 | return f"reading api, {self.url}".encode()
21 |
22 |
23 | class RandomReader:
24 | def read(self) -> bytes:
25 | line = str(self) + "".join(random.choices(string.ascii_letters, k=10))
26 | return line.encode()
27 |
28 |
29 | def stream_data(filelike: Readable) -> None:
30 | print("streaming data", filelike.read())
31 |
32 |
33 | def main() -> None:
34 | api = ApiClient("https://example.com/api-resource")
35 |
36 | random_reader = RandomReader()
37 | stream_data(api)
38 | stream_data(random_reader)
39 |
40 | with open(FILE_PATH, "rb") as file:
41 | stream_data(file)
42 |
43 |
44 | if __name__ == "__main__":
45 | main()
46 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "pb-abc-and-proto-13-05"
3 | version = "0.1.0"
4 | description = "Add your description here"
5 | readme = "README.md"
6 | requires-python = ">=3.13"
7 | dependencies = [
8 | "mypy>=1.15.0",
9 | "requests>=2.32.3",
10 | ]
11 |
12 | [tool.mypy]
13 | strict = true
14 |
--------------------------------------------------------------------------------
/open-lessons/abc-and-proto-13.05.2025/uv.lock:
--------------------------------------------------------------------------------
1 | version = 1
2 | revision = 1
3 | requires-python = ">=3.13"
4 |
5 | [[package]]
6 | name = "certifi"
7 | version = "2025.4.26"
8 | source = { registry = "https://pypi.org/simple" }
9 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 }
10 | wheels = [
11 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 },
12 | ]
13 |
14 | [[package]]
15 | name = "charset-normalizer"
16 | version = "3.4.2"
17 | source = { registry = "https://pypi.org/simple" }
18 | sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 }
19 | wheels = [
20 | { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 },
21 | { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 },
22 | { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 },
23 | { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 },
24 | { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 },
25 | { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 },
26 | { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 },
27 | { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 },
28 | { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 },
29 | { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 },
30 | { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 },
31 | { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 },
32 | { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 },
33 | { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 },
34 | ]
35 |
36 | [[package]]
37 | name = "idna"
38 | version = "3.10"
39 | source = { registry = "https://pypi.org/simple" }
40 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
41 | wheels = [
42 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
43 | ]
44 |
45 | [[package]]
46 | name = "mypy"
47 | version = "1.15.0"
48 | source = { registry = "https://pypi.org/simple" }
49 | dependencies = [
50 | { name = "mypy-extensions" },
51 | { name = "typing-extensions" },
52 | ]
53 | sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 }
54 | wheels = [
55 | { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 },
56 | { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 },
57 | { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 },
58 | { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 },
59 | { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 },
60 | { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 },
61 | { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 },
62 | ]
63 |
64 | [[package]]
65 | name = "mypy-extensions"
66 | version = "1.1.0"
67 | source = { registry = "https://pypi.org/simple" }
68 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
69 | wheels = [
70 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
71 | ]
72 |
73 | [[package]]
74 | name = "pb-abc-and-proto-13-05"
75 | version = "0.1.0"
76 | source = { virtual = "." }
77 | dependencies = [
78 | { name = "mypy" },
79 | { name = "requests" },
80 | ]
81 |
82 | [package.metadata]
83 | requires-dist = [
84 | { name = "mypy", specifier = ">=1.15.0" },
85 | { name = "requests", specifier = ">=2.32.3" },
86 | ]
87 |
88 | [[package]]
89 | name = "requests"
90 | version = "2.32.3"
91 | source = { registry = "https://pypi.org/simple" }
92 | dependencies = [
93 | { name = "certifi" },
94 | { name = "charset-normalizer" },
95 | { name = "idna" },
96 | { name = "urllib3" },
97 | ]
98 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
99 | wheels = [
100 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
101 | ]
102 |
103 | [[package]]
104 | name = "typing-extensions"
105 | version = "4.13.2"
106 | source = { registry = "https://pypi.org/simple" }
107 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
108 | wheels = [
109 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
110 | ]
111 |
112 | [[package]]
113 | name = "urllib3"
114 | version = "2.4.0"
115 | source = { registry = "https://pypi.org/simple" }
116 | sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
117 | wheels = [
118 | { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
119 | ]
120 |
--------------------------------------------------------------------------------
/open-lessons/decorators.06.03.2024/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==4.3.0
2 | appnope==0.1.4
3 | argon2-cffi==23.1.0
4 | argon2-cffi-bindings==21.2.0
5 | arrow==1.3.0
6 | asttokens==2.4.1
7 | async-lru==2.0.4
8 | attrs==23.2.0
9 | Babel==2.14.0
10 | beautifulsoup4==4.12.3
11 | bleach==6.1.0
12 | certifi==2024.2.2
13 | cffi==1.16.0
14 | charset-normalizer==3.3.2
15 | comm==0.2.1
16 | debugpy==1.8.1
17 | decorator==5.1.1
18 | defusedxml==0.7.1
19 | executing==2.0.1
20 | fastjsonschema==2.19.1
21 | fqdn==1.5.1
22 | h11==0.14.0
23 | httpcore==1.0.4
24 | httpx==0.27.0
25 | idna==3.6
26 | ipykernel==6.29.3
27 | ipython==8.22.2
28 | isoduration==20.11.0
29 | jedi==0.19.1
30 | Jinja2==3.1.3
31 | json5==0.9.20
32 | jsonpointer==2.4
33 | jsonschema==4.21.1
34 | jsonschema-specifications==2023.12.1
35 | jupyter-events==0.9.0
36 | jupyter-lsp==2.2.4
37 | jupyter_client==8.6.0
38 | jupyter_core==5.7.1
39 | jupyter_server==2.13.0
40 | jupyter_server_terminals==0.5.2
41 | jupyterlab==4.1.3
42 | jupyterlab_pygments==0.3.0
43 | jupyterlab_server==2.25.3
44 | MarkupSafe==2.1.5
45 | matplotlib-inline==0.1.6
46 | mistune==3.0.2
47 | nbclient==0.9.0
48 | nbconvert==7.16.2
49 | nbformat==5.9.2
50 | nest-asyncio==1.6.0
51 | notebook==7.1.1
52 | notebook_shim==0.2.4
53 | overrides==7.7.0
54 | packaging==23.2
55 | pandocfilters==1.5.1
56 | parso==0.8.3
57 | pexpect==4.9.0
58 | platformdirs==4.2.0
59 | prometheus_client==0.20.0
60 | prompt-toolkit==3.0.43
61 | psutil==5.9.8
62 | ptyprocess==0.7.0
63 | pure-eval==0.2.2
64 | pycparser==2.21
65 | Pygments==2.17.2
66 | python-dateutil==2.9.0.post0
67 | python-json-logger==2.0.7
68 | PyYAML==6.0.1
69 | pyzmq==25.1.2
70 | referencing==0.33.0
71 | requests==2.31.0
72 | rfc3339-validator==0.1.4
73 | rfc3986-validator==0.1.1
74 | rpds-py==0.18.0
75 | Send2Trash==1.8.2
76 | six==1.16.0
77 | sniffio==1.3.1
78 | soupsieve==2.5
79 | stack-data==0.6.3
80 | terminado==0.18.0
81 | tinycss2==1.2.1
82 | tornado==6.4
83 | traitlets==5.14.1
84 | types-python-dateutil==2.8.19.20240106
85 | uri-template==1.3.0
86 | urllib3==2.2.1
87 | wcwidth==0.2.13
88 | webcolors==1.13
89 | webencodings==0.5.1
90 | websocket-client==1.7.0
91 |
--------------------------------------------------------------------------------
/open-lessons/decorators.15.02.2022/requirements.txt:
--------------------------------------------------------------------------------
1 | appnope==0.1.2
2 | argon2-cffi==21.3.0
3 | argon2-cffi-bindings==21.2.0
4 | asttokens==2.0.5
5 | attrs==21.4.0
6 | backcall==0.2.0
7 | black==22.1.0
8 | bleach==4.1.0
9 | cffi==1.15.0
10 | click==8.0.3
11 | debugpy==1.5.1
12 | decorator==5.1.1
13 | defusedxml==0.7.1
14 | entrypoints==0.4
15 | executing==0.8.2
16 | ipykernel==6.9.1
17 | ipython==8.0.1
18 | ipython-genutils==0.2.0
19 | jedi==0.18.1
20 | Jinja2==3.0.3
21 | jsonschema==4.4.0
22 | jupyter-client==7.1.2
23 | jupyter-core==4.9.1
24 | jupyterlab-pygments==0.1.2
25 | MarkupSafe==2.0.1
26 | matplotlib-inline==0.1.3
27 | mistune==0.8.4
28 | mypy-extensions==0.4.3
29 | nbclient==0.5.11
30 | nbconvert==6.4.2
31 | nbformat==5.1.3
32 | nest-asyncio==1.5.4
33 | notebook==6.4.8
34 | packaging==21.3
35 | pandocfilters==1.5.0
36 | parso==0.8.3
37 | pathspec==0.9.0
38 | pexpect==4.8.0
39 | pickleshare==0.7.5
40 | platformdirs==2.5.0
41 | prometheus-client==0.13.1
42 | prompt-toolkit==3.0.28
43 | ptyprocess==0.7.0
44 | pure-eval==0.2.2
45 | pycparser==2.21
46 | Pygments==2.11.2
47 | pyparsing==3.0.7
48 | pyrsistent==0.18.1
49 | python-dateutil==2.8.2
50 | pyzmq==22.3.0
51 | Send2Trash==1.8.0
52 | six==1.16.0
53 | stack-data==0.2.0
54 | terminado==0.13.1
55 | testpath==0.5.0
56 | tomli==2.0.1
57 | tornado==6.1
58 | traitlets==5.1.1
59 | typing_extensions==4.1.1
60 | wcwidth==0.2.5
61 | webencodings==0.5.1
62 |
--------------------------------------------------------------------------------
/open-lessons/decorators.25.01.2023/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==3.6.2
2 | appnope==0.1.3
3 | argon2-cffi==21.3.0
4 | argon2-cffi-bindings==21.2.0
5 | arrow==1.2.3
6 | asttokens==2.2.1
7 | attrs==22.2.0
8 | backcall==0.2.0
9 | beautifulsoup4==4.11.1
10 | bleach==6.0.0
11 | cffi==1.15.1
12 | comm==0.1.2
13 | debugpy==1.6.6
14 | decorator==5.1.1
15 | defusedxml==0.7.1
16 | entrypoints==0.4
17 | executing==1.2.0
18 | fastjsonschema==2.16.2
19 | fqdn==1.5.1
20 | idna==3.4
21 | ipykernel==6.20.2
22 | ipython==8.8.0
23 | ipython-genutils==0.2.0
24 | ipywidgets==8.0.4
25 | isoduration==20.11.0
26 | jedi==0.18.2
27 | Jinja2==3.1.2
28 | jsonpointer==2.3
29 | jsonschema==4.17.3
30 | jupyter==1.0.0
31 | jupyter-console==6.4.4
32 | jupyter-events==0.6.3
33 | jupyter_client==7.4.9
34 | jupyter_core==5.1.5
35 | jupyter_server==2.1.0
36 | jupyter_server_terminals==0.4.4
37 | jupyterlab-pygments==0.2.2
38 | jupyterlab-widgets==3.0.5
39 | MarkupSafe==2.1.2
40 | matplotlib-inline==0.1.6
41 | mistune==2.0.4
42 | nbclassic==0.4.8
43 | nbclient==0.7.2
44 | nbconvert==7.2.9
45 | nbformat==5.7.3
46 | nest-asyncio==1.5.6
47 | notebook==6.5.2
48 | notebook_shim==0.2.2
49 | packaging==23.0
50 | pandocfilters==1.5.0
51 | parso==0.8.3
52 | pexpect==4.8.0
53 | pickleshare==0.7.5
54 | platformdirs==2.6.2
55 | prometheus-client==0.16.0
56 | prompt-toolkit==3.0.36
57 | psutil==5.9.4
58 | ptyprocess==0.7.0
59 | pure-eval==0.2.2
60 | pycparser==2.21
61 | Pygments==2.14.0
62 | pyrsistent==0.19.3
63 | python-dateutil==2.8.2
64 | python-json-logger==2.0.4
65 | PyYAML==6.0
66 | pyzmq==25.0.0
67 | qtconsole==5.4.0
68 | QtPy==2.3.0
69 | rfc3339-validator==0.1.4
70 | rfc3986-validator==0.1.1
71 | Send2Trash==1.8.0
72 | six==1.16.0
73 | sniffio==1.3.0
74 | soupsieve==2.3.2.post1
75 | stack-data==0.6.2
76 | terminado==0.17.1
77 | tinycss2==1.2.1
78 | tornado==6.2
79 | traitlets==5.8.1
80 | uri-template==1.2.0
81 | wcwidth==0.2.6
82 | webcolors==1.12
83 | webencodings==0.5.1
84 | websocket-client==1.4.2
85 | widgetsnbextension==4.0.5
86 |
--------------------------------------------------------------------------------
/open-lessons/decorators.27.07.2023/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==3.7.1
2 | appnope==0.1.3
3 | argon2-cffi==21.3.0
4 | argon2-cffi-bindings==21.2.0
5 | arrow==1.2.3
6 | asttokens==2.2.1
7 | async-lru==2.0.3
8 | attrs==23.1.0
9 | Babel==2.12.1
10 | backcall==0.2.0
11 | beautifulsoup4==4.12.2
12 | bleach==6.0.0
13 | certifi==2023.7.22
14 | cffi==1.15.1
15 | charset-normalizer==3.2.0
16 | comm==0.1.3
17 | debugpy==1.6.7
18 | decorator==5.1.1
19 | defusedxml==0.7.1
20 | executing==1.2.0
21 | fastjsonschema==2.18.0
22 | fqdn==1.5.1
23 | idna==3.4
24 | ipykernel==6.25.0
25 | ipython==8.14.0
26 | isoduration==20.11.0
27 | jedi==0.18.2
28 | Jinja2==3.1.2
29 | json5==0.9.14
30 | jsonpointer==2.4
31 | jsonschema==4.18.4
32 | jsonschema-specifications==2023.7.1
33 | jupyter-events==0.6.3
34 | jupyter-lsp==2.2.0
35 | jupyter_client==8.3.0
36 | jupyter_core==5.3.1
37 | jupyter_server==2.7.0
38 | jupyter_server_terminals==0.4.4
39 | jupyterlab==4.0.3
40 | jupyterlab-pygments==0.2.2
41 | jupyterlab_server==2.24.0
42 | MarkupSafe==2.1.3
43 | matplotlib-inline==0.1.6
44 | mistune==3.0.1
45 | nbclient==0.8.0
46 | nbconvert==7.7.3
47 | nbformat==5.9.1
48 | nest-asyncio==1.5.6
49 | notebook==7.0.0
50 | notebook_shim==0.2.3
51 | overrides==7.3.1
52 | packaging==23.1
53 | pandocfilters==1.5.0
54 | parso==0.8.3
55 | pexpect==4.8.0
56 | pickleshare==0.7.5
57 | platformdirs==3.9.1
58 | prometheus-client==0.17.1
59 | prompt-toolkit==3.0.39
60 | psutil==5.9.5
61 | ptyprocess==0.7.0
62 | pure-eval==0.2.2
63 | pycparser==2.21
64 | Pygments==2.15.1
65 | python-dateutil==2.8.2
66 | python-json-logger==2.0.7
67 | PyYAML==6.0.1
68 | pyzmq==25.1.0
69 | referencing==0.30.0
70 | requests==2.31.0
71 | rfc3339-validator==0.1.4
72 | rfc3986-validator==0.1.1
73 | rpds-py==0.9.2
74 | Send2Trash==1.8.2
75 | six==1.16.0
76 | sniffio==1.3.0
77 | soupsieve==2.4.1
78 | stack-data==0.6.2
79 | terminado==0.17.1
80 | tinycss2==1.2.1
81 | tornado==6.3.2
82 | traitlets==5.9.0
83 | uri-template==1.3.0
84 | urllib3==2.0.4
85 | wcwidth==0.2.6
86 | webcolors==1.13
87 | webencodings==0.5.1
88 | websocket-client==1.6.1
89 |
--------------------------------------------------------------------------------
/open-lessons/decorators.27.12.2023/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==4.2.0
2 | appnope==0.1.3
3 | argon2-cffi==23.1.0
4 | argon2-cffi-bindings==21.2.0
5 | arrow==1.3.0
6 | asttokens==2.4.1
7 | async-lru==2.0.4
8 | attrs==23.1.0
9 | Babel==2.14.0
10 | beautifulsoup4==4.12.2
11 | bleach==6.1.0
12 | certifi==2023.11.17
13 | cffi==1.16.0
14 | charset-normalizer==3.3.2
15 | comm==0.2.0
16 | debugpy==1.8.0
17 | decorator==5.1.1
18 | defusedxml==0.7.1
19 | executing==2.0.1
20 | fastjsonschema==2.19.0
21 | fqdn==1.5.1
22 | idna==3.6
23 | ipykernel==6.28.0
24 | ipython==8.19.0
25 | isoduration==20.11.0
26 | jedi==0.19.1
27 | Jinja2==3.1.2
28 | json5==0.9.14
29 | jsonpointer==2.4
30 | jsonschema==4.20.0
31 | jsonschema-specifications==2023.12.1
32 | jupyter-events==0.9.0
33 | jupyter-lsp==2.2.1
34 | jupyter_client==8.6.0
35 | jupyter_core==5.6.0
36 | jupyter_server==2.12.1
37 | jupyter_server_terminals==0.5.1
38 | jupyterlab==4.0.9
39 | jupyterlab_pygments==0.3.0
40 | jupyterlab_server==2.25.2
41 | MarkupSafe==2.1.3
42 | matplotlib-inline==0.1.6
43 | mistune==3.0.2
44 | nbclient==0.9.0
45 | nbconvert==7.13.1
46 | nbformat==5.9.2
47 | nest-asyncio==1.5.8
48 | notebook==7.0.6
49 | notebook_shim==0.2.3
50 | overrides==7.4.0
51 | packaging==23.2
52 | pandocfilters==1.5.0
53 | parso==0.8.3
54 | pexpect==4.9.0
55 | platformdirs==4.1.0
56 | prometheus-client==0.19.0
57 | prompt-toolkit==3.0.43
58 | psutil==5.9.7
59 | ptyprocess==0.7.0
60 | pure-eval==0.2.2
61 | pycparser==2.21
62 | Pygments==2.17.2
63 | python-dateutil==2.8.2
64 | python-json-logger==2.0.7
65 | PyYAML==6.0.1
66 | pyzmq==25.1.2
67 | referencing==0.32.0
68 | requests==2.31.0
69 | rfc3339-validator==0.1.4
70 | rfc3986-validator==0.1.1
71 | rpds-py==0.15.2
72 | Send2Trash==1.8.2
73 | six==1.16.0
74 | sniffio==1.3.0
75 | soupsieve==2.5
76 | stack-data==0.6.3
77 | terminado==0.18.0
78 | tinycss2==1.2.1
79 | tornado==6.4
80 | traitlets==5.14.0
81 | types-python-dateutil==2.8.19.14
82 | uri-template==1.3.0
83 | urllib3==2.1.0
84 | wcwidth==0.2.12
85 | webcolors==1.13
86 | webencodings==0.5.1
87 | websocket-client==1.7.0
88 |
--------------------------------------------------------------------------------
/open-lessons/decorators.30.10.2024/example_0.py:
--------------------------------------------------------------------------------
1 | from typing import TypeVar, reveal_type
2 |
3 | IntOrFloat = TypeVar("IntOrFloat", float, int)
4 |
5 |
6 | def cube(num: IntOrFloat) -> IntOrFloat:
7 | return num**3
8 |
9 |
10 | def main() -> None:
11 | c1 = cube(5)
12 | reveal_type(c1)
13 | print(c1)
14 |
15 | c2 = cube(2.5)
16 | reveal_type(c2)
17 | print(c2)
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
--------------------------------------------------------------------------------
/open-lessons/decorators.30.10.2024/main.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 | from typing import TypeVar, reveal_type, Sized, Callable, ParamSpec
3 |
4 | IntOrFloat = TypeVar("IntOrFloat", float, int)
5 |
6 | ReturnType = TypeVar("ReturnType")
7 | P = ParamSpec("P")
8 |
9 |
10 | def announced(func: Callable[P, ReturnType]) -> Callable[P, ReturnType]:
11 | print("creating announced function", func)
12 |
13 | @wraps(func)
14 | def new_announced(*args: P.args, **kwargs: P.kwargs) -> ReturnType:
15 | print("run function", func, "args:", args, "kwargs:", kwargs)
16 | result = func(*args, **kwargs)
17 | print("done function", func, "res:", result)
18 | return result
19 |
20 | print("successfully created announced func for", func, "announced:", new_announced)
21 | return new_announced
22 |
23 |
24 | @announced
25 | def cube(num: IntOrFloat) -> IntOrFloat:
26 | return num ** 3
27 |
28 |
29 | @announced
30 | def power(num: IntOrFloat, exponent: int) -> IntOrFloat:
31 | val: IntOrFloat = num ** exponent
32 | return val
33 |
34 |
35 | @announced
36 | def many_str(line: str, times: int) -> str:
37 | return line * times
38 |
39 |
40 | @announced
41 | def get_size(obj: Sized) -> int:
42 | return len(obj)
43 |
44 |
45 | def main() -> None:
46 | c1 = cube(5)
47 | reveal_type(c1)
48 | print("print:", c1)
49 |
50 | c2 = cube(2.5)
51 | reveal_type(c2)
52 | print("print:", c2)
53 | #
54 | p = power(2, 10)
55 | reveal_type(p)
56 | print("print:", p)
57 | p2 = power(num=3, exponent=5)
58 | reveal_type(p2)
59 | print("print:", p2)
60 | line = many_str("foobar", 3)
61 | reveal_type(line)
62 | print("print:", line)
63 | size = get_size({"spam", "eggs"})
64 | reveal_type(size)
65 | print("print:", size)
66 |
67 |
68 | if __name__ == '__main__':
69 | main()
70 |
--------------------------------------------------------------------------------
/open-lessons/decorators.30.10.2024/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.mypy]
2 | strict = true
3 |
--------------------------------------------------------------------------------
/open-lessons/decorators.30.10.2024/requirements.txt:
--------------------------------------------------------------------------------
1 | anyio==4.6.2.post1
2 | appnope==0.1.4
3 | argon2-cffi==23.1.0
4 | argon2-cffi-bindings==21.2.0
5 | arrow==1.3.0
6 | asttokens==2.4.1
7 | async-lru==2.0.4
8 | attrs==24.2.0
9 | babel==2.16.0
10 | beautifulsoup4==4.12.3
11 | bleach==6.2.0
12 | certifi==2024.8.30
13 | cffi==1.17.1
14 | charset-normalizer==3.4.0
15 | comm==0.2.2
16 | debugpy==1.8.7
17 | decorator==5.1.1
18 | defusedxml==0.7.1
19 | executing==2.1.0
20 | fastjsonschema==2.20.0
21 | fqdn==1.5.1
22 | h11==0.14.0
23 | httpcore==1.0.6
24 | httpx==0.27.2
25 | idna==3.10
26 | ipykernel==6.29.5
27 | ipython==8.29.0
28 | isoduration==20.11.0
29 | jedi==0.19.1
30 | Jinja2==3.1.4
31 | json5==0.9.25
32 | jsonpointer==3.0.0
33 | jsonschema==4.23.0
34 | jsonschema-specifications==2024.10.1
35 | jupyter-events==0.10.0
36 | jupyter-lsp==2.2.5
37 | jupyter_client==8.6.3
38 | jupyter_core==5.7.2
39 | jupyter_server==2.14.2
40 | jupyter_server_terminals==0.5.3
41 | jupyterlab==4.2.5
42 | jupyterlab_pygments==0.3.0
43 | jupyterlab_server==2.27.3
44 | MarkupSafe==3.0.2
45 | matplotlib-inline==0.1.7
46 | mistune==3.0.2
47 | mypy==1.13.0
48 | mypy-extensions==1.0.0
49 | nbclient==0.10.0
50 | nbconvert==7.16.4
51 | nbformat==5.10.4
52 | nest-asyncio==1.6.0
53 | notebook==7.2.2
54 | notebook_shim==0.2.4
55 | overrides==7.7.0
56 | packaging==24.1
57 | pandocfilters==1.5.1
58 | parso==0.8.4
59 | pexpect==4.9.0
60 | platformdirs==4.3.6
61 | prometheus_client==0.21.0
62 | prompt_toolkit==3.0.48
63 | psutil==6.1.0
64 | ptyprocess==0.7.0
65 | pure_eval==0.2.3
66 | pycparser==2.22
67 | Pygments==2.18.0
68 | python-dateutil==2.9.0.post0
69 | python-json-logger==2.0.7
70 | PyYAML==6.0.2
71 | pyzmq==26.2.0
72 | referencing==0.35.1
73 | requests==2.32.3
74 | rfc3339-validator==0.1.4
75 | rfc3986-validator==0.1.1
76 | rpds-py==0.20.0
77 | Send2Trash==1.8.3
78 | setuptools==75.3.0
79 | six==1.16.0
80 | sniffio==1.3.1
81 | soupsieve==2.6
82 | stack-data==0.6.3
83 | terminado==0.18.1
84 | tinycss2==1.4.0
85 | tornado==6.4.1
86 | traitlets==5.14.3
87 | types-python-dateutil==2.9.0.20241003
88 | typing_extensions==4.12.2
89 | uri-template==1.3.0
90 | urllib3==2.2.3
91 | wcwidth==0.2.13
92 | webcolors==24.8.0
93 | webencodings==0.5.1
94 | websocket-client==1.8.0
95 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/README.md:
--------------------------------------------------------------------------------
1 | # Django Class Based Views Examples
2 |
3 | Init poetry:
4 |
5 | ```shell
6 | poetry init --no-interaction
7 | ```
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "asgiref"
5 | version = "3.8.1"
6 | description = "ASGI specs, helper code, and adapters"
7 | optional = false
8 | python-versions = ">=3.8"
9 | files = [
10 | {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
11 | {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
12 | ]
13 |
14 | [package.extras]
15 | tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
16 |
17 | [[package]]
18 | name = "django"
19 | version = "5.1.1"
20 | description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
21 | optional = false
22 | python-versions = ">=3.10"
23 | files = [
24 | {file = "Django-5.1.1-py3-none-any.whl", hash = "sha256:71603f27dac22a6533fb38d83072eea9ddb4017fead6f67f2562a40402d61c3f"},
25 | {file = "Django-5.1.1.tar.gz", hash = "sha256:021ffb7fdab3d2d388bc8c7c2434eb9c1f6f4d09e6119010bbb1694dda286bc2"},
26 | ]
27 |
28 | [package.dependencies]
29 | asgiref = ">=3.8.1,<4"
30 | sqlparse = ">=0.3.1"
31 | tzdata = {version = "*", markers = "sys_platform == \"win32\""}
32 |
33 | [package.extras]
34 | argon2 = ["argon2-cffi (>=19.1.0)"]
35 | bcrypt = ["bcrypt"]
36 |
37 | [[package]]
38 | name = "sqlparse"
39 | version = "0.5.1"
40 | description = "A non-validating SQL parser."
41 | optional = false
42 | python-versions = ">=3.8"
43 | files = [
44 | {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
45 | {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
46 | ]
47 |
48 | [package.extras]
49 | dev = ["build", "hatch"]
50 | doc = ["sphinx"]
51 |
52 | [[package]]
53 | name = "tzdata"
54 | version = "2024.2"
55 | description = "Provider of IANA time zone data"
56 | optional = false
57 | python-versions = ">=2"
58 | files = [
59 | {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
60 | {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
61 | ]
62 |
63 | [metadata]
64 | lock-version = "2.0"
65 | python-versions = "^3.12"
66 | content-hash = "319a0ba62510a6c5bc6f5a861990a2bf9a827331daa8563ce5d8dcf426fefd39"
67 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | package-mode = false
3 |
4 | [tool.poetry.dependencies]
5 | python = "^3.12"
6 | django = "^5.1.1"
7 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "task_manager.settings")
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/task_manager/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/django-cbv-25.09/task_manager/task_manager/__init__.py
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/task_manager/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for task_manager project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "task_manager.settings")
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/task_manager/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for task_manager project.
3 |
4 | Generated by 'django-admin startproject' using Django 5.1.1.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/5.1/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 |
15 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
16 | BASE_DIR = Path(__file__).resolve().parent.parent
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = "django-insecure-o%@r8k&sl5@1xak!asdkm3knxwtebfk)u)xl&6(%^4vrs(g@h="
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | "django.contrib.admin",
35 | "django.contrib.auth",
36 | "django.contrib.contenttypes",
37 | "django.contrib.sessions",
38 | "django.contrib.messages",
39 | "django.contrib.staticfiles",
40 | #
41 | "tasks.apps.TasksConfig",
42 | ]
43 |
44 | MIDDLEWARE = [
45 | "django.middleware.security.SecurityMiddleware",
46 | "django.contrib.sessions.middleware.SessionMiddleware",
47 | "django.middleware.common.CommonMiddleware",
48 | "django.middleware.csrf.CsrfViewMiddleware",
49 | "django.contrib.auth.middleware.AuthenticationMiddleware",
50 | "django.contrib.messages.middleware.MessageMiddleware",
51 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
52 | ]
53 |
54 | ROOT_URLCONF = "task_manager.urls"
55 |
56 | TEMPLATES = [
57 | {
58 | "BACKEND": "django.template.backends.django.DjangoTemplates",
59 | "DIRS": [BASE_DIR / "templates"],
60 | "APP_DIRS": True,
61 | "OPTIONS": {
62 | "context_processors": [
63 | "django.template.context_processors.debug",
64 | "django.template.context_processors.request",
65 | "django.contrib.auth.context_processors.auth",
66 | "django.contrib.messages.context_processors.messages",
67 | ],
68 | },
69 | },
70 | ]
71 |
72 | WSGI_APPLICATION = "task_manager.wsgi.application"
73 |
74 |
75 | # Database
76 | # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
77 |
78 | DATABASES = {
79 | "default": {
80 | "ENGINE": "django.db.backends.sqlite3",
81 | "NAME": BASE_DIR / "db.sqlite3",
82 | }
83 | }
84 |
85 |
86 | # Password validation
87 | # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
88 |
89 | AUTH_PASSWORD_VALIDATORS = [
90 | {
91 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
92 | },
93 | {
94 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
95 | },
96 | {
97 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
98 | },
99 | {
100 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
101 | },
102 | ]
103 |
104 |
105 | # Internationalization
106 | # https://docs.djangoproject.com/en/5.1/topics/i18n/
107 |
108 | LANGUAGE_CODE = "en-us"
109 |
110 | TIME_ZONE = "UTC"
111 |
112 | USE_I18N = True
113 |
114 | USE_TZ = True
115 |
116 |
117 | # Static files (CSS, JavaScript, Images)
118 | # https://docs.djangoproject.com/en/5.1/howto/static-files/
119 |
120 | STATIC_URL = "static/"
121 |
122 | # Default primary key field type
123 | # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
124 |
125 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
126 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/task_manager/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | URL configuration for task_manager project.
3 |
4 | The `urlpatterns` list routes URLs to views. For more information please see:
5 | https://docs.djangoproject.com/en/5.1/topics/http/urls/
6 | Examples:
7 | Function views
8 | 1. Add an import: from my_app import views
9 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
10 | Class-based views
11 | 1. Add an import: from other_app.views import Home
12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13 | Including another URLconf
14 | 1. Import the include() function: from django.urls import include, path
15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16 | """
17 |
18 | from django.contrib import admin
19 | from django.urls import path, include
20 | from django.views.generic import TemplateView
21 |
22 |
23 | urlpatterns = [
24 | path("", TemplateView.as_view(template_name="index.html"), name="index"),
25 | path("about/", TemplateView.as_view(template_name="about.html"), name="about"),
26 | path("tasks/", include("tasks.urls")),
27 | path("admin/", admin.site.urls),
28 | ]
29 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/task_manager/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for task_manager project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "task_manager.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/django-cbv-25.09/task_manager/tasks/__init__.py
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import Task
4 |
5 |
6 | @admin.register(Task)
7 | class TaskAdmin(admin.ModelAdmin):
8 | list_display = "id", "title", "description", "done"
9 | list_display_links = "id", "title",
10 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class TasksConfig(AppConfig):
5 | default_auto_field = "django.db.models.BigAutoField"
6 | name = "tasks"
7 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.1 on 2024-09-25 17:41
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | initial = True
9 |
10 | dependencies = []
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name="Task",
15 | fields=[
16 | (
17 | "id",
18 | models.BigAutoField(
19 | auto_created=True,
20 | primary_key=True,
21 | serialize=False,
22 | verbose_name="ID",
23 | ),
24 | ),
25 | ("title", models.CharField(default="", max_length=200)),
26 | ("done", models.BooleanField(default=False)),
27 | ],
28 | options={
29 | "ordering": ("id",),
30 | },
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/migrations/0002_task_description.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.1 on 2024-09-25 18:03
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("tasks", "0001_initial"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="task",
15 | name="description",
16 | field=models.TextField(blank=True, db_default="", default=""),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/django-cbv-25.09/task_manager/tasks/migrations/__init__.py
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.shortcuts import reverse
3 |
4 |
5 | class Task(models.Model):
6 |
7 | class Meta:
8 | ordering = ("id", )
9 |
10 | title = models.CharField(max_length=200, default="", null=False)
11 | description = models.TextField(default="", db_default="", blank=True)
12 | done = models.BooleanField(default=False)
13 |
14 | def get_absolute_url(self):
15 | return reverse("tasks:detail", kwargs={"pk": self.pk})
16 |
17 | def __str__(self):
18 | return self.title
19 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/templates/tasks/base.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/templates/tasks/task_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'tasks/base.html' %}
2 |
3 | {% block title %}
4 | Task #{{ task.id }}
5 | {% endblock %}
6 |
7 | {% block body %}
8 |
Task "{{ task.title }}
"
9 |
10 | Description: {{ task.description }}
11 |
12 |
13 | Done: {{ task.done }}
14 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/templates/tasks/task_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'tasks/base.html' %}
2 |
3 | {% block title %}
4 | Add a new task
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Create
9 |
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/templates/tasks/task_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'tasks/base.html' %}
2 |
3 | {% block title %}
4 | Tasks list
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Tasks
9 |
10 | {% for task in task_list %}
11 | -
12 | {{ task }}
14 |
15 | {% if task.done %}
16 | ✅
17 | {% else %}
18 | 🔲
19 | {% endif %}
20 |
21 | {% endfor %}
22 |
23 |
24 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from . import views
4 |
5 | app_name = "tasks"
6 |
7 | urlpatterns = [
8 | path("", views.TasksListView.as_view(), name="list"),
9 | path("/", views.TaskDetailView.as_view(), name="detail"),
10 | path("create/", views.TaskCreateView.as_view(), name="create"),
11 | ]
12 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/tasks/views.py:
--------------------------------------------------------------------------------
1 | from django.views.generic import (
2 | ListView,
3 | DetailView,
4 | CreateView,
5 | )
6 |
7 | from .models import Task
8 |
9 |
10 | class TasksListView(ListView):
11 | model = Task
12 |
13 |
14 | class TaskDetailView(DetailView):
15 | model = Task
16 |
17 |
18 | class TaskCreateView(CreateView):
19 | model = Task
20 | fields = (
21 | "title",
22 | "description",
23 | )
24 | # template_name = "custom-template-name.html"
25 | # context_object_name = "task"
26 | # context_object_name = "assignment"
27 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/templates/about.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | About
5 | {% endblock %}
6 |
7 | {% block body %}
8 | About
9 | It's a task manager
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %}base title{% endblock %}
6 |
7 |
8 |
21 | {% block body %}
22 | {% endblock %}
23 |
24 |
25 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/open-lessons/django-cbv-25.09/task_manager/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Index
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Hello world!
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/README.md:
--------------------------------------------------------------------------------
1 | Полезные материалы:
2 | - FastAPI Jinja Templates https://fastapi.tiangolo.com/reference/templating/
3 | - Templates - FastAPI https://fastapi.tiangolo.com/advanced/templates/
4 | - Базовое приложение на FastAPI https://github.com/mahenzon/FastAPI-base-app
5 | - Template Designer Documentation — Jinja Documentation (3.1.x) https://jinja.palletsprojects.com/en/3.1.x/templates/
6 | - Начало работы с FastAPI - базовое приложение https://www.youtube.com/watch?v=XWJWJfTWjSs
7 | - Полное погружение в HTMX + Jinja шаблоны https://www.youtube.com/watch?v=EbBicPnQBoE
8 | - Стрим по сравнению Flask vs FastAPI vs Django https://www.youtube.com/watch?v=D20C8bBj2hY (работа с шаблонами на примере todo list)
9 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/create_fastapi_app.py:
--------------------------------------------------------------------------------
1 | """
2 | Take a look:
3 | https://github.com/mahenzon/FastAPI-base-app
4 | """
5 |
6 | from contextlib import asynccontextmanager
7 |
8 | from fastapi import FastAPI
9 | from fastapi.responses import ORJSONResponse
10 | from fastapi.openapi.docs import (
11 | get_redoc_html,
12 | get_swagger_ui_html,
13 | get_swagger_ui_oauth2_redirect_html,
14 | )
15 |
16 |
17 | @asynccontextmanager
18 | async def lifespan(app: FastAPI):
19 | # startup
20 | yield
21 | # shutdown
22 |
23 |
24 | def register_static_docs_routes(app: FastAPI):
25 | @app.get("/docs", include_in_schema=False)
26 | async def custom_swagger_ui_html():
27 | return get_swagger_ui_html(
28 | openapi_url=app.openapi_url,
29 | title=app.title + " - Swagger UI",
30 | oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
31 | swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js",
32 | swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css",
33 | )
34 |
35 | @app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
36 | async def swagger_ui_redirect():
37 | return get_swagger_ui_oauth2_redirect_html()
38 |
39 | @app.get("/redoc", include_in_schema=False)
40 | async def redoc_html():
41 | return get_redoc_html(
42 | openapi_url=app.openapi_url,
43 | title=app.title + " - ReDoc",
44 | redoc_js_url="https://unpkg.com/redoc@next/bundles/redoc.standalone.js",
45 | )
46 |
47 |
48 | def create_app(
49 | create_custom_static_urls: bool = False,
50 | ) -> FastAPI:
51 | app = FastAPI(
52 | default_response_class=ORJSONResponse,
53 | lifespan=lifespan,
54 | docs_url=None if create_custom_static_urls else "/docs",
55 | redoc_url=None if create_custom_static_urls else "/redoc",
56 | )
57 | if create_custom_static_urls:
58 | register_static_docs_routes(app)
59 | return app
60 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/main.py:
--------------------------------------------------------------------------------
1 | from create_fastapi_app import create_app
2 |
3 | from views.index import router as router_index
4 | from views.products import router as router_products
5 |
6 | app = create_app(create_custom_static_urls=True)
7 |
8 | app.include_router(router_index)
9 | app.include_router(router_products)
10 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.7.0
2 | anyio==4.4.0
3 | certifi==2024.6.2
4 | click==8.1.7
5 | dnspython==2.6.1
6 | email_validator==2.2.0
7 | fastapi==0.111.0
8 | fastapi-cli==0.0.4
9 | h11==0.14.0
10 | httpcore==1.0.5
11 | httptools==0.6.1
12 | httpx==0.27.0
13 | idna==3.7
14 | itsdangerous==2.2.0
15 | Jinja2==3.1.4
16 | markdown-it-py==3.0.0
17 | MarkupSafe==2.1.5
18 | mdurl==0.1.2
19 | orjson==3.10.5
20 | pydantic==2.7.4
21 | pydantic-extra-types==2.8.2
22 | pydantic-settings==2.3.4
23 | pydantic_core==2.18.4
24 | Pygments==2.18.0
25 | python-dotenv==1.0.1
26 | python-multipart==0.0.9
27 | PyYAML==6.0.1
28 | rich==13.7.1
29 | shellingham==1.5.4
30 | sniffio==1.3.1
31 | starlette==0.37.2
32 | typer==0.12.3
33 | typing_extensions==4.12.2
34 | ujson==5.10.0
35 | uvicorn==0.30.1
36 | uvloop==0.19.0
37 | watchfiles==0.22.0
38 | websockets==12.0
39 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/run_main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 |
3 | if __name__ == "__main__":
4 | uvicorn.run(
5 | "main:app",
6 | host="0.0.0.0",
7 | port=8000,
8 | reload=True,
9 | )
10 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %}
6 | Base
7 | {% endblock %}
8 |
9 |
10 |
11 | {% block body %}
12 |
13 | {% endblock %}
14 |
15 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Index Page
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Hello world!
9 |
10 |
11 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/templates/products/details.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Product #{{ product.id }} details
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Product info
9 |
10 |
11 |
Name: {{ product.name }}
12 |
Price: {{ product.price }}
13 |
14 |
15 |
18 | {% endblock %}
19 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/templates/products/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Products list
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Products
9 |
10 |
11 | {% for product in products %}
12 | -
13 | {{ product.name }} ¢{{ product.price }}
15 |
16 | {% endfor %}
17 |
18 |
19 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi-and-jinja-27.06/utils/__init__.py
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/utils/templating.py:
--------------------------------------------------------------------------------
1 | __all__ = (
2 | "templates",
3 | "TemplateResponse",
4 | )
5 |
6 | from starlette.templating import Jinja2Templates
7 |
8 | templates = Jinja2Templates(directory="templates")
9 | TemplateResponse = templates.TemplateResponse
10 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/views/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi-and-jinja-27.06/views/__init__.py
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/views/index.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, Request
2 | from fastapi.responses import HTMLResponse
3 |
4 | from utils.templating import TemplateResponse
5 |
6 | router = APIRouter()
7 |
8 |
9 | @router.get(
10 | "/",
11 | name="main:index",
12 | response_class=HTMLResponse,
13 | )
14 | def index(request: Request):
15 | return TemplateResponse(
16 | request=request,
17 | name="index.html",
18 | )
19 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/views/products/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = ("router",)
2 |
3 | from .views import router
4 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/views/products/crud.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 | from .schemas import ProductSchema, ProductCreateSchema, ProductIdType
4 |
5 |
6 | class ProductCRUD(BaseModel):
7 | id_to_product: dict[ProductIdType, ProductSchema] = {}
8 | last_id: int = 0
9 |
10 | @property
11 | def next_id(self) -> int:
12 | self.last_id += 1
13 | return self.last_id
14 |
15 | def products_list(self) -> list[ProductSchema]:
16 | return list(self.id_to_product.values())
17 |
18 | def product_details(self, product_id: int) -> ProductSchema | None:
19 | return self.id_to_product.get(product_id)
20 |
21 | def create_product(self, product_in: ProductCreateSchema) -> ProductSchema:
22 | product = ProductSchema(
23 | id=self.next_id,
24 | **product_in.model_dump(),
25 | )
26 | self.id_to_product[product.id] = product
27 | return product
28 |
29 |
30 | storage = ProductCRUD()
31 |
32 | storage.create_product(
33 | ProductCreateSchema(
34 | name="Desktop",
35 | price=1234,
36 | )
37 | )
38 | storage.create_product(
39 | ProductCreateSchema(
40 | name="Laptop",
41 | price=1122,
42 | )
43 | )
44 | storage.create_product(
45 | ProductCreateSchema(
46 | name="Smartphone",
47 | price=999,
48 | )
49 | )
50 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/views/products/schemas.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 |
3 | from pydantic import BaseModel, Field
4 | from annotated_types import MinLen
5 |
6 |
7 | ProductIdType = int
8 |
9 |
10 | class ProductBaseSchema(BaseModel):
11 | name: str
12 | price: int
13 |
14 |
15 | class ProductCreateSchema(ProductBaseSchema):
16 | """
17 | Create new product w/ this data
18 | """
19 |
20 | name: Annotated[str, MinLen(3)]
21 |
22 |
23 | # class ProductUpdateSchema(ProductBaseSchema):
24 | # pass
25 |
26 |
27 | class ProductReadSchema(ProductBaseSchema):
28 | id: ProductIdType = Field(
29 | example="1",
30 | )
31 | name: str = Field(example="Laptop")
32 |
33 |
34 | class ProductSchema(ProductBaseSchema):
35 | id: ProductIdType
36 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-and-jinja-27.06/views/products/views.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 |
3 | from fastapi import APIRouter, Request, HTTPException, status, Form
4 | from fastapi.responses import HTMLResponse
5 | from annotated_types import Gt
6 | from starlette.responses import RedirectResponse
7 |
8 | from utils.templating import TemplateResponse
9 |
10 | from .crud import storage
11 | from .schemas import ProductCreateSchema
12 |
13 | router = APIRouter(
14 | prefix="/products",
15 | tags=["Products"],
16 | )
17 |
18 |
19 | @router.get(
20 | "/",
21 | name="products:list",
22 | response_class=HTMLResponse,
23 | )
24 | def get_products_list(request: Request):
25 | products_list = storage.products_list()
26 | return TemplateResponse(
27 | request=request,
28 | name="products/list.html",
29 | context={"products": products_list},
30 | )
31 |
32 |
33 | @router.get(path="/create/", response_class=HTMLResponse)
34 | def show_form_to_create_new_product():
35 | # TODO
36 | pass
37 |
38 |
39 | @router.post(
40 | path="/create/",
41 | response_class=RedirectResponse,
42 | status_code=status.HTTP_303_SEE_OTHER,
43 | )
44 | def create_new_product(
45 | name: str = Form(...),
46 | price: int = Form(...),
47 | ):
48 | product_in = ProductCreateSchema(name=name, price=price)
49 | product = storage.create_product(product_in=product_in)
50 |
51 | return router.url_path_for("products:details", product_id=product.id)
52 |
53 |
54 | @router.get(
55 | "/{product_id}/",
56 | name="products:details",
57 | response_class=HTMLResponse,
58 | )
59 | def get_product_details(
60 | request: Request,
61 | product_id: Annotated[int, Gt(0)],
62 | ):
63 | product = storage.product_details(product_id=product_id)
64 | if product is None:
65 | raise HTTPException(
66 | status_code=status.HTTP_404_NOT_FOUND,
67 | detail=f"Product #{product_id} not found",
68 | )
69 | return TemplateResponse(
70 | request=request,
71 | name="products/details.html",
72 | context={"product": product},
73 | )
74 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi-bg-tasks-20.05.2025/README.md
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 | from api.api_v1 import router as api_v1
3 |
4 |
5 | router = APIRouter(
6 | prefix="/api",
7 | )
8 | router.include_router(api_v1)
9 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 | from api.api_v1.users.views import router as users_router
3 |
4 | router = APIRouter(
5 | prefix="/v1",
6 | )
7 | router.include_router(users_router)
8 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/__init__.py
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/schemas.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 | from uuid import UUID
3 |
4 | from pydantic import BaseModel, EmailStr
5 | from annotated_types import Len
6 |
7 |
8 | class UserBase(BaseModel):
9 | # noinspection PyTypeHints
10 | username: Annotated[
11 | str,
12 | Len(min_length=3, max_length=20),
13 | ]
14 | email: EmailStr
15 | full_name: str = ""
16 |
17 |
18 | class UserCreate(UserBase):
19 | """
20 | Create user
21 | """
22 |
23 | # noinspection PyTypeHints
24 | password: Annotated[
25 | str,
26 | Len(min_length=8, max_length=1000),
27 | ]
28 |
29 |
30 | class UserRead(UserBase):
31 | id: UUID
32 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/utils/__init__.py
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/utils/email_senders.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from email.message import EmailMessage
3 |
4 | import aiosmtplib
5 |
6 | from config import ADMIN_EMAIL
7 |
8 |
9 | async def send_email(
10 | recipient: str,
11 | subject: str,
12 | body: str,
13 | ):
14 | message = EmailMessage()
15 | message["Subject"] = subject
16 | message["From"] = ADMIN_EMAIL
17 | message["To"] = recipient
18 | message.set_content(body)
19 |
20 | await aiosmtplib.send(
21 | message,
22 | sender=ADMIN_EMAIL,
23 | recipients=[recipient],
24 | hostname="localhost",
25 | port=1025,
26 | )
27 |
28 |
29 | if __name__ == "__main__":
30 | asyncio.run(
31 | send_email(
32 | "rec@a.b",
33 | "hello",
34 | "first line\n\nanother line",
35 | ),
36 | )
37 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/utils/welcome_email_sender.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import logging
3 |
4 | import httpx
5 |
6 | from api.api_v1.users.schemas import UserBase
7 | from api.api_v1.users.utils.email_senders import send_email
8 | from config import LOG_DEFAULT_FORMAT
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | body_template = """\
14 | Hello, {name}!
15 |
16 | Welcome to our site!
17 | You can do here some things.
18 |
19 | Feel free to ask me any questions!
20 |
21 | Best regards,
22 | Site Admin.
23 | """
24 |
25 |
26 | async def send_welcome_email(
27 | user: UserBase,
28 | ):
29 | log.info("Sending welcome email to %s", user)
30 | # # НИКОГДА не делаем синхронных вызовов в асинк коде!!
31 | # response = httpx.get(
32 | # "https://httpbin.org/get",
33 | # params={"foo": "bar", "spam": "eggs"},
34 | # )
35 | async with httpx.AsyncClient() as client:
36 | response = await client.get(
37 | "https://httpbin.org/get",
38 | params={
39 | "foo": "bar",
40 | "spam": "eggs",
41 | "fizz": "buzz",
42 | },
43 | )
44 | log.info("fetched data from api %s", response.json())
45 | await asyncio.sleep(5)
46 | subject = "Welcome to our Shop!"
47 | body = body_template.format(
48 | name=user.full_name or user.username,
49 | )
50 | await send_email(
51 | recipient=str(user.email),
52 | subject=subject,
53 | body=body,
54 | )
55 | log.info("Successfully sent welcome email to %s", user)
56 |
57 |
58 | if __name__ == "__main__":
59 | logging.basicConfig(
60 | level=logging.INFO,
61 | format=LOG_DEFAULT_FORMAT,
62 | )
63 | # noinspection PyTypeChecker
64 | asyncio.run(
65 | send_welcome_email(
66 | UserBase(
67 | username="john",
68 | full_name="John Smith",
69 | email="john.smith@email.com",
70 | )
71 | )
72 | )
73 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/api/api_v1/users/views.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from uuid import uuid4
3 |
4 | from fastapi import APIRouter, BackgroundTasks
5 |
6 | from api.api_v1.users.schemas import UserRead, UserCreate
7 | from api.api_v1.users.utils.welcome_email_sender import send_welcome_email
8 |
9 | log = logging.getLogger(__name__)
10 |
11 |
12 | router = APIRouter(
13 | prefix="/users",
14 | tags=["users"],
15 | )
16 |
17 |
18 | @router.post("/register")
19 | async def register(
20 | user_create: UserCreate,
21 | background_tasks: BackgroundTasks,
22 | ) -> UserRead:
23 | log.info("Create new user %s", user_create)
24 |
25 | # мы будто бы сохранили юзера в базу
26 | user = UserRead(
27 | id=uuid4(),
28 | **user_create.model_dump(),
29 | )
30 | log.info("User %s created", user)
31 | # await send_welcome_email(user)
32 | background_tasks.add_task(send_welcome_email, user=user)
33 | return user
34 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/app.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from fastapi import FastAPI, Request
4 |
5 | from api import router as api_router
6 | from config import LOG_DEFAULT_FORMAT
7 |
8 | logging.basicConfig(
9 | level=logging.INFO,
10 | format=LOG_DEFAULT_FORMAT,
11 | )
12 |
13 | app = FastAPI()
14 | app.include_router(api_router)
15 |
16 |
17 | @app.get("/")
18 | def read_root(request: Request):
19 | url = request.url.replace(path="/docs")
20 | return {
21 | "docs": str(url),
22 | }
23 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/config.py:
--------------------------------------------------------------------------------
1 | LOG_DEFAULT_FORMAT = (
2 | "[%(asctime)s] %(module)10s:%(lineno)-3d %(levelname)-7s - %(message)s"
3 | )
4 | ADMIN_EMAIL = "admin@shop.com"
5 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | maildev:
3 | image: maildev/maildev
4 | environment:
5 | MAILDEV_SMTP_PORT: 1025
6 | MAILDEV_WEB_PORT: 1080
7 | TZ: Europe/Moscow
8 | ports:
9 | - "1080:1080"
10 | - "1025:1025"
11 | logging:
12 | driver: "json-file"
13 | options:
14 | max-size: "1m"
15 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-bg-tasks-20.05.2025/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "fastapi-bg-tasks-20-05"
3 | version = "0.1.0"
4 | description = "Add your description here"
5 | readme = "README.md"
6 | requires-python = ">=3.13"
7 | dependencies = [
8 | "aiosmtplib>=4.0.1",
9 | "fastapi[standard]>=0.115.12",
10 | ]
11 |
12 | [dependency-groups]
13 | dev = [
14 | "black>=25.1.0",
15 | ]
16 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 |
3 | from views import router as api_router
4 |
5 | app = FastAPI()
6 | app.include_router(api_router)
7 |
8 |
9 | @app.get(
10 | "/",
11 | summary="Hello World",
12 | )
13 | def read_root_hello_world():
14 | """
15 | Greets the World!
16 |
17 | # Header 1
18 | ## Header 2
19 | ### Header 3
20 |
21 | ToDo List:
22 | - learn FastAPI
23 | - learn Django
24 | - learn SQLAlchemy
25 |
26 | Example:
27 | ```json
28 | {"message": "Hello World!"}
29 | ```
30 | """
31 | return {"message": "Hello World"}
32 |
33 |
34 | @app.get("/hello/")
35 | def greet_by_name(name: str):
36 | return {"message": f"Hello, {name}!"}
37 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.7.0
2 | anyio==4.8.0
3 | certifi==2025.1.31
4 | click==8.1.8
5 | dnspython==2.7.0
6 | email_validator==2.2.0
7 | fastapi==0.115.9
8 | fastapi-cli==0.0.7
9 | h11==0.14.0
10 | httpcore==1.0.7
11 | httptools==0.6.4
12 | httpx==0.28.1
13 | idna==3.10
14 | itsdangerous==2.2.0
15 | Jinja2==3.1.5
16 | markdown-it-py==3.0.0
17 | MarkupSafe==3.0.2
18 | mdurl==0.1.2
19 | orjson==3.10.15
20 | pydantic==2.10.6
21 | pydantic-extra-types==2.10.2
22 | pydantic-settings==2.8.1
23 | pydantic_core==2.27.2
24 | Pygments==2.19.1
25 | python-dotenv==1.0.1
26 | python-multipart==0.0.20
27 | PyYAML==6.0.2
28 | rich==13.9.4
29 | rich-toolkit==0.13.2
30 | shellingham==1.5.4
31 | sniffio==1.3.1
32 | starlette==0.45.3
33 | typer==0.15.1
34 | typing_extensions==4.12.2
35 | ujson==5.10.0
36 | uvicorn==0.34.0
37 | uvloop==0.21.0
38 | watchfiles==1.0.4
39 | websockets==15.0
40 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/views/__init__.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter
2 |
3 | from .movies.views import router as movies_router
4 |
5 | router = APIRouter(
6 | prefix="/api",
7 | tags=["API"],
8 | )
9 |
10 | router.include_router(movies_router)
11 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/views/movies/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi-intro-27.02.2025/views/movies/__init__.py
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/views/movies/crud.py:
--------------------------------------------------------------------------------
1 | """
2 | Create
3 | Read
4 | Update
5 | Delete
6 | """
7 | from pydantic import BaseModel
8 |
9 | from .schema import Movie, MovieCreate
10 |
11 |
12 | class MoviesStorage(BaseModel):
13 | last_id: int = 0
14 | movies: dict[int, Movie] = {}
15 |
16 | @property
17 | def next_id(self) -> int:
18 | self.last_id += 1
19 | return self.last_id
20 |
21 | def get(self) -> list[Movie]:
22 | return list(self.movies.values())
23 |
24 | def get_by_id(self, movie_id: int) -> Movie | None:
25 | return self.movies.get(movie_id)
26 |
27 | def add(self, movie_in: MovieCreate) -> Movie:
28 | movie = Movie(
29 | id=self.next_id,
30 | **movie_in.model_dump(),
31 | )
32 | self.movies[movie.id] = movie
33 | return movie
34 |
35 | def delete(self, movie_id: int) -> None:
36 | self.movies.pop(movie_id, None)
37 |
38 |
39 | storage = MoviesStorage()
40 |
41 | storage.add(
42 | MovieCreate(
43 | title="The Shawshank Redemption",
44 | year=1994,
45 | ),
46 | )
47 | storage.add(
48 | MovieCreate(
49 | title="Pulp Fiction",
50 | year=1994,
51 | ),
52 | )
53 | storage.add(
54 | MovieCreate(
55 | title="The Game",
56 | year=1997,
57 | ),
58 | )
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/views/movies/schema.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 |
3 | from annotated_types import Len, Ge, Le
4 | from pydantic import BaseModel, Field
5 |
6 |
7 |
8 | class MovieBase(BaseModel):
9 | title: str
10 | year: int
11 |
12 |
13 | class MovieCreate(MovieBase):
14 | title: Annotated[
15 | str,
16 | Len(min_length=1, max_length=200),
17 | ]
18 | year: Annotated[
19 | int,
20 | Ge(1878),
21 | Le(2222),
22 | ]
23 |
24 |
25 | class MovieRead(MovieBase):
26 | id: int = Field(example=42)
27 |
28 |
29 | class Movie(MovieBase):
30 | """
31 | The Movie model
32 | """
33 | id: int
34 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-intro-27.02.2025/views/movies/views.py:
--------------------------------------------------------------------------------
1 | from fastapi import APIRouter, HTTPException, status
2 | from pydantic import PositiveInt
3 |
4 | from .crud import storage
5 | from .schema import MovieRead, MovieCreate
6 |
7 | router = APIRouter(
8 | prefix="/movies",
9 | tags=["Movies"],
10 | )
11 |
12 | @router.get(
13 | "/",
14 | response_model=list[MovieRead],
15 | )
16 | def get_movies():
17 | return storage.get()
18 |
19 |
20 | @router.post(
21 | "/",
22 | response_model=MovieRead,
23 | )
24 | def create_movie(
25 | movie_in: MovieCreate,
26 | ):
27 | return storage.add(movie_in=movie_in)
28 |
29 |
30 | @router.get(
31 | "/{movie_id}/",
32 | response_model=MovieRead,
33 | )
34 | def get_movie(
35 | movie_id: PositiveInt,
36 | ):
37 | movie = storage.get_by_id(movie_id=movie_id)
38 | if movie:
39 | return movie
40 | raise HTTPException(
41 | status_code=status.HTTP_404_NOT_FOUND,
42 | detail=f"Movie #{movie_id} not found!",
43 | )
44 |
45 | @router.delete(
46 | "/{movie_id}/",
47 | status_code=status.HTTP_204_NO_CONTENT,
48 | )
49 | def get_movie(
50 | movie_id: PositiveInt,
51 | ) -> None:
52 | storage.delete(movie_id=movie_id)
53 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/config.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | CURRENT_FILE = Path(__file__).resolve()
4 | PROJECT_DIR = CURRENT_FILE.parent
5 | DB_URL = f"sqlite+aiosqlite:///{PROJECT_DIR}/db.sqlite3"
6 | # DB_ECHO = False
7 | DB_ECHO = True
8 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/db.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.asyncio import async_sessionmaker
2 |
3 | from models.database import create_engine
4 |
5 |
6 | def async_session() -> async_sessionmaker:
7 | engine = create_engine()
8 | _async_session = async_sessionmaker(bind=engine, expire_on_commit=False)
9 | return _async_session
10 |
11 |
12 | class Connector:
13 | @classmethod
14 | async def get_session(cls):
15 | """
16 | Get session as dependency
17 | """
18 | sess = async_session()
19 | async with sess() as db_session:
20 | yield db_session
21 | await db_session.rollback()
22 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/main.py:
--------------------------------------------------------------------------------
1 | from contextlib import asynccontextmanager
2 | from pathlib import Path
3 |
4 | import uvicorn
5 | from fastapi import (
6 | FastAPI,
7 | APIRouter,
8 | )
9 |
10 | from models.database import sqlalchemy_init
11 | from urls import register_routes
12 |
13 | CURRENT_FILE = Path(__file__).resolve()
14 | PROJECT_DIR = CURRENT_FILE.parent
15 | DB_URL = f"sqlite+aiosqlite:///{PROJECT_DIR}/db.sqlite3"
16 |
17 |
18 | @asynccontextmanager
19 | async def lifespan(app: FastAPI):
20 | await sqlalchemy_init()
21 | yield
22 |
23 |
24 | router = APIRouter(prefix="/api")
25 | register_routes(router)
26 |
27 | app = FastAPI(lifespan=lifespan)
28 | app.include_router(router)
29 |
30 | if __name__ == "__main__":
31 | uvicorn.run(
32 | "main:app",
33 | host="0.0.0.0",
34 | port=8000,
35 | reload=True,
36 | )
37 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/models/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = (
2 | "User",
3 | "UserBio",
4 | )
5 |
6 | from .user import User
7 | from .user_bio import UserBio
8 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/models/database.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import (
2 | make_url,
3 | Integer,
4 | Column,
5 | )
6 | from sqlalchemy.ext.asyncio import create_async_engine
7 | from sqlalchemy.orm import DeclarativeBase
8 |
9 | from config import (
10 | DB_URL,
11 | DB_ECHO,
12 | )
13 |
14 |
15 | class Base(DeclarativeBase):
16 | id = Column(Integer, primary_key=True)
17 |
18 |
19 | def create_engine():
20 | return create_async_engine(
21 | url=make_url(DB_URL),
22 | echo=DB_ECHO,
23 | )
24 |
25 |
26 | async def sqlalchemy_init() -> None:
27 | engine = create_engine()
28 | async with engine.begin() as conn:
29 | await conn.run_sync(Base.metadata.create_all)
30 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import (
2 | Column,
3 | Integer,
4 | String,
5 | )
6 | from sqlalchemy.orm import relationship
7 |
8 | from .database import Base
9 |
10 |
11 | class User(Base):
12 | __tablename__ = "users"
13 |
14 | first_name = Column(String, nullable=False)
15 | last_name = Column(String, nullable=False)
16 | age = Column(Integer, nullable=True)
17 | email = Column(String, nullable=True)
18 |
19 | bio = relationship(
20 | "UserBio",
21 | back_populates="user",
22 | uselist=False,
23 | )
24 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/models/user_bio.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import (
2 | Column,
3 | ForeignKey,
4 | Integer,
5 | String,
6 | )
7 | from sqlalchemy.orm import relationship
8 |
9 | from .database import Base
10 |
11 |
12 | class UserBio(Base):
13 | __tablename__ = "user_bio"
14 | birth_city = Column(String, nullable=False, default="", server_default="")
15 | favourite_movies = Column(String, nullable=False, default="", server_default="")
16 |
17 | user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True)
18 | user = relationship(
19 | "User",
20 | back_populates="bio",
21 | uselist=False,
22 | )
23 |
24 | def __repr__(self):
25 | return (
26 | f"{self.__class__.__name__}("
27 | f"id={self.id},"
28 | f" birth_city={self.birth_city!r},"
29 | f" favourite_movies={self.favourite_movies!r},"
30 | f" user_id={self.user_id}"
31 | ")"
32 | )
33 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/requirements.txt:
--------------------------------------------------------------------------------
1 | aiosqlite==0.20.0
2 | annotated-types==0.7.0
3 | anyio==4.4.0
4 | certifi==2024.6.2
5 | click==8.1.7
6 | dnspython==2.6.1
7 | email_validator==2.1.1
8 | fastapi==0.111.0
9 | fastapi-cli==0.0.4
10 | FastAPI-JSONAPI==2.8.0
11 | greenlet==3.0.3
12 | h11==0.14.0
13 | httpcore==1.0.5
14 | httptools==0.6.1
15 | httpx==0.27.0
16 | idna==3.7
17 | Jinja2==3.1.4
18 | markdown-it-py==3.0.0
19 | MarkupSafe==2.1.5
20 | mdurl==0.1.2
21 | orjson==3.10.3
22 | pydantic==1.10.15
23 | pydantic_core==2.18.3
24 | Pygments==2.18.0
25 | python-dotenv==1.0.1
26 | python-multipart==0.0.9
27 | PyYAML==6.0.1
28 | rich==13.7.1
29 | shellingham==1.5.4
30 | simplejson==3.19.2
31 | sniffio==1.3.1
32 | SQLAlchemy==2.0.30
33 | starlette==0.37.2
34 | typer==0.12.3
35 | typing_extensions==4.12.1
36 | ujson==5.10.0
37 | uvicorn==0.30.1
38 | uvloop==0.19.0
39 | watchfiles==0.22.0
40 | websockets==12.0
41 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = (
2 | "UserSchema",
3 | "UserUpdateSchema",
4 | "UserBioSchema",
5 | "UserBioUpdateSchema",
6 | )
7 |
8 | from .user import UserSchema, UserUpdateSchema
9 | from .user_bio import UserBioSchema, UserBioUpdateSchema
10 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/schemas/user.py:
--------------------------------------------------------------------------------
1 |
2 | from datetime import datetime
3 | from typing import TYPE_CHECKING, List, Optional
4 |
5 | from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo
6 |
7 | if TYPE_CHECKING:
8 | # from .post import PostSchema
9 | from .user_bio import UserBioSchema
10 |
11 |
12 | class UserBaseSchema(BaseModel):
13 | """User base schema."""
14 |
15 | class Config:
16 | """Pydantic schema config."""
17 |
18 | orm_mode = True
19 |
20 | first_name: str
21 | last_name: str
22 | age: int | None = None
23 | email: str | None = None
24 |
25 | bio: Optional["UserBioSchema"] = Field(
26 | relationship=RelationshipInfo(
27 | resource_type="user_bio",
28 | ),
29 | )
30 |
31 |
32 | class UserUpdateSchema(UserBaseSchema):
33 | """User PATCH schema."""
34 |
35 | first_name: str | None = None
36 | last_name: str | None = None
37 |
38 |
39 | # class UserCreateSchema(UserBaseSchema):
40 | # """User input schema."""
41 |
42 |
43 | class UserSchema(UserBaseSchema):
44 | """User item schema."""
45 |
46 | id: int
47 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/schemas/user_bio.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING
2 |
3 | from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo
4 |
5 | if TYPE_CHECKING:
6 | from .user import UserSchema
7 |
8 |
9 | class UserBioBaseSchema(BaseModel):
10 | """UserBio base schema."""
11 |
12 | class Config:
13 | """Pydantic schema config."""
14 |
15 | orm_mode = True
16 |
17 | birth_city: str
18 | favourite_movies: str
19 |
20 | user: "UserSchema" = Field(
21 | relationship=RelationshipInfo(
22 | resource_type="user",
23 | ),
24 | )
25 |
26 |
27 | class UserBioUpdateSchema(UserBioBaseSchema):
28 | """UserBio PATCH schema."""
29 |
30 | birth_city: str | None = None
31 | favourite_movies: str | None = None
32 |
33 | # class UserBioInSchema(UserBioBaseSchema):
34 | # """UserBio input schema."""
35 |
36 |
37 | class UserBioSchema(UserBioBaseSchema):
38 | """UserBio item schema."""
39 |
40 | class Config:
41 | """Pydantic model config."""
42 |
43 | orm_mode = True
44 |
45 | id: int
46 |
--------------------------------------------------------------------------------
/open-lessons/fastapi-jsonapi.03.06.2024/urls.py:
--------------------------------------------------------------------------------
1 | from typing import ClassVar
2 |
3 | from fastapi import (
4 | APIRouter,
5 | Depends,
6 | )
7 |
8 | from fastapi_jsonapi import RoutersJSONAPI
9 | from fastapi_jsonapi.misc.sqla.generics.base import (
10 | ListViewBaseGeneric,
11 | DetailViewBaseGeneric,
12 | )
13 | from fastapi_jsonapi.views.utils import (
14 | HTTPMethodConfig,
15 | HTTPMethod,
16 | )
17 | from fastapi_jsonapi.views.view_base import ViewBase
18 | from pydantic import BaseModel
19 | from sqlalchemy.ext.asyncio import AsyncSession
20 |
21 | from db import Connector
22 | from models import (
23 | User,
24 | UserBio,
25 | )
26 | from schemas import (
27 | UserSchema,
28 | UserUpdateSchema,
29 | UserBioSchema,
30 | UserBioUpdateSchema,
31 | )
32 |
33 |
34 | class SessionDependency(BaseModel):
35 | session: AsyncSession = Depends(Connector.get_session)
36 |
37 | class Config:
38 | arbitrary_types_allowed = True
39 |
40 |
41 | def session_dependency_handler(view: ViewBase, dto: SessionDependency):
42 | return {
43 | "session": dto.session,
44 | }
45 |
46 |
47 | class DetailView(DetailViewBaseGeneric):
48 | method_dependencies: ClassVar = {
49 | HTTPMethod.ALL: HTTPMethodConfig(
50 | dependencies=SessionDependency,
51 | prepare_data_layer_kwargs=session_dependency_handler,
52 | ),
53 | }
54 |
55 |
56 | class ListView(ListViewBaseGeneric):
57 | method_dependencies: ClassVar = {
58 | HTTPMethod.ALL: HTTPMethodConfig(
59 | dependencies=SessionDependency,
60 | prepare_data_layer_kwargs=session_dependency_handler,
61 | ),
62 | }
63 |
64 |
65 | def register_routes(router: APIRouter) -> None:
66 | default_methods = [
67 | RoutersJSONAPI.Methods.GET_LIST,
68 | RoutersJSONAPI.Methods.POST,
69 | RoutersJSONAPI.Methods.GET,
70 | RoutersJSONAPI.Methods.PATCH,
71 | RoutersJSONAPI.Methods.DELETE,
72 | ]
73 | RoutersJSONAPI(
74 | router=router,
75 | path="/users",
76 | tags=["User"],
77 | class_detail=DetailView,
78 | class_list=ListView,
79 | model=User,
80 | schema=UserSchema,
81 | resource_type="user",
82 | schema_in_patch=UserUpdateSchema,
83 | methods=default_methods,
84 | )
85 |
86 | RoutersJSONAPI(
87 | router=router,
88 | path="/user-bio",
89 | tags=["User", "User Bio"],
90 | class_detail=DetailView,
91 | class_list=ListView,
92 | model=UserBio,
93 | schema=UserBioSchema,
94 | resource_type="user_bio",
95 | schema_in_patch=UserBioUpdateSchema,
96 | methods=default_methods,
97 | )
98 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/main.py:
--------------------------------------------------------------------------------
1 | import uvicorn
2 | from fastapi import FastAPI
3 |
4 | from views.calc import router as calc_router
5 | from views.items import router as items_router
6 |
7 | app = FastAPI()
8 | app.include_router(calc_router)
9 | app.include_router(items_router)
10 |
11 |
12 | @app.get("/")
13 | def hello_index():
14 | return {"message": "Hello Index!"}
15 |
16 |
17 | @app.get("/hello/")
18 | def hello_world(name: str = "World"):
19 | return {"message": f"Hello {name}!"}
20 |
21 |
22 | @app.get("/hello/{name}/")
23 | def hello_view(name: str):
24 | return {"message": f"Hello {name.title()}!"}
25 |
26 |
27 | if __name__ == "__main__":
28 | uvicorn.run(
29 | "main:app",
30 | # app=app,
31 | reload=True,
32 | )
33 | # uvicorn.run(app)
34 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.6.0
2 | anyio==3.7.1
3 | certifi==2023.7.22
4 | click==8.1.7
5 | dnspython==2.4.2
6 | email-validator==2.1.0.post1
7 | fastapi==0.104.0
8 | h11==0.14.0
9 | httpcore==0.18.0
10 | httptools==0.6.1
11 | httpx==0.25.0
12 | idna==3.4
13 | itsdangerous==2.1.2
14 | Jinja2==3.1.2
15 | MarkupSafe==2.1.3
16 | orjson==3.9.9
17 | pydantic==2.4.2
18 | pydantic-extra-types==2.1.0
19 | pydantic-settings==2.0.3
20 | pydantic_core==2.10.1
21 | python-dotenv==1.0.0
22 | python-multipart==0.0.6
23 | PyYAML==6.0.1
24 | sniffio==1.3.0
25 | starlette==0.27.0
26 | typing_extensions==4.8.0
27 | ujson==5.8.0
28 | uvicorn==0.23.2
29 | uvloop==0.19.0
30 | watchfiles==0.21.0
31 | websockets==12.0
32 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/fastapi.24.10.2023/views/__init__.py
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/calc/__init__.py:
--------------------------------------------------------------------------------
1 | from .views import router
2 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/calc/crud.py:
--------------------------------------------------------------------------------
1 | from .schemas import CalcInput
2 |
3 |
4 | def calc_mul(calc: CalcInput):
5 | return {
6 | "total": calc.a * calc.b,
7 | "calc": calc.model_dump(),
8 | }
9 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/calc/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel
2 |
3 |
4 | class CalcInput(BaseModel):
5 | a: int
6 | b: int
7 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/calc/views.py:
--------------------------------------------------------------------------------
1 | from fastapi import Depends, APIRouter
2 |
3 | from . import crud
4 | from .schemas import CalcInput
5 |
6 | router = APIRouter(prefix="/calc", tags=["Calc"])
7 |
8 |
9 | @router.get("/add/")
10 | def calc_add(a: int, b: int):
11 | return {
12 | "total": a + b,
13 | "a": a,
14 | "b": b,
15 | }
16 |
17 |
18 | @router.get("/mul/")
19 | def calc_mul(calc: CalcInput = Depends()):
20 | return crud.calc_mul(calc=calc)
21 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/items/__init__.py:
--------------------------------------------------------------------------------
1 | from .views import router
2 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/items/schemas.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, Field, EmailStr
2 |
3 |
4 | class NewItemModel(BaseModel):
5 | # username: constr(min_length=3)
6 | username: str = Field(min_length=3)
7 | email: EmailStr
8 |
--------------------------------------------------------------------------------
/open-lessons/fastapi.24.10.2023/views/items/views.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated
2 | from fastapi import APIRouter, Path
3 |
4 | from .schemas import NewItemModel
5 |
6 | router = APIRouter(prefix="/items", tags=["Items"])
7 |
8 |
9 | @router.post("/")
10 | def create_item(item: NewItemModel):
11 | return {"item": item.model_dump()}
12 |
13 |
14 | @router.get("/{product_id}/")
15 | def get_item(
16 | product_id: Annotated[int, Path(gt=0, lt=1_000_000)],
17 | ):
18 | return {
19 | "item":
20 | {
21 | "id": product_id,
22 | "name": f"product_{product_id}",
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/main.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request, render_template
2 |
3 | from views.products import products_app
4 |
5 | app = Flask(__name__)
6 | app.register_blueprint(products_app)
7 |
8 |
9 | @app.route("/")
10 | def index_view():
11 | return render_template("index.html")
12 |
13 |
14 | @app.route("/hello/")
15 | @app.route("/hello//")
16 | def get_hello(name=None):
17 | if name is None:
18 | name = request.args.get("name") or ""
19 | if not name:
20 | name = "World"
21 | # return f"Hello {name}!
"
22 | return render_template("hello.html", name=name)
23 |
24 |
25 | @app.route("/items//")
26 | def get_item_by_id(item_id):
27 | return {
28 | "data": {
29 | "id": item_id,
30 | "name": f"Name_{item_id}"
31 | }
32 | }
33 |
34 |
35 | if __name__ == "__main__":
36 | app.run(debug=True)
37 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/requirements.txt:
--------------------------------------------------------------------------------
1 | click==8.1.3
2 | Flask==2.2.3
3 | itsdangerous==2.1.2
4 | Jinja2==3.1.2
5 | MarkupSafe==2.1.2
6 | Werkzeug==2.2.3
7 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | {% block title %}Base Title{% endblock %}
9 |
10 |
11 | {% block body %}
12 |
13 | {% endblock %}
14 |
15 |
16 | © OTUS 2023
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/templates/hello.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Hello page
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Hello {{ name }}
9 | Time 0
10 |
13 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Index page
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Index Page!!!
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/templates/products/create.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Create Product
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Create a new product
9 |
10 |
11 |
16 |
17 |
18 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/templates/products/details.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Product #{{ product.id }} details
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Product {{ product.name }}
9 |
10 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/templates/products/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Products
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Products list:
9 |
10 |
21 |
22 |
25 | {% endblock %}
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/views/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/flask-.21.02.2023/views/__init__.py
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/views/products.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from flask import Blueprint, render_template, request, redirect, url_for
4 | from werkzeug.exceptions import BadRequest, NotFound
5 |
6 |
7 | products_app = Blueprint(
8 | "products_app",
9 | __name__,
10 | url_prefix="/products",
11 | )
12 |
13 |
14 | @dataclass(frozen=True, slots=True)
15 | class Product:
16 | id: int
17 | name: str
18 |
19 |
20 | @dataclass(slots=True)
21 | class ProductsStorage:
22 | products: dict[int, Product]
23 | last_index: int = 0
24 |
25 | @property
26 | def next_index(self):
27 | self.last_index += 1
28 | return self.last_index
29 |
30 | def create(self, name: str):
31 | product = Product(id=self.next_index, name=name)
32 | self.products[product.id] = product
33 | return product
34 |
35 |
36 | storage = ProductsStorage(products={})
37 | storage.create("Laptop")
38 | storage.create("Desktop")
39 | storage.create("Smartphone")
40 |
41 |
42 | @products_app.route("/", endpoint="list")
43 | def get_products_list():
44 | products = list(storage.products.values())
45 | return render_template("products/list.html", products=products)
46 |
47 |
48 | @products_app.route("//", endpoint="details")
49 | def get_product_by_id(product_id):
50 | product = storage.products.get(product_id)
51 | if product is None:
52 | raise NotFound(f"Product #{product_id} not found!")
53 | return render_template("products/details.html", product=product)
54 |
55 |
56 | @products_app.route("/create/", methods=["GET", "POST"], endpoint="create")
57 | def create_product():
58 | if request.method == "GET":
59 | return render_template("products/create.html")
60 |
61 | product_name = request.form.get("product-name") or ""
62 | product_name = product_name.strip()
63 | if not product_name:
64 | raise BadRequest("Field `product-name` is required!")
65 |
66 | product = storage.create(name=product_name)
67 | url = url_for('products_app.details', product_id=product.id)
68 | return redirect(url)
69 |
--------------------------------------------------------------------------------
/open-lessons/flask-.21.02.2023/wsgi.py:
--------------------------------------------------------------------------------
1 | __all__ = ("app",)
2 |
3 | from main import app
4 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask_combo_jsonapi import Api
3 | from combojsonapi.spec import ApiSpecPlugin
4 |
5 | from models.database import db
6 | from views import PersonList, PersonDetail, ComputerList, ComputerDetail
7 |
8 | app = Flask(__name__)
9 |
10 | app.config.from_object("config.DevelopmentConfig")
11 | db.init_app(app)
12 |
13 |
14 | api_spec_plugin = ApiSpecPlugin(
15 | app=app,
16 | tags={
17 | "Person": "Persons API",
18 | "Computer": "Computers API",
19 | },
20 | )
21 |
22 | api = Api(
23 | app,
24 | plugins=[
25 | api_spec_plugin,
26 | ],
27 | )
28 |
29 | api.route(PersonList, "person_list", "/persons", tag="Person")
30 | api.route(PersonDetail, "person_detail", "/persons/", tag="Person")
31 | api.route(ComputerList, "computer_list", "/computers", tag="Computer")
32 | api.route(ComputerDetail, "computer_detail", "/computers/", tag="Computer")
33 |
34 |
35 | @app.route("/")
36 | def hello():
37 | db.create_all() # use for only for dev
38 | return {"message": "Hello world!"}
39 |
40 |
41 | if __name__ == "__main__":
42 | app.run()
43 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/config.py:
--------------------------------------------------------------------------------
1 | class BaseConfig:
2 | SQLALCHEMY_DATABASE_URI = "sqlite:////:memory:"
3 | SQLALCHEMY_ECHO = False
4 | SQLALCHEMY_TRACK_MODIFICATIONS = False
5 |
6 | # swagger
7 |
8 | OPENAPI_URL_PREFIX = "/docs"
9 | OPENAPI_VERSION = "2.0.0"
10 | OPENAPI_SWAGGER_UI_PATH = "/"
11 | OPENAPI_SWAGGER_UI_VERSION = "3.45.0"
12 |
13 |
14 | class DevelopmentConfig(BaseConfig):
15 | DEBUG = True
16 | SQLALCHEMY_DATABASE_URI = "sqlite:///./flask-jsonapi.db"
17 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .person import Person
2 | from .computer import Computer
3 |
4 | __all__ = (
5 | "Person",
6 | "Computer",
7 | )
8 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/models/computer.py:
--------------------------------------------------------------------------------
1 | from models.database import db
2 |
3 |
4 | class Computer(db.Model):
5 | id = db.Column(db.Integer, primary_key=True)
6 | serial = db.Column(db.String)
7 | person_id = db.Column(db.Integer, db.ForeignKey("person.id"))
8 | person = db.relationship("Person", back_populates="computers")
9 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/models/database.py:
--------------------------------------------------------------------------------
1 | from flask_sqlalchemy import SQLAlchemy
2 |
3 | db = SQLAlchemy()
4 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/models/person.py:
--------------------------------------------------------------------------------
1 | from .database import db
2 |
3 |
4 | class Person(db.Model):
5 | id = db.Column(db.Integer, primary_key=True)
6 | name = db.Column(db.String)
7 | birth_date = db.Column(db.Date)
8 | computers = db.relationship("Computer", back_populates="person")
9 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/requirements.txt:
--------------------------------------------------------------------------------
1 | apispec==4.7.1
2 | click==8.0.1
3 | ComboJSONAPI==1.1.1
4 | Flask==1.1.2
5 | Flask-COMBO-JSONAPI==1.0.7
6 | Flask-SQLAlchemy==2.5.1
7 | itsdangerous==2.0.1
8 | Jinja2==3.0.1
9 | MarkupSafe==2.0.1
10 | marshmallow==3.2.1
11 | marshmallow-jsonapi==0.24.0
12 | PyYAML==5.4.1
13 | simplejson==3.17.3
14 | SQLAlchemy==1.3.24
15 | Werkzeug==2.0.1
16 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .person import PersonSchema
2 | from .computer import ComputerSchema
3 |
4 | __all__ = (
5 | "PersonSchema",
6 | "ComputerSchema",
7 | )
8 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/schemas/computer.py:
--------------------------------------------------------------------------------
1 | from marshmallow_jsonapi import fields
2 | from marshmallow_jsonapi.flask import Schema
3 |
4 | from combojsonapi.utils import Relationship
5 |
6 |
7 | class ComputerSchema(Schema):
8 | class Meta:
9 | type_ = "computer"
10 | self_view = "computer_detail"
11 | self_view_kwargs = {"id": ""}
12 | self_view_many = "computer_list"
13 |
14 | id = fields.Integer(as_string=True, dump_only=True)
15 | serial = fields.String(required=True)
16 | owner = Relationship(
17 | type_="person",
18 | attribute="person",
19 | schema="PersonSchema",
20 | nested="PersonSchema",
21 | related_view="person_detail",
22 | related_view_kwargs={"id": ""},
23 | self_view="computer_person",
24 | self_view_kwargs={"id": ""},
25 | )
26 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/schemas/person.py:
--------------------------------------------------------------------------------
1 | from marshmallow_jsonapi import fields
2 | from marshmallow_jsonapi.flask import Schema
3 | from combojsonapi.utils import Relationship
4 |
5 |
6 | class PersonSchema(Schema):
7 | class Meta:
8 | type_ = "person"
9 | self_view_many = "person_list"
10 | self_view = "person_detail"
11 | self_view_kwargs = {"id": ""}
12 |
13 | id = fields.Integer(as_string=True)
14 | name = fields.String()
15 | birth_date = fields.Date()
16 | computers = Relationship(
17 | type_="computer",
18 | schema="ComputerSchema",
19 | nested="ComputerSchema",
20 | many=True,
21 | related_view="computer_list",
22 | # related_view_kwargs={"id": ""},
23 | self_view="person_computers",
24 | self_view_kwargs={"id": ""},
25 | )
26 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/views/__init__.py:
--------------------------------------------------------------------------------
1 | from .person import PersonList, PersonDetail
2 | from .computer import ComputerList, ComputerDetail
3 |
4 | __all__ = (
5 | "PersonList",
6 | "PersonDetail",
7 | "ComputerList",
8 | "ComputerDetail",
9 | )
10 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/views/computer.py:
--------------------------------------------------------------------------------
1 | from flask_combo_jsonapi import ResourceList, ResourceDetail
2 |
3 |
4 | from models import Computer
5 | from models.database import db
6 | from schemas import ComputerSchema
7 |
8 |
9 | class ComputerList(ResourceList):
10 | schema = ComputerSchema
11 | data_layer = {
12 | "session": db.session,
13 | "model": Computer,
14 | }
15 |
16 |
17 | class ComputerDetail(ResourceDetail):
18 | schema = ComputerSchema
19 | data_layer = {
20 | "session": db.session,
21 | "model": Computer,
22 | }
23 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.19.08.2021/views/person.py:
--------------------------------------------------------------------------------
1 | from flask_combo_jsonapi import ResourceList, ResourceDetail
2 |
3 | from models import Person
4 | from models.database import db
5 | from schemas import PersonSchema
6 |
7 |
8 | class PersonList(ResourceList):
9 | schema = PersonSchema
10 | data_layer = {
11 | "session": db.session,
12 | "model": Person,
13 | }
14 |
15 |
16 | class PersonDetail(ResourceDetail):
17 | schema = PersonSchema
18 | data_layer = {
19 | "session": db.session,
20 | "model": Person,
21 | }
22 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask_combo_jsonapi import Api
3 | from combojsonapi.event import EventPlugin
4 | from combojsonapi.spec import ApiSpecPlugin
5 | from combojsonapi.permission import PermissionPlugin
6 |
7 | from models.database import db
8 | from views import PersonList, PersonDetail, ComputerList, ComputerDetail, PersonRelationship
9 |
10 | app = Flask(__name__)
11 |
12 | app.config.from_object("config.DevelopmentConfig")
13 | db.init_app(app)
14 |
15 |
16 | api_spec_plugin = ApiSpecPlugin(
17 | app=app,
18 | tags={
19 | "Person": "Persons API",
20 | "Computer": "Computers API",
21 | },
22 | )
23 |
24 |
25 | event_plugin = EventPlugin(trailing_slash=False)
26 |
27 | permission_plugin = PermissionPlugin(strict=False)
28 |
29 | api = Api(
30 | app,
31 | plugins=[
32 | event_plugin,
33 | api_spec_plugin,
34 | # permission_plugin,
35 | ],
36 | )
37 |
38 | api.route(
39 | PersonList, "person_list", "/persons", tag="Person")
40 | api.route(
41 | PersonDetail,
42 | "person_detail",
43 | "/persons/",
44 | # "/computers//owner",
45 | tag="Person",
46 | )
47 | api.route(
48 | PersonRelationship,
49 | "person_computers",
50 | "/persons//relationships/computers",
51 | )
52 |
53 | api.route(
54 | ComputerList, "computer_list", "/computers", tag="Computer")
55 | api.route(
56 | ComputerDetail, "computer_detail", "/computers/", tag="Computer")
57 |
58 |
59 | @app.route("/")
60 | def hello():
61 | db.create_all() # use for only for dev
62 | return {"message": "Hello world!"}
63 |
64 |
65 | if __name__ == "__main__":
66 | app.run()
67 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/config.py:
--------------------------------------------------------------------------------
1 | class BaseConfig:
2 | SQLALCHEMY_DATABASE_URI = "sqlite:////:memory:"
3 | SQLALCHEMY_ECHO = False
4 | SQLALCHEMY_TRACK_MODIFICATIONS = False
5 |
6 | # swagger
7 |
8 | OPENAPI_URL_PREFIX = "/docs"
9 | OPENAPI_VERSION = "2.0.0"
10 | OPENAPI_SWAGGER_UI_PATH = "/"
11 | OPENAPI_SWAGGER_UI_VERSION = "3.45.0"
12 |
13 |
14 | class DevelopmentConfig(BaseConfig):
15 | DEBUG = True
16 | SQLALCHEMY_DATABASE_URI = "sqlite:///./flask-jsonapi.db"
17 | SQLALCHEMY_ECHO = True
18 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .person import Person
2 | from .computer import Computer
3 |
4 | __all__ = (
5 | "Person",
6 | "Computer",
7 | )
8 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/models/computer.py:
--------------------------------------------------------------------------------
1 | from models.database import db
2 |
3 |
4 | class Computer(db.Model):
5 | id = db.Column(db.Integer, primary_key=True)
6 | serial = db.Column(db.String)
7 | person_id = db.Column(db.Integer, db.ForeignKey("person.id"))
8 | person = db.relationship("Person", back_populates="computers")
9 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/models/database.py:
--------------------------------------------------------------------------------
1 | from flask_sqlalchemy import SQLAlchemy
2 |
3 | db = SQLAlchemy()
4 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/models/person.py:
--------------------------------------------------------------------------------
1 | from datetime import date
2 |
3 | from .database import db
4 |
5 |
6 | class Person(db.Model):
7 | class Meta:
8 | required_fields = {
9 | "verbose_name": ["name", "birth_date"],
10 | }
11 |
12 | id = db.Column(db.Integer, primary_key=True)
13 | name = db.Column(db.String)
14 | birth_date = db.Column(db.Date)
15 | computers = db.relationship("Computer", back_populates="person")
16 | email = db.Column(db.String)
17 |
18 | @property
19 | def verbose_name(self):
20 | timedelta = date.today() - self.birth_date
21 | return f"{self.name} ({int(timedelta.days // 365.25)} y.o.)"
22 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/requirements.txt:
--------------------------------------------------------------------------------
1 | apispec==4.7.1
2 | click==8.0.1
3 | ComboJSONAPI==1.1.1
4 | Flask==1.1.2
5 | Flask-COMBO-JSONAPI==1.0.7
6 | Flask-SQLAlchemy==2.5.1
7 | itsdangerous==2.0.1
8 | Jinja2==3.0.1
9 | MarkupSafe==2.0.1
10 | marshmallow==3.2.1
11 | marshmallow-jsonapi==0.24.0
12 | PyYAML==5.4.1
13 | simplejson==3.17.3
14 | SQLAlchemy==1.3.24
15 | Werkzeug==2.0.1
16 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/schemas/__init__.py:
--------------------------------------------------------------------------------
1 | from .person import PersonSchema
2 | from .computer import ComputerSchema
3 |
4 | __all__ = (
5 | "PersonSchema",
6 | "ComputerSchema",
7 | )
8 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/schemas/computer.py:
--------------------------------------------------------------------------------
1 | from marshmallow_jsonapi import fields
2 | from marshmallow_jsonapi.flask import Schema
3 |
4 | from combojsonapi.utils import Relationship
5 |
6 |
7 | class ComputerSchema(Schema):
8 | class Meta:
9 | type_ = "computer"
10 | self_view = "computer_detail"
11 | self_view_kwargs = {"id": ""}
12 | self_view_many = "computer_list"
13 |
14 | id = fields.Integer(as_string=True, dump_only=True)
15 | serial = fields.String(required=True)
16 | owner = Relationship(
17 | type_="person",
18 | attribute="person",
19 | schema="PersonSchema",
20 | nested="PersonSchema",
21 | related_view="person_detail",
22 | related_view_kwargs={"id": ""},
23 | self_view="computer_person",
24 | self_view_kwargs={"id": ""},
25 | )
26 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/schemas/person.py:
--------------------------------------------------------------------------------
1 | from marshmallow_jsonapi import fields
2 | from marshmallow_jsonapi.flask import Schema
3 | from combojsonapi.utils import Relationship
4 |
5 |
6 | class PersonSchema(Schema):
7 | class Meta:
8 | type_ = "person"
9 | self_view_many = "person_list"
10 | self_view = "person_detail"
11 | self_view_kwargs = {"id": ""}
12 |
13 | id = fields.Integer(as_string=True)
14 | name = fields.String()
15 | birth_date = fields.Date()
16 | verbose_name = fields.String(dump_only=True)
17 | email = fields.Email()
18 | computers = Relationship(
19 | type_="computer",
20 | schema="ComputerSchema",
21 | nested="ComputerSchema",
22 | many=True,
23 | related_view="computer_list",
24 | # related_view_kwargs={"id": ""},
25 | self_view="person_computers",
26 | self_view_kwargs={"id": ""},
27 | )
28 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/__init__.py:
--------------------------------------------------------------------------------
1 | from .person import PersonList, PersonDetail, PersonRelationship
2 | from .computer import ComputerList, ComputerDetail
3 |
4 | __all__ = (
5 | "PersonList",
6 | "PersonDetail",
7 | "PersonRelationship",
8 | "ComputerList",
9 | "ComputerDetail",
10 | )
11 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/computer.py:
--------------------------------------------------------------------------------
1 | from flask_combo_jsonapi import ResourceList, ResourceDetail
2 |
3 |
4 | from models import Computer
5 | from models.database import db
6 | from schemas import ComputerSchema
7 | from .permissions.computer import ComputerPermission
8 |
9 |
10 | class ComputerList(ResourceList):
11 | schema = ComputerSchema
12 | data_layer = {
13 | "session": db.session,
14 | "model": Computer,
15 | "permission_get": [ComputerPermission],
16 | }
17 |
18 |
19 | class ComputerDetail(ResourceDetail):
20 | schema = ComputerSchema
21 | data_layer = {
22 | "session": db.session,
23 | "model": Computer,
24 | "permission_get": [ComputerPermission],
25 | }
26 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/permissions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/flask-jsonapi.20.08.2021/views/permissions/__init__.py
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/permissions/base.py:
--------------------------------------------------------------------------------
1 | from combojsonapi.permission import PermissionMixin, PermissionForGet, PermissionUser
2 |
3 |
4 | class BaseAllowGetAllPermission(PermissionMixin):
5 | ALL_FIELDS = []
6 |
7 | def get(
8 | self,
9 | *args,
10 | many=True,
11 | user_permission: PermissionUser = None,
12 | **kwargs,
13 | ) -> PermissionForGet:
14 | """
15 | Allow all the declared columns
16 | """
17 | self.permission_for_get.allow_columns = (self.ALL_FIELDS, 10)
18 | return self.permission_for_get
19 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/permissions/computer.py:
--------------------------------------------------------------------------------
1 | from .base import BaseAllowGetAllPermission
2 |
3 |
4 | class ComputerPermission(BaseAllowGetAllPermission):
5 | ALL_FIELDS = [
6 | "id",
7 | "serial",
8 | "owner",
9 | ]
10 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/permissions/person.py:
--------------------------------------------------------------------------------
1 | from .base import BaseAllowGetAllPermission
2 |
3 |
4 | class PersonPermission(BaseAllowGetAllPermission):
5 | ALL_FIELDS = [
6 | "id",
7 | "name",
8 | "birth_date",
9 | # "email",
10 | "verbose_name",
11 | "computers",
12 | ]
13 |
--------------------------------------------------------------------------------
/open-lessons/flask-jsonapi.20.08.2021/views/person.py:
--------------------------------------------------------------------------------
1 | from combojsonapi.event import EventPlugin
2 | from flask_combo_jsonapi import ResourceList, ResourceDetail, ResourceRelationship
3 | from flask_combo_jsonapi.data_layers.alchemy import SqlalchemyDataLayer
4 | from flask_combo_jsonapi.exceptions import ObjectNotFound
5 | from sqlalchemy.orm.exc import NoResultFound
6 |
7 | from models import Person, Computer
8 | from models.database import db
9 | from schemas import PersonSchema
10 | from views.permissions.person import PersonPermission
11 |
12 |
13 | class PersonDetailSqlalchemyDataLayer(SqlalchemyDataLayer):
14 |
15 | def before_get_object(self, view_kwargs):
16 | if not view_kwargs.get("computer_id"):
17 | return
18 |
19 | try:
20 | # result = self.session.query(Computer).values("id", "person_id").filter_by(
21 | computer = self.session.query(Computer).filter_by(
22 | id=view_kwargs["computer_id"],
23 | ).one()
24 | except NoResultFound:
25 | raise ObjectNotFound(
26 | "Computer: {} not found".format(view_kwargs["computer_id"]),
27 | source={"parameter": 'computer_id'},
28 | )
29 | else:
30 | view_kwargs["id"] = computer.person_id
31 |
32 |
33 | class PersonListEvents(EventPlugin):
34 | def event_get_info(self):
35 | return {"message": "Hello Person List events!"}
36 |
37 | def event_refresh_persons(self):
38 | return {"message": "Persons refreshed!"}
39 |
40 |
41 | class PersonList(ResourceList):
42 | schema = PersonSchema
43 | events = PersonListEvents
44 | # methods = ["GET"]
45 | data_layer = {
46 | "session": db.session,
47 | "model": Person,
48 | "permission_get": [PersonPermission],
49 | # "permission_post": [PersonPermission],
50 | }
51 |
52 |
53 | class PersonDetailEvents(EventPlugin):
54 | def event_update_online_status(self, *args, **kwargs):
55 | # language=YAML
56 | """
57 | ---
58 | summary: Update person's online status
59 | tags:
60 | - Person
61 | parameters:
62 | - in: path
63 | name: id
64 | required: True
65 | type: integer
66 | format: int32
67 | description: 'person id'
68 | consumes:
69 | - application/json
70 | responses:
71 | 200:
72 | description: update status
73 | """
74 | # some swagger description
75 | person_id = kwargs["id"]
76 | # person update status
77 | return {"status": "updated", "person_id": person_id}
78 |
79 | event_update_online_status.extra = {
80 | "url_suffix": "update-online",
81 | "method": "PUT",
82 | }
83 |
84 |
85 | class PersonDetail(ResourceDetail):
86 | schema = PersonSchema
87 | events = PersonDetailEvents
88 | # methods = ["GET", "PATCH"]
89 | # methods = ["GET"]
90 | data_layer = {
91 | "class": PersonDetailSqlalchemyDataLayer,
92 | "session": db.session,
93 | "model": Person,
94 | "permission_get": [PersonPermission],
95 | # "permission_patch": [PersonPermission],
96 | # "permission_put": [PersonPermission],
97 | # "permission_delete": [PersonPermission],
98 | }
99 |
100 |
101 | class PersonRelationship(ResourceRelationship):
102 | schema = PersonSchema
103 | data_layer = {
104 | 'session': db.session,
105 | 'model': Person
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/main.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request
2 |
3 | from views.products import products_app
4 |
5 | app = Flask(__name__)
6 | app.config.update(ENV="development")
7 |
8 | app.register_blueprint(
9 | products_app,
10 | url_prefix="/products",
11 | )
12 |
13 |
14 | @app.route("/")
15 | def hello_world():
16 | # another_func()
17 | return "Hello, World!!
"
18 |
19 |
20 | def another_func():
21 | print(request)
22 | print(request.path)
23 |
24 |
25 | @app.route("/hello/")
26 | def hello_view():
27 | # another_func()
28 | name = request.args.get("name", "World")
29 | name = name.strip()
30 | if not name:
31 | name = "World"
32 | return {"message": f"Hello {name}!"}
33 |
34 |
35 | @app.route("/items//")
36 | def get_item(item_id: int):
37 | return {"item_id": item_id}
38 |
39 |
40 | @app.route("/items//")
41 | def get_item_str(item_id: str):
42 | return {"item": {"id": item_id.lower()}}
43 |
44 |
45 | if __name__ == '__main__':
46 | app.run(debug=True)
47 |
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/requirements.txt:
--------------------------------------------------------------------------------
1 | click==8.1.3
2 | Flask==2.1.2
3 | itsdangerous==2.1.2
4 | Jinja2==3.1.2
5 | MarkupSafe==2.1.1
6 | Werkzeug==2.1.2
7 |
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {% block title %}
7 | Base Title
8 | {% endblock %}
9 |
10 |
11 |
12 | {% block body %}
13 | Base Body
14 | {# #}
15 | {# {% block small %}#}
16 | {# small text#}
17 | {# {% endblock %}#}
18 | {# #}
19 | {% endblock %}
20 |
21 |
26 |
27 |
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/templates/products/add.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block body %}
4 |
5 |
11 |
12 | {% endblock %}
13 |
14 | {% block title %}
15 | Add product
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/templates/products/details.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Product #{{ product_id }}
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Product {{ product_name }}
9 |
10 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/templates/products/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}
4 | Products List
5 | {% endblock %}
6 |
7 | {% block body %}
8 | Products list:
9 |
20 |
24 | {% endblock %}
25 |
26 |
27 | {#{% block small %}#}
28 | {#some products#}
29 | {#{% endblock %}#}
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/views/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/flask.23.05.2022/views/__init__.py
--------------------------------------------------------------------------------
/open-lessons/flask.23.05.2022/views/products.py:
--------------------------------------------------------------------------------
1 | from flask import (
2 | Blueprint,
3 | render_template,
4 | request,
5 | redirect,
6 | url_for,
7 | )
8 | from werkzeug.exceptions import BadRequest, NotFound
9 |
10 | products_app = Blueprint("products_app", __name__)
11 |
12 | PRODUCTS = {
13 | 1: "Laptop",
14 | 2: "Desktop",
15 | 3: "Smartphone",
16 | }
17 |
18 |
19 | @products_app.route("/", endpoint="list")
20 | def products_list():
21 | return render_template(
22 | "products/list.html",
23 | products=list(PRODUCTS.items()),
24 | )
25 |
26 |
27 | @products_app.route("//", endpoint="details")
28 | def get_product(product_id: int):
29 | product_name = PRODUCTS.get(product_id)
30 | if product_name is None:
31 | raise NotFound(f"product #{product_id} doesn't exist!")
32 | return render_template(
33 | "products/details.html",
34 | product_id=product_id,
35 | product_name=product_name,
36 | )
37 |
38 |
39 | @products_app.route("/add/", methods=["GET", "POST"], endpoint="add")
40 | def add_product():
41 | if request.method == "GET":
42 | return render_template("products/add.html")
43 |
44 | # if request.method == "POST":
45 | # pass
46 |
47 | product_name = request.form.get("product-name")
48 | if not product_name:
49 | raise BadRequest("Field `product-name` required!")
50 |
51 | product_id = len(PRODUCTS) + 1
52 | PRODUCTS[product_id] = product_name
53 | url = url_for("products_app.details", product_id=product_id)
54 | return redirect(url)
55 |
--------------------------------------------------------------------------------
/open-lessons/helper-funcs.09.02.2023/requirements.txt:
--------------------------------------------------------------------------------
1 | jupyter==1.0.0
2 | jupyter-console==6.4.4
3 | jupyter-events==0.6.3
4 | jupyter_client==8.0.2
5 | jupyter_core==5.2.0
6 | jupyter_server==2.2.1
7 | jupyter_server_terminals==0.4.4
8 | jupyterlab-pygments==0.2.2
9 | jupyterlab-widgets==3.0.5
10 |
--------------------------------------------------------------------------------
/open-lessons/helper-funcs.21.02.2022/requirements.txt:
--------------------------------------------------------------------------------
1 | appnope==0.1.2
2 | argon2-cffi==21.3.0
3 | argon2-cffi-bindings==21.2.0
4 | asttokens==2.0.5
5 | attrs==21.4.0
6 | backcall==0.2.0
7 | black==22.1.0
8 | bleach==4.1.0
9 | cffi==1.15.0
10 | click==8.0.4
11 | debugpy==1.5.1
12 | decorator==5.1.1
13 | defusedxml==0.7.1
14 | entrypoints==0.4
15 | executing==0.8.2
16 | ipykernel==6.9.1
17 | ipython==8.0.1
18 | ipython-genutils==0.2.0
19 | jedi==0.18.1
20 | Jinja2==3.0.3
21 | jsonschema==4.4.0
22 | jupyter-client==7.1.2
23 | jupyter-core==4.9.2
24 | jupyterlab-pygments==0.1.2
25 | MarkupSafe==2.1.0
26 | matplotlib-inline==0.1.3
27 | mistune==0.8.4
28 | mypy-extensions==0.4.3
29 | nbclient==0.5.11
30 | nbconvert==6.4.2
31 | nbformat==5.1.3
32 | nest-asyncio==1.5.4
33 | notebook==6.4.8
34 | packaging==21.3
35 | pandocfilters==1.5.0
36 | parso==0.8.3
37 | pathspec==0.9.0
38 | pexpect==4.8.0
39 | pickleshare==0.7.5
40 | platformdirs==2.5.1
41 | prometheus-client==0.13.1
42 | prompt-toolkit==3.0.28
43 | ptyprocess==0.7.0
44 | pure-eval==0.2.2
45 | pycparser==2.21
46 | Pygments==2.11.2
47 | pyparsing==3.0.7
48 | pyrsistent==0.18.1
49 | python-dateutil==2.8.2
50 | pyzmq==22.3.0
51 | Send2Trash==1.8.0
52 | six==1.16.0
53 | stack-data==0.2.0
54 | terminado==0.13.1
55 | testpath==0.5.0
56 | tomli==2.0.1
57 | tornado==6.1
58 | traitlets==5.1.1
59 | typing_extensions==4.1.1
60 | wcwidth==0.2.5
61 | webencodings==0.5.1
62 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/file.txt:
--------------------------------------------------------------------------------
1 | some file text
2 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/interfaces/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/interfaces-and-protocols-27.08/interfaces/__init__.py
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/interfaces/light_bulb.py:
--------------------------------------------------------------------------------
1 | from .switchable import Switchable
2 |
3 |
4 | class LightBulb(Switchable):
5 | def on(self):
6 | print("light bulb on")
7 |
8 | def off(self):
9 | print("light bulb off")
10 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/interfaces/power_switch.py:
--------------------------------------------------------------------------------
1 | from typing import TYPE_CHECKING
2 |
3 | if TYPE_CHECKING:
4 | from .switchable import Switchable
5 |
6 |
7 | class PowerSwitch:
8 | def __init__(self, client: "Switchable"):
9 | self.client = client
10 | self.on = False
11 | self.client.off()
12 |
13 | def toggle(self):
14 | if self.on:
15 | self.client.off()
16 | self.on = False
17 | else:
18 | self.client.on()
19 | self.on = True
20 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/interfaces/switchable.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 |
3 |
4 | class Switchable(ABC):
5 | @abstractmethod
6 | def on(self):
7 | raise NotImplementedError
8 |
9 | @abstractmethod
10 | def off(self):
11 | raise NotImplementedError
12 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/interfaces/wireless_mouse.py:
--------------------------------------------------------------------------------
1 | from .switchable import Switchable
2 |
3 |
4 | class WirelessMouse(Switchable):
5 | def on(self):
6 | print("mouse on")
7 |
8 | def off(self):
9 | print("mouse off")
10 |
11 | def track(self):
12 | print("mouse tracking")
13 |
14 | def click(self):
15 | print("mouse clicking")
16 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/main.py:
--------------------------------------------------------------------------------
1 | from interfaces.switchable import Switchable
2 | from interfaces.light_bulb import LightBulb
3 | from interfaces.wireless_mouse import WirelessMouse
4 | from interfaces.power_switch import PowerSwitch
5 |
6 |
7 | class Oven(Switchable):
8 | def off(self):
9 | print("oven off")
10 |
11 | def on(self):
12 | print("oven on")
13 |
14 |
15 | def example_interfaces():
16 | # sw = Switchable()
17 | # print("SW:", sw)
18 | bulb = LightBulb()
19 | print("bulb:", bulb)
20 | bulb.on()
21 | bulb.off()
22 |
23 | mouse = WirelessMouse()
24 | print("mouse:", mouse)
25 | mouse.on()
26 | mouse.off()
27 |
28 | switch = PowerSwitch(client=bulb)
29 | switch.toggle()
30 | switch.toggle()
31 | switch.toggle()
32 |
33 | mouse_switch = PowerSwitch(client=mouse)
34 | mouse_switch.toggle()
35 | mouse_switch.toggle()
36 | mouse_switch.toggle()
37 |
38 | oven = Oven()
39 | oven_switch = PowerSwitch(client=oven)
40 | oven_switch.toggle()
41 |
42 |
43 | def main():
44 | example_interfaces()
45 |
46 |
47 | if __name__ == "__main__":
48 | main()
49 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/protocols/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/interfaces-and-protocols-27.08/protocols/__init__.py
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/protocols/example_message_receiver.py:
--------------------------------------------------------------------------------
1 | from typing import Protocol
2 |
3 |
4 | class CallbackProtocol(Protocol):
5 | __name__: str
6 |
7 | def __call__(
8 | self,
9 | message: str,
10 | size: int,
11 | ) -> None: ...
12 |
13 |
14 | class Callback:
15 |
16 | @property
17 | def __name__(self):
18 | return self.__class__.__name__
19 |
20 | def __call__(self, message: str, size: int) -> None:
21 | print("(cls) received message:", message, "of size", size)
22 |
23 |
24 | def process_incoming_message(
25 | data: bytes,
26 | callback: CallbackProtocol,
27 | ) -> None:
28 | message = data.decode("utf-8")
29 | message_size = len(message)
30 | print("notify callback", callback.__name__)
31 | callback(
32 | message=message,
33 | size=message_size,
34 | )
35 | callback(
36 | message,
37 | size=message_size,
38 | )
39 | callback(
40 | message,
41 | message_size,
42 | )
43 |
44 |
45 | def my_callback(message: str, size: int) -> None:
46 | print("received message:", message, "of size", size)
47 |
48 |
49 | def main() -> None:
50 | data = b"Hello World!"
51 | process_incoming_message(
52 | data=data,
53 | callback=my_callback,
54 | )
55 | callback = Callback()
56 |
57 | process_incoming_message(
58 | data=data,
59 | # беда
60 | callback=callback,
61 | )
62 |
63 |
64 | if __name__ == "__main__":
65 | main()
66 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/protocols/example_readable.py:
--------------------------------------------------------------------------------
1 | from typing import Protocol
2 |
3 |
4 | class Readable(Protocol):
5 | def read(self) -> bytes: ...
6 |
7 |
8 | class FileReader:
9 | def __init__(self, filename: str):
10 | self.filename = filename
11 |
12 | def read(self) -> bytes:
13 | """
14 | Тут мы читаем файл
15 | """
16 | print("reading file", self.filename)
17 | return self.filename.encode()
18 |
19 |
20 | class APIReader:
21 | def __init__(self, api_path: str):
22 | self.api_path = api_path
23 |
24 | def read(self) -> bytes:
25 | """
26 | Тут мы читаем внешний API
27 | """
28 | print("reading api path", self.api_path)
29 | return self.api_path.encode()
30 |
31 |
32 | def read_data_to_db(readable: Readable):
33 | print("save to db data:", readable.read())
34 |
35 |
36 | def main():
37 | file_reader = FileReader("some-filename.txt")
38 | api_reader = APIReader("some/api/path")
39 | file = open(
40 | "/Users/suren/MyFiles/OTUS/lessons/open-lessons/py-basic-proto-and-interfaces-27.08.2024/file.txt",
41 | mode="rb",
42 | )
43 | read_data_to_db(file_reader)
44 | read_data_to_db(api_reader)
45 | read_data_to_db(file)
46 |
47 |
48 | if __name__ == "__main__":
49 | main()
50 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/protocols/example_runtime_checkable_protocol.py:
--------------------------------------------------------------------------------
1 | from typing import Protocol, runtime_checkable
2 |
3 |
4 | @runtime_checkable
5 | class Draggable(Protocol):
6 | weight: int
7 | handles: int
8 |
9 |
10 | class Bag:
11 | def __init__(self, weight: int, handles: int = 2) -> None:
12 | self.weight = weight
13 | self.handles = handles
14 |
15 |
16 | class SuitCase:
17 | def __init__(self, weight: int, handles: int = 1) -> None:
18 | self.weight = weight
19 | self.handles = handles
20 |
21 |
22 | class Cat:
23 | def __init__(self, name: str, weight: int) -> None:
24 | self.name = name
25 | self.weight = weight
26 |
27 |
28 | def drag(item: Draggable) -> None:
29 | print("Drag", item.weight, "kg using", item.handles, "handles")
30 |
31 |
32 | def main():
33 | bag = Bag(15)
34 | case = SuitCase(7)
35 | cat = Cat("Cat", 5)
36 | elements = [bag, case, cat]
37 | for el in elements:
38 | if isinstance(el, Draggable):
39 | drag(el)
40 | else:
41 | print(el, "is not draggable")
42 |
43 |
44 | if __name__ == "__main__":
45 | main()
46 |
--------------------------------------------------------------------------------
/open-lessons/interfaces-and-protocols-27.08/requirements.txt:
--------------------------------------------------------------------------------
1 | mypy==1.11.2
2 | mypy-extensions==1.0.0
3 | typing_extensions==4.12.2
4 |
--------------------------------------------------------------------------------
/open-lessons/oop.18.08.2022/step_1.py:
--------------------------------------------------------------------------------
1 | """
2 | This is a documentation string
3 | """
4 |
5 | some_object = object()
6 | print(some_object)
7 |
8 | a = 1
9 | print(type(a))
10 | b = ''
11 | print(type(b))
12 |
13 | c = int()
14 | d = str()
15 |
16 | print(type(c))
17 | print(type(d))
18 |
19 |
20 | # class User(object):
21 | class User:
22 | """
23 | Doc string for user
24 | """
25 |
26 |
27 | print(int)
28 | print(User)
29 |
30 | user1 = User()
31 | print(user1)
32 | print(type(user1))
33 |
34 | user1.name = "John"
35 | print("user name:", user1.name)
36 | print("user1 dict", user1.__dict__)
37 |
38 | user2 = User()
39 | user2.name = "Sam"
40 | print("user2 name", user2.name)
41 | print("user2 dict", user2.__dict__)
42 | user2.age = 20
43 | print("user2 age", user2.age)
44 | print("user2 dict", user2.__dict__)
45 |
46 | print("user1 dict", user1.__dict__)
47 | print(User.mro())
48 |
49 | # print("user1 age", user1.age)
50 |
51 |
52 | # print("__name__:", __name__)
53 |
54 |
--------------------------------------------------------------------------------
/open-lessons/oop.18.08.2022/step_2.py:
--------------------------------------------------------------------------------
1 | class User:
2 | name = "John"
3 | age = "30"
4 |
5 |
6 | """
7 | # дескрипторы: поиск атрибутов на объектах
8 | - на экземпляре
9 | - на классе экземпляра
10 | - на родителях класса (типа)
11 | - на родителях родителя (до самого верха -- object)
12 | !- если не найдено - ошибка!
13 | """
14 |
15 | print("User name", User.name)
16 | print("User age", User.age)
17 |
18 |
19 | user1 = User()
20 | print(User.mro())
21 | print("user1 name", user1.name)
22 | print("user1 age", user1.age)
23 | print("user1 dict", user1.__dict__)
24 |
25 | user1.name = "Nick"
26 | print("user1 name", user1.name)
27 | print("user1 dict", user1.__dict__)
28 | print("User name", User.name)
29 |
30 | user2 = User()
31 | print("user2 name", user2.name)
32 |
33 | User.name = "Bob"
34 | print("user1 name", user1.name)
35 | print("user2 name", user2.name)
36 |
37 |
--------------------------------------------------------------------------------
/open-lessons/oop.18.08.2022/step_3.py:
--------------------------------------------------------------------------------
1 | class User:
2 | def __init__(self):
3 | self.name = None
4 | self.age = None
5 |
6 |
7 | # def init(user):
8 | # user.name = None
9 | # user.age = None
10 |
11 |
12 | user1 = User()
13 | # init(user1)
14 | print(user1.__dict__)
15 |
16 |
--------------------------------------------------------------------------------
/open-lessons/oop.18.08.2022/step_4.py:
--------------------------------------------------------------------------------
1 | class Product:
2 | def __init__(self, name, price):
3 | self.name = name
4 | self.price = price
5 |
6 | def __str__(self):
7 | return f"Product(name={self.name!r}, price={self.price})"
8 |
9 | def __repr__(self):
10 | return str(self)
11 | # return "Product"
12 |
13 | def make_discount(self, discount_percent):
14 | """
15 | Applies discount in %
16 | :param discount_percent:
17 | :return:
18 | """
19 | self.price *= (100 - discount_percent) / 100
20 |
21 |
22 | # def init_product(product, name, price):
23 | # product.name = name
24 | # product.price = price
25 | #
26 | #
27 | # Product.__init__ = init_product
28 |
29 | product_laptop = Product("Laptop 1", 2000)
30 | # init_product(product_laptop, "Laptop 2", 2999)
31 |
32 | print(product_laptop.name)
33 | print(repr(product_laptop.name))
34 | print(product_laptop.price)
35 | print(product_laptop.__dict__)
36 |
37 | print(type(product_laptop))
38 | print(product_laptop)
39 | print(repr(product_laptop))
40 |
41 |
42 | product_smartphone = Product("Smartphone 1", 1000)
43 |
44 | print(product_smartphone)
45 | print(product_laptop)
46 |
47 | product_smartphone.make_discount(10)
48 | product_laptop.make_discount(20)
49 |
50 | print(product_smartphone)
51 | print(product_laptop)
52 |
--------------------------------------------------------------------------------
/open-lessons/oop.18.08.2022/step_5.py:
--------------------------------------------------------------------------------
1 | class Date:
2 | def __init__(self, year, month, day):
3 | self.year = year
4 | self.month = month
5 | self.day = day
6 | # self.__demo_hidden = "super secret!!"
7 |
8 | def copy(self):
9 | return Date(year=self.year, month=self.month, day=self.day)
10 |
11 | @classmethod
12 | def from_date_string(cls, date_string: str):
13 | """
14 | Parse date from string '2022-08-18'
15 |
16 | :param date_string:
17 | :return:
18 | """
19 | year, month, day = map(int, date_string.split("-"))
20 | date = cls(year=year, month=month, day=day)
21 | return date
22 |
23 | @staticmethod
24 | def is_date_string_valid(date_string: str):
25 | if date_string.count("-") != 2:
26 | return False
27 |
28 | year, month, day = map(int, date_string.split("-"))
29 | return day <= 31 and month <= 12 and year <= 3999
30 |
31 | def __str__(self):
32 | return (
33 | f"{self.__class__.__name__}(year={self.year}, "
34 | f"month={self.month}, day={self.day})"
35 | )
36 |
37 |
38 | # Date.is_date_string_valid()
39 |
40 |
41 | print(Date)
42 | print(Date.__name__)
43 |
44 |
45 | date1 = Date(2022, 8, 18)
46 | date2 = Date(1934, 3, 24)
47 |
48 | print(date1)
49 | print(date2)
50 |
51 | date3 = date1
52 | print(date3)
53 | print(date1)
54 | date3.month = 1
55 | date1.year = 2000
56 |
57 | print(date3)
58 | print(date1)
59 |
60 | date4 = date1.copy()
61 | print("---")
62 | print(date1)
63 | print(date4)
64 |
65 | date1.year = 2002
66 | date4.day = 22
67 | date4.month = 2
68 | print(date1)
69 | print(date4)
70 |
71 | print('***')
72 |
73 | # date5 = date4.from_date_string("")
74 | date5 = Date.from_date_string("2048-11-22")
75 | print(date5)
76 |
77 | print(Date.is_date_string_valid("200"))
78 | print(Date.is_date_string_valid("2048-11-22"))
79 | print(Date.is_date_string_valid("4048-11-22"))
80 | print(Date.is_date_string_valid("2048-11-31"))
81 |
82 | # Date.copy(date4)
83 | # date4.copy()
84 |
85 |
86 | # print(date1.__dict__)
87 | # # print(date1.__demo_hidden)
88 | # print(date2._Date__demo_hidden)
89 | # date2._Date__demo_hidden = "not secret!"
90 | # print(date2._Date__demo_hidden)
91 | # print(date2.__dict__)
92 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.04.2023/step0.py:
--------------------------------------------------------------------------------
1 | print("Hello world!")
2 |
3 | some_object = object()
4 |
5 | print(some_object)
6 |
7 |
8 | def my_func():
9 | print("hello")
10 | # return "abc"
11 | # return None
12 | # return
13 |
14 |
15 | print(my_func)
16 |
17 | res = my_func()
18 | print("result:", res)
19 |
20 |
21 | class MyObject(object):
22 | pass
23 |
24 |
25 | print(object)
26 | print(MyObject)
27 | my_obj = MyObject()
28 | print(some_object)
29 | print(my_obj)
30 |
31 | print("MyObject mro", MyObject.mro())
32 |
33 |
34 | class User:
35 | pass
36 |
37 |
38 | user = User()
39 | print(User)
40 | print(user)
41 | print("user type:", type(user))
42 | print("User type:", type(User))
43 |
44 | print("User mro", User.mro())
45 |
46 | line_hello = "hello"
47 | # line_hello = str("hello")
48 | print(line_hello)
49 | line_hello_type = type(line_hello)
50 | print(line_hello_type)
51 | print(line_hello_type is str)
52 | print("str type:", type(str))
53 | print("type is type", type(str) is type)
54 | print("type mro", type.mro(type))
55 | print("type object", type(object))
56 |
57 |
58 | john = User()
59 | print("john dict", john.__dict__)
60 | # print("john name", john.name)
61 | john.name = "John"
62 | print("john:", john, john.name)
63 | print("john dict", john.__dict__)
64 |
65 |
66 | sam = User()
67 | print("sam dict", sam.__dict__)
68 |
69 | sam.username = "sam3000"
70 | print("sam:", sam, sam.username)
71 | # print("sam:", sam, sam.name)
72 |
73 | print("john dict", john.__dict__)
74 | print("sam dict", sam.__dict__)
75 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.04.2023/step1.py:
--------------------------------------------------------------------------------
1 | class User:
2 | name = None
3 | username = None
4 | age = None
5 |
6 |
7 | sam = User()
8 | print("sam:", sam, sam.name, sam.age)
9 | print("sam dict:", sam.__dict__)
10 |
11 | john = User()
12 | print(john, john.name, john.age)
13 | print("john dict:", john.__dict__)
14 |
15 | print("dict eq?", john.__dict__ == sam.__dict__)
16 | print("dict same object?", john.__dict__ is sam.__dict__)
17 | john.name = "John"
18 | john.age = 42
19 | print(john, john.name, john.age)
20 | print(john.__dict__)
21 |
22 | print("sam:", sam, sam.name, sam.age)
23 |
24 | print("___")
25 | print("sam dict:", sam.__dict__)
26 | print("john dict:", john.__dict__)
27 |
28 | print("sam username", sam.username)
29 | print("john username", john.username)
30 |
31 |
32 | print(User.__dict__)
33 | User.username = "default-username"
34 | print(User.__dict__)
35 |
36 | print("sam username", sam.username)
37 | print("john username", john.username)
38 |
39 | print("sam dict:", sam.__dict__)
40 | print("john dict:", john.__dict__)
41 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.04.2023/step2.py:
--------------------------------------------------------------------------------
1 | class User:
2 | def __init__(
3 | self,
4 | name: str,
5 | username: str | None = None,
6 | age: int | None = None,
7 | ):
8 | self.name = name
9 | self.username = username
10 | self.age = age
11 |
12 | def increase_age(self):
13 | if self.age is not None:
14 | self.age += 1
15 | else:
16 | self.age = 0
17 |
18 |
19 | print(User.__dict__)
20 |
21 | john = User(name="John")
22 | print("john", john, john.__dict__)
23 |
24 | sam = User(name="Sam", username="sam3000")
25 | print(sam.name)
26 | print("sam", sam, sam.__dict__)
27 | print("sam name:", sam.name)
28 | print("sam username:", sam.username)
29 | # sam.username = "sam3000"
30 | # print("sam", sam, sam.__dict__)
31 |
32 | print(sam.age)
33 | sam.increase_age()
34 | print(sam.age)
35 | sam.increase_age()
36 | print(sam.age)
37 | sam.increase_age()
38 | print(sam.age)
39 |
40 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.04.2023/step3.py:
--------------------------------------------------------------------------------
1 | class Shape:
2 |
3 | def get_area(self):
4 | print("not enough data")
5 | # return None
6 |
7 |
8 | class Rectangle(Shape):
9 | def __init__(self, a, b):
10 | self.a = a
11 | self.b = b
12 |
13 | # overload - (перегрузка) не бывает в Python
14 | # def method(self, a):
15 | # return a ** 2
16 | #
17 | # def method(self, a, b):
18 | # return a ** b
19 | #
20 | # def method(self, a, b, c):
21 | # return a * b * c
22 |
23 | # override - переопределение
24 | def get_area(self):
25 | return self.a * self.b
26 |
27 | def __str__(self):
28 | return f"{self.__class__.__name__}(a={self.a}, b={self.b})"
29 |
30 |
31 | class Square(Rectangle):
32 | def __init__(self, a):
33 | self.a = a
34 | # self.b = a
35 |
36 | @property
37 | def b(self):
38 | return self.a
39 |
40 | @b.setter
41 | def b(self, value):
42 | # self.a = int(value)
43 | self.a = value
44 |
45 | def __str__(self):
46 | return f"{self.__class__.__name__}(a={self.a})"
47 |
48 |
49 | shape = Shape()
50 | print(shape)
51 | shape.get_area()
52 |
53 | rectangle_1 = Rectangle(2, 6)
54 | print(rectangle_1)
55 | rectangle_2 = Rectangle(3, 5)
56 | print(rectangle_2)
57 |
58 | print("r1 area:", rectangle_1.get_area())
59 | print("r2 area:", rectangle_2.get_area())
60 |
61 | sq1 = Square(6)
62 | print(sq1)
63 | print(sq1.a)
64 | print(sq1.b)
65 | print("sq1 square", sq1.get_area())
66 |
67 | sq1.b = 7
68 | print(sq1)
69 | print(sq1.get_area())
70 |
71 | sq1.a = 9
72 | print(sq1)
73 | print(sq1.get_area())
74 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.08.2022/step_1.py:
--------------------------------------------------------------------------------
1 | a = 1
2 | b = "2"
3 |
4 | c = a + b
5 |
6 | print("c", c)
7 |
8 | d = c ** 2
9 | print("d", d)
10 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.08.2022/step_2.py:
--------------------------------------------------------------------------------
1 | # 1 / 0
2 | # print("never!")
3 |
4 | # print(ZeroDivisionError, ZeroDivisionError.mro())
5 | # print(TypeError, TypeError.mro())
6 | # raise Exception
7 |
8 | def call_exc():
9 | exc = Exception("unexpected error!")
10 | raise exc
11 |
12 |
13 | def another():
14 | raise ValueError
15 | call_exc()
16 |
17 |
18 | def main():
19 | call_exc()
20 |
21 |
22 | if __name__ == '__main__':
23 | another()
24 | main()
25 | print("never!")
26 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.08.2022/step_3.py:
--------------------------------------------------------------------------------
1 | class MyError(Exception):
2 | pass
3 |
4 |
5 | print(MyError, MyError.mro())
6 |
7 | raise MyError("this is an error")
8 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.08.2022/step_4.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 |
4 | class InvalidOperandError(TypeError):
5 | pass
6 |
7 |
8 | class Point:
9 | def __init__(self, x, y):
10 | self.x = x
11 | self.y = y
12 |
13 | def __str__(self):
14 | return str([self.x, self.y])
15 |
16 | def __repr__(self):
17 | return f"{self.__class__.__name__}(" \
18 | f"x={self.x}, y={self.y})"
19 |
20 | # def add(self, other: list | tuple | "Point"):
21 | def add(self, other: Union["Point", list, tuple]):
22 | # if not isinstance(point, self.__class__):
23 | # raise InvalidOperandError(
24 | # f"Should be {self.__class__}, not"
25 | # f" {type(point)}")
26 | if isinstance(other, self.__class__):
27 | return self.__class__(
28 | x=self.x + other.x,
29 | y=self.y + other.y,
30 | )
31 |
32 | if (
33 | isinstance(other, (tuple, list))
34 | and len(other) == 2
35 | ):
36 | x, y = other
37 | return self.__class__(
38 | x=self.x + x,
39 | y=self.y + y,
40 | )
41 |
42 | raise InvalidOperandError(
43 | f"Should be {self.__class__}, not"
44 | f" {type(other)}")
45 |
46 | def __add__(self, other):
47 | return self.add(other)
48 |
49 | # def __radd__(self, other: "Point"):
50 | def __radd__(self, other):
51 | return self.add(other)
52 |
53 | def __iadd__(self, point: "Point"):
54 | if not isinstance(point, self.__class__):
55 | raise InvalidOperandError(
56 | f"Should be {self.__class__}, not"
57 | f" {type(point)}")
58 | self.x += point.x
59 | self.y += point.y
60 | return self
61 |
62 |
63 | p1 = Point(1, 2)
64 | p2 = Point(x=3, y=4)
65 |
66 | print(p1)
67 | print(p2)
68 | print([p1, p2])
69 |
70 | p3 = p1 + p2
71 | print(p3)
72 |
73 | p4 = p3.add(p2)
74 | print(p4)
75 |
76 | res = (5, 7) + p1
77 | print(res)
78 |
79 | print([res, p1 + [1, 2]])
80 |
81 | list1 = [1, 2, 3]
82 | list2 = [4, 5, 6]
83 | print("id l1", id(list1))
84 |
85 | list3 = list1 + list2
86 | print(list1)
87 | print("id l1", id(list1))
88 |
89 | print(list2)
90 | print(list3)
91 |
92 | list1 += list2
93 | print(list1)
94 | print("id l1", id(list1))
95 | print(list2)
96 |
97 | list1.append(7)
98 | print(list1)
99 | print("id l1", id(list1))
100 |
101 |
102 | print([p1, p2])
103 | print("id p1", id(p1))
104 | p1 += p2
105 | print("id p1", id(p1))
106 | print([p1, p2])
107 |
108 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.08.2022/step_5.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | def get_weather_conditions(city: str):
4 | if city.lower() != "moscow":
5 | raise ValueError("invalid city")
6 |
7 | return {
8 | # "temp": 23,
9 | # "humidity": 50,
10 | "rain_chance": 60,
11 | }
12 |
13 |
14 | def rain_tomorrow(city) -> bool | None:
15 | print("Will it rain tomorrow?")
16 | # return True
17 | # return False
18 | try:
19 | conditions = get_weather_conditions(city)
20 | except ValueError:
21 | return None
22 | # return
23 |
24 | if "rain_chance" not in conditions:
25 | return None
26 |
27 | return conditions["rain_chance"] > 50
28 |
29 | # if (rain_chance := conditions.get("rain_chance")) is None:
30 | # return None
31 | # return rain_chance > 50
32 |
33 |
34 | res = rain_tomorrow("Omsk")
35 | # res = get_weather_conditions("Omsk")
36 | print("res for Omsk:", res)
37 |
38 | res = rain_tomorrow("Sochi")
39 | print("res for Sochi:", res)
40 |
41 | res = rain_tomorrow("Moscow")
42 | print("res for Moscow:", res)
43 |
--------------------------------------------------------------------------------
/open-lessons/oop.25.08.2022/step_6.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logger = logging.getLogger(__name__)
4 |
5 |
6 | def div_safe(a, b):
7 | try:
8 | c = a / b
9 | # except ArithmeticError:
10 | # return 1
11 | except ZeroDivisionError:
12 | # logger.exception("divide by zero")
13 | # logger.error("divide by zero")
14 | # logger.warning("divide by zero", exc_info=True)
15 |
16 | print("please don't divide by zero")
17 | return 42
18 | except TypeError as e:
19 | # print(e.__traceback__)
20 | # logger.warning("type error", exc_info=e)
21 |
22 | print("oops", e.args, repr(e))
23 | # return None
24 | # except Exception:
25 | # pass
26 | else:
27 | print("ok div, res:", [a, b], c)
28 | return c
29 | finally:
30 | print("finally for", [a, b])
31 |
32 | print("leaving func, args:", [a, b])
33 |
34 |
35 |
36 | print(div_safe(1, 0))
37 | print(div_safe(2, 1))
38 | print(div_safe(10, 2))
39 | print(div_safe("10", 2))
40 |
--------------------------------------------------------------------------------
/open-lessons/oop.base.11.06.2020/multi_inheritance.py:
--------------------------------------------------------------------------------
1 | class A:
2 | def a(self):
3 | return "A"
4 |
5 |
6 | class B:
7 | def b(self):
8 | return "B"
9 |
10 |
11 | class C:
12 | def c(self):
13 | return "C"
14 |
15 |
16 | class MyMixin():
17 | def __init__(self):
18 | self.__value = 40
19 | self.__value_square = 0
20 | self.update_square()
21 |
22 | def update_square(self):
23 | self.__value_square = self.value ** 2
24 |
25 | @property
26 | def value_square(self):
27 | return self.__value_square
28 |
29 | @property
30 | def value(self):
31 | return self.__value
32 |
33 | @value.setter
34 | def value(self, v):
35 | if v < 0:
36 | raise Exception("Value has to be 0 or greater")
37 | self.__value = v
38 | self.update_square()
39 |
40 |
41 | class MyClass(A, B, C, MyMixin):
42 | def method(self):
43 | return " ".join((self.a(), self.b(), self.c()))
44 |
45 |
46 | if __name__ == '__main__':
47 | print(MyClass.__mro__)
48 | my_cls = MyClass()
49 | print(my_cls.method())
50 |
51 | print("value", my_cls.value)
52 | print("square", my_cls.value_square)
53 | print("set value to 30")
54 | my_cls.value = 30
55 | print("now value", my_cls.value)
56 | print("now value square", my_cls.value_square)
57 |
--------------------------------------------------------------------------------
/open-lessons/oop.base.11.06.2020/people.py:
--------------------------------------------------------------------------------
1 | class Person:
2 | WHAT_TO_SAY_PERSON = "My name is {name} and I'm {age} years old"
3 |
4 | def __init__(self, name: str, age: int):
5 | self.name = name
6 | self.age = age
7 |
8 | def say(self):
9 | speech = self.WHAT_TO_SAY_PERSON.format(name=self.name, age=self.age)
10 | print(speech)
11 | return speech
12 |
13 |
14 | class Employee(Person):
15 | WHAT_TO_SAY_EMPLOYEE = "I work {years} years already and get {salary} annually"
16 |
17 | def __init__(self, name: str, age: int, salary: int, experience: int):
18 | super().__init__(name, age)
19 | self.salary = salary
20 | self.experience = experience
21 |
22 | def say(self):
23 | initial_speech = super().say()
24 | speech = "\n".join((
25 | initial_speech,
26 | self.WHAT_TO_SAY_EMPLOYEE.format(years=self.experience, salary=self.salary)
27 | ))
28 | print(speech)
29 | return speech
30 |
31 |
32 | if __name__ == '__main__':
33 | employee = Employee("John", 30, 100_000, 7)
34 | employee.say()
35 |
--------------------------------------------------------------------------------
/open-lessons/oop.base.11.06.2020/shapes.py:
--------------------------------------------------------------------------------
1 | class BaseShape:
2 | def area(self):
3 | raise NotImplementedError
4 |
5 |
6 | class Rectangle(BaseShape):
7 | def __init__(self, length: int, width: int, **kwargs):
8 | self.length = length
9 | self.width = width
10 | super().__init__(**kwargs)
11 |
12 | def area(self) -> int:
13 | return self.length * self.width
14 |
15 | def perimeter(self) -> int:
16 | return 2 * self.length + 2 * self.width
17 |
18 |
19 | class Square(Rectangle):
20 | def __init__(self, length: int, **kwargs):
21 | super().__init__(length, length, **kwargs)
22 |
23 |
24 | class Triangle(BaseShape):
25 | def __init__(self, base: int, height: int, **kwargs):
26 | self.base = base
27 | self.height = height
28 | super().__init__(**kwargs)
29 |
30 | def tri_area(self) -> float:
31 | return 0.5 * self.base * self.height
32 |
33 |
34 | class RightPyramid(Square, Triangle):
35 | def __init__(self, base: int, slant_height: int):
36 | super().__init__(base=base, height=slant_height, length=base)
37 | self.base = base
38 | self.slant_height = slant_height
39 |
40 | def pyramid_area(self):
41 | base_area = self.area()
42 | side_area = self.tri_area()
43 | return side_area * 4 + base_area
44 |
45 |
46 | if __name__ == '__main__':
47 | rectangle = Rectangle(2, 4)
48 | print("Rectangle:", rectangle.length, rectangle.width)
49 | print("Area:", rectangle.area())
50 | print("Perimeter:", rectangle.perimeter())
51 |
52 | square = Square(5)
53 | print("Square:", square.length)
54 | print("Area:", square.area())
55 | print("Perimeter:", square.perimeter())
56 |
57 | triangle = Triangle(4, 6)
58 | print("triangle area:", triangle.tri_area())
59 |
60 | pyramid = RightPyramid(10, 12)
61 | print(RightPyramid.__mro__)
62 | print("pyramid area", pyramid.pyramid_area())
63 |
--------------------------------------------------------------------------------
/open-lessons/oop.base.11.06.2020/vehicles.py:
--------------------------------------------------------------------------------
1 | class BaseVehicle:
2 | def make_sound(self):
3 | raise NotImplementedError
4 |
5 |
6 | class Car(BaseVehicle):
7 | SOUND = "beep!"
8 | CONSUMPTION = 10
9 |
10 | def __init__(self, fuel: int, doors: int = 5, wheels: int = 4):
11 | """
12 | :param fuel:
13 | :param doors:
14 | :param wheels:
15 | """
16 | self.__fuel = fuel
17 | self.__doors = doors
18 | self.__wheels = wheels
19 |
20 | def __str__(self):
21 | return f"{self.__class__.__name__}. fuel: {self.__fuel}, doors: {self.__doors}, wheels: {self.__wheels}"
22 |
23 | @property
24 | def fuel(self):
25 | return self.__fuel
26 |
27 | def make_sound(self):
28 | print(self.SOUND)
29 | return self.SOUND
30 |
31 | def drive(self, distance: int):
32 | to_spend = self.CONSUMPTION * distance
33 | if self.__fuel < to_spend:
34 | raise Exception("Not enough fuel!")
35 | self.__fuel -= to_spend
36 | print(f"Drive {distance}, spend: {to_spend}, fuel left: {self.__fuel}")
37 | return self.__fuel
38 |
39 | def add_fuel(self, amount: int):
40 | self.__fuel += amount
41 | print(f"Added {amount}, left {self.__fuel}")
42 | return self.__fuel
43 |
44 |
45 | class SportCar(Car):
46 | SOUND = "beep beep!!"
47 | CONSUMPTION = 30
48 |
49 | def __init__(
50 | self,
51 | fuel: int,
52 | doors: int = 2,
53 | wheels: int = 4,
54 | doors_open_upwards: bool = True,
55 | ):
56 | super().__init__(fuel, doors, wheels)
57 | self.doors_open_upwards = doors_open_upwards
58 |
59 |
60 | def drive_by_car(car: Car, distance: int):
61 | print("Going", distance, "by", car)
62 | car.drive(distance)
63 | print("fuel left", car._Car__fuel)
64 |
65 |
66 | if __name__ == '__main__':
67 | car = Car(1000)
68 | print("car:", car)
69 | # car._Car__fuel = 100
70 | # print("car fuel:", car.fuel)
71 | print("Drive, fuel left:", car.drive(15))
72 | print(car)
73 | print("Fuel left:", car.add_fuel(100))
74 |
75 | # car.drive(30)
76 | # car.drive(30)
77 | # car.drive(30)
78 |
79 | sport_car = SportCar(500)
80 |
81 | print("car sound:")
82 | car.make_sound()
83 | print("sport car sound:")
84 | sport_car.make_sound()
85 |
86 | sport_car.drive(10)
87 | sport_car._Car__fuel = 300
88 | sport_car.drive(10)
89 |
90 | car.add_fuel(300)
91 | drive_by_car(car, 70)
92 | sport_car.add_fuel(1000)
93 | drive_by_car(sport_car, 30)
94 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/README.md:
--------------------------------------------------------------------------------
1 | # Знакомство с Protobuf и gRPC. Работа с gRPC на Python
2 |
3 |
4 | ## Ссылки
5 | - https://grpc.github.io/grpc/core/md_doc_g_stands_for.html
6 | - https://grpc.io/docs/languages/python/quickstart/
7 | - https://github.com/protocolbuffers/protobuf/releases/tag/v26.0
8 | - https://protobuf.dev/getting-started/pythontutorial/
9 | - https://github.com/grpc/grpc/tree/master/examples/python
10 |
11 | ## Шаги
12 |
13 | - настроили Poetry
14 | - установили Python пакет `protobuf`
15 | - поставили protobuf на macOS командой
16 | ```shell
17 | brew install protobuf
18 | ```
19 | - компилируем протокол на Python
20 | ```shell
21 | protoc --proto_path=protos --python_out=./pb protos/hello.proto
22 | ```
23 | - компилируем протокол в Python плюс аннотации типов
24 | ```shell
25 | protoc --proto_path=protos --python_out=./pb --pyi_out=./pb protos/hello.proto
26 | ```
27 | - установили Python пакет `grpcio`
28 | - установили Python пакет `grpcio-tools`
29 | - описали сервис `Greeter`
30 | - компилируем командой
31 | ```shell
32 | python -m grpc_tools.protoc --proto_path=protos --python_out=./pb --pyi_out=./pb --grpc_python_out=./pb protos/hello.proto
33 | ```
34 | - правим импорт
35 | - реализуем сервисер
36 | - запускаем сервер
37 | - вызываем клиент
38 |
39 |
40 | Example stream:
41 | ```python
42 | # client
43 | def request_iter():
44 | for idx in range(5):
45 | yield service_pb2.HelloRequest(name=f"Stream #{idx}")
46 |
47 |
48 | stub.GreetStream(request_iter())
49 |
50 | # server
51 | for r in request_iter:
52 | print(f"Stream request {r}")
53 | yield service_pb2.HelloReply(message=f"Hello, {r.name}!")
54 | ```
55 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/address-book.txt:
--------------------------------------------------------------------------------
1 | *John Doejdoe@example.com"
2 |
3 | 555-4321"
4 | 123-4321
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/client_hello.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from concurrent import futures
3 | from random import randint
4 |
5 | import grpc
6 |
7 | from common import configure_logging
8 | from pb import hello_pb2, hello_pb2_grpc
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | def run() -> None:
14 | log.info("Start greeting")
15 | with grpc.insecure_channel('localhost:50051') as channel:
16 | stub = hello_pb2_grpc.GreeterStub(channel)
17 |
18 | hello_request = hello_pb2.HelloRequest(name="Sam")
19 | hello_reply: hello_pb2.HelloReply = (
20 | stub.Greet(hello_request)
21 | )
22 | log.info(
23 | "Reply for %s: %s. Text: %r, number: %s",
24 | hello_request.name,
25 | hello_reply,
26 | hello_reply.hello.text,
27 | hello_reply.hello.number,
28 | )
29 |
30 | log.info("Finished")
31 |
32 |
33 | def main() -> None:
34 | configure_logging()
35 | run()
36 |
37 |
38 | if __name__ == '__main__':
39 | main()
40 |
41 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/common.py:
--------------------------------------------------------------------------------
1 | __all__ = (
2 | "DEFAULT_LOG_FORMAT",
3 | "configure_logging",
4 | )
5 |
6 | import logging
7 |
8 | DEFAULT_LOG_FORMAT = (
9 | # "[%(asctime)s.%(msecs)03d] %(module)10s:%(lineno)-3d %(levelname)-7s - %(message)s"
10 | "%(module)10s:%(lineno)-3d %(levelname)-7s - %(message)s"
11 | )
12 |
13 |
14 | def configure_logging(level: int = logging.INFO) -> None:
15 | logging.basicConfig(
16 | level=level,
17 | datefmt="%Y-%m-%d %H:%M:%S",
18 | format=DEFAULT_LOG_FORMAT,
19 | )
20 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/demo_pb_address_book.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from pb import address_book_pb2
4 |
5 |
6 | ADDRESS_BOOK_FILE = Path(__file__).parent / "address-book.txt"
7 |
8 |
9 | def write() -> None:
10 | person = address_book_pb2.Person()
11 | person.id = 42
12 | person.name = "John Doe"
13 | person.email = "jdoe@example.com"
14 |
15 | phone_home = person.phones.add()
16 | phone_home.number = "555-4321"
17 |
18 | phone_mobile = person.phones.add()
19 | phone_mobile.number = "123-4321"
20 | phone_mobile.type = address_book_pb2.Person.PHONE_TYPE_MOBILE
21 |
22 | print(person)
23 | print(person.SerializeToString())
24 | with ADDRESS_BOOK_FILE.open("wb") as f:
25 | f.write(person.SerializeToString())
26 |
27 |
28 | def read() -> None:
29 | person = address_book_pb2.Person()
30 | print("person is initialized (before):", person.IsInitialized())
31 | with ADDRESS_BOOK_FILE.open("rb") as f:
32 | person.ParseFromString(f.read())
33 | print("person is initialized (after):", person.IsInitialized())
34 | print(person)
35 |
36 |
37 | def main() -> None:
38 | # write()
39 | read()
40 |
41 |
42 | if __name__ == "__main__":
43 | main()
44 |
45 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/demo_pb_hello.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from pb import hello_pb2
4 |
5 |
6 | HELLO_FILE = Path(__file__).parent / "hello.txt"
7 |
8 |
9 | def write() -> None:
10 | # hello = hello_pb2.Hello()
11 | # hello.text = "Hello World!"
12 | hello = hello_pb2.Hello(
13 | text="Hello world!",
14 | )
15 | print(hello)
16 | print(hello.SerializeToString())
17 | with HELLO_FILE.open("wb") as f:
18 | f.write(hello.SerializeToString())
19 |
20 |
21 | def read() -> None:
22 | hello = hello_pb2.Hello()
23 | print("hello is initialized (before):", hello.IsInitialized())
24 | with HELLO_FILE.open("rb") as f:
25 | hello.ParseFromString(f.read())
26 | print("hello is initialized (after):", hello.IsInitialized())
27 | print(hello)
28 |
29 |
30 | def main() -> None:
31 | # write()
32 | read()
33 |
34 |
35 | if __name__ == "__main__":
36 | main()
37 |
38 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/hello.txt:
--------------------------------------------------------------------------------
1 |
2 | Hello world!
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pb/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OtusTeam/BasePython/d87ee93755f4c2dbb4c2b4bbd35743a2c6e44043/open-lessons/pb-and-grpc.07.11.2024/pb/__init__.py
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pb/address_book_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # NO CHECKED-IN PROTOBUF GENCODE
4 | # source: address-book.proto
5 | # Protobuf Python Version: 5.28.3
6 | """Generated protocol buffer code."""
7 | from google.protobuf import descriptor as _descriptor
8 | from google.protobuf import descriptor_pool as _descriptor_pool
9 | from google.protobuf import runtime_version as _runtime_version
10 | from google.protobuf import symbol_database as _symbol_database
11 | from google.protobuf.internal import builder as _builder
12 | _runtime_version.ValidateProtobufRuntimeVersion(
13 | _runtime_version.Domain.PUBLIC,
14 | 5,
15 | 28,
16 | 3,
17 | '',
18 | 'address-book.proto'
19 | )
20 | # @@protoc_insertion_point(imports)
21 |
22 | _sym_db = _symbol_database.Default()
23 |
24 |
25 |
26 |
27 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x61\x64\x64ress-book.proto\x12\x0b\x61\x64\x64ressbook\"\xa9\x02\n\x06Person\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12/\n\x06phones\x18\x04 \x03(\x0b\x32\x1f.addressbook.Person.PhoneNumber\x1a[\n\x0bPhoneNumber\x12\x0e\n\x06number\x18\x01 \x01(\t\x12<\n\x04type\x18\x02 \x01(\x0e\x32\x1d.addressbook.Person.PhoneType:\x0fPHONE_TYPE_HOME\"h\n\tPhoneType\x12\x1a\n\x16PHONE_TYPE_UNSPECIFIED\x10\x00\x12\x15\n\x11PHONE_TYPE_MOBILE\x10\x01\x12\x13\n\x0fPHONE_TYPE_HOME\x10\x02\x12\x13\n\x0fPHONE_TYPE_WORK\x10\x03\"2\n\x0b\x41\x64\x64ressBook\x12#\n\x06people\x18\x01 \x03(\x0b\x32\x13.addressbook.Person')
28 |
29 | _globals = globals()
30 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'address_book_pb2', _globals)
32 | if not _descriptor._USE_C_DESCRIPTORS:
33 | DESCRIPTOR._loaded_options = None
34 | _globals['_PERSON']._serialized_start=36
35 | _globals['_PERSON']._serialized_end=333
36 | _globals['_PERSON_PHONENUMBER']._serialized_start=136
37 | _globals['_PERSON_PHONENUMBER']._serialized_end=227
38 | _globals['_PERSON_PHONETYPE']._serialized_start=229
39 | _globals['_PERSON_PHONETYPE']._serialized_end=333
40 | _globals['_ADDRESSBOOK']._serialized_start=335
41 | _globals['_ADDRESSBOOK']._serialized_end=385
42 | # @@protoc_insertion_point(module_scope)
43 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pb/address_book_pb2.pyi:
--------------------------------------------------------------------------------
1 | from google.protobuf.internal import containers as _containers
2 | from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
3 | from google.protobuf import descriptor as _descriptor
4 | from google.protobuf import message as _message
5 | from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
6 |
7 | DESCRIPTOR: _descriptor.FileDescriptor
8 |
9 | class Person(_message.Message):
10 | __slots__ = ("id", "name", "email", "phones")
11 | class PhoneType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
12 | __slots__ = ()
13 | PHONE_TYPE_UNSPECIFIED: _ClassVar[Person.PhoneType]
14 | PHONE_TYPE_MOBILE: _ClassVar[Person.PhoneType]
15 | PHONE_TYPE_HOME: _ClassVar[Person.PhoneType]
16 | PHONE_TYPE_WORK: _ClassVar[Person.PhoneType]
17 | PHONE_TYPE_UNSPECIFIED: Person.PhoneType
18 | PHONE_TYPE_MOBILE: Person.PhoneType
19 | PHONE_TYPE_HOME: Person.PhoneType
20 | PHONE_TYPE_WORK: Person.PhoneType
21 | class PhoneNumber(_message.Message):
22 | __slots__ = ("number", "type")
23 | NUMBER_FIELD_NUMBER: _ClassVar[int]
24 | TYPE_FIELD_NUMBER: _ClassVar[int]
25 | number: str
26 | type: Person.PhoneType
27 | def __init__(self, number: _Optional[str] = ..., type: _Optional[_Union[Person.PhoneType, str]] = ...) -> None: ...
28 | ID_FIELD_NUMBER: _ClassVar[int]
29 | NAME_FIELD_NUMBER: _ClassVar[int]
30 | EMAIL_FIELD_NUMBER: _ClassVar[int]
31 | PHONES_FIELD_NUMBER: _ClassVar[int]
32 | id: int
33 | name: str
34 | email: str
35 | phones: _containers.RepeatedCompositeFieldContainer[Person.PhoneNumber]
36 | def __init__(self, id: _Optional[int] = ..., name: _Optional[str] = ..., email: _Optional[str] = ..., phones: _Optional[_Iterable[_Union[Person.PhoneNumber, _Mapping]]] = ...) -> None: ...
37 |
38 | class AddressBook(_message.Message):
39 | __slots__ = ("people",)
40 | PEOPLE_FIELD_NUMBER: _ClassVar[int]
41 | people: _containers.RepeatedCompositeFieldContainer[Person]
42 | def __init__(self, people: _Optional[_Iterable[_Union[Person, _Mapping]]] = ...) -> None: ...
43 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pb/hello_pb2.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by the protocol buffer compiler. DO NOT EDIT!
3 | # NO CHECKED-IN PROTOBUF GENCODE
4 | # source: hello.proto
5 | # Protobuf Python Version: 5.27.2
6 | """Generated protocol buffer code."""
7 | from google.protobuf import descriptor as _descriptor
8 | from google.protobuf import descriptor_pool as _descriptor_pool
9 | from google.protobuf import runtime_version as _runtime_version
10 | from google.protobuf import symbol_database as _symbol_database
11 | from google.protobuf.internal import builder as _builder
12 | _runtime_version.ValidateProtobufRuntimeVersion(
13 | _runtime_version.Domain.PUBLIC,
14 | 5,
15 | 27,
16 | 2,
17 | '',
18 | 'hello.proto'
19 | )
20 | # @@protoc_insertion_point(imports)
21 |
22 | _sym_db = _symbol_database.Default()
23 |
24 |
25 |
26 |
27 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bhello.proto\x12\x05hello\"%\n\x05Hello\x12\x0c\n\x04text\x18\x01 \x02(\t\x12\x0e\n\x06number\x18\x02 \x01(\x03\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x02(\t\")\n\nHelloReply\x12\x1b\n\x05hello\x18\x01 \x02(\x0b\x32\x0c.hello.Hello2<\n\x07Greeter\x12\x31\n\x05Greet\x12\x13.hello.HelloRequest\x1a\x11.hello.HelloReply\"\x00')
28 |
29 | _globals = globals()
30 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hello_pb2', _globals)
32 | if not _descriptor._USE_C_DESCRIPTORS:
33 | DESCRIPTOR._loaded_options = None
34 | _globals['_HELLO']._serialized_start=22
35 | _globals['_HELLO']._serialized_end=59
36 | _globals['_HELLOREQUEST']._serialized_start=61
37 | _globals['_HELLOREQUEST']._serialized_end=89
38 | _globals['_HELLOREPLY']._serialized_start=91
39 | _globals['_HELLOREPLY']._serialized_end=132
40 | _globals['_GREETER']._serialized_start=134
41 | _globals['_GREETER']._serialized_end=194
42 | # @@protoc_insertion_point(module_scope)
43 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pb/hello_pb2.pyi:
--------------------------------------------------------------------------------
1 | from google.protobuf import descriptor as _descriptor
2 | from google.protobuf import message as _message
3 | from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
4 |
5 | DESCRIPTOR: _descriptor.FileDescriptor
6 |
7 | class Hello(_message.Message):
8 | __slots__ = ("text", "number")
9 | TEXT_FIELD_NUMBER: _ClassVar[int]
10 | NUMBER_FIELD_NUMBER: _ClassVar[int]
11 | text: str
12 | number: int
13 | def __init__(self, text: _Optional[str] = ..., number: _Optional[int] = ...) -> None: ...
14 |
15 | class HelloRequest(_message.Message):
16 | __slots__ = ("name",)
17 | NAME_FIELD_NUMBER: _ClassVar[int]
18 | name: str
19 | def __init__(self, name: _Optional[str] = ...) -> None: ...
20 |
21 | class HelloReply(_message.Message):
22 | __slots__ = ("hello",)
23 | HELLO_FIELD_NUMBER: _ClassVar[int]
24 | hello: Hello
25 | def __init__(self, hello: _Optional[_Union[Hello, _Mapping]] = ...) -> None: ...
26 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pb/hello_pb2_grpc.py:
--------------------------------------------------------------------------------
1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2 | """Client and server classes corresponding to protobuf-defined services."""
3 | import grpc
4 | import warnings
5 |
6 | from pb import hello_pb2 as hello__pb2
7 |
8 | GRPC_GENERATED_VERSION = '1.67.1'
9 | GRPC_VERSION = grpc.__version__
10 | _version_not_supported = False
11 |
12 | try:
13 | from grpc._utilities import first_version_is_lower
14 | _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
15 | except ImportError:
16 | _version_not_supported = True
17 |
18 | if _version_not_supported:
19 | raise RuntimeError(
20 | f'The grpc package installed is at version {GRPC_VERSION},'
21 | + f' but the generated code in hello_pb2_grpc.py depends on'
22 | + f' grpcio>={GRPC_GENERATED_VERSION}.'
23 | + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
24 | + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
25 | )
26 |
27 |
28 | class GreeterStub(object):
29 | """The greeting service definition.
30 | """
31 |
32 | def __init__(self, channel):
33 | """Constructor.
34 |
35 | Args:
36 | channel: A grpc.Channel.
37 | """
38 | self.Greet = channel.unary_unary(
39 | '/hello.Greeter/Greet',
40 | request_serializer=hello__pb2.HelloRequest.SerializeToString,
41 | response_deserializer=hello__pb2.HelloReply.FromString,
42 | _registered_method=True)
43 |
44 |
45 | class GreeterServicer(object):
46 | """The greeting service definition.
47 | """
48 |
49 | def Greet(self, request, context):
50 | """Missing associated documentation comment in .proto file."""
51 | context.set_code(grpc.StatusCode.UNIMPLEMENTED)
52 | context.set_details('Method not implemented!')
53 | raise NotImplementedError('Method not implemented!')
54 |
55 |
56 | def add_GreeterServicer_to_server(servicer, server):
57 | rpc_method_handlers = {
58 | 'Greet': grpc.unary_unary_rpc_method_handler(
59 | servicer.Greet,
60 | request_deserializer=hello__pb2.HelloRequest.FromString,
61 | response_serializer=hello__pb2.HelloReply.SerializeToString,
62 | ),
63 | }
64 | generic_handler = grpc.method_handlers_generic_handler(
65 | 'hello.Greeter', rpc_method_handlers)
66 | server.add_generic_rpc_handlers((generic_handler,))
67 | server.add_registered_method_handlers('hello.Greeter', rpc_method_handlers)
68 |
69 |
70 | # This class is part of an EXPERIMENTAL API.
71 | class Greeter(object):
72 | """The greeting service definition.
73 | """
74 |
75 | @staticmethod
76 | def Greet(request,
77 | target,
78 | options=(),
79 | channel_credentials=None,
80 | call_credentials=None,
81 | insecure=False,
82 | compression=None,
83 | wait_for_ready=None,
84 | timeout=None,
85 | metadata=None):
86 | return grpc.experimental.unary_unary(
87 | request,
88 | target,
89 | '/hello.Greeter/Greet',
90 | hello__pb2.HelloRequest.SerializeToString,
91 | hello__pb2.HelloReply.FromString,
92 | options,
93 | channel_credentials,
94 | insecure,
95 | call_credentials,
96 | compression,
97 | wait_for_ready,
98 | timeout,
99 | metadata,
100 | _registered_method=True)
101 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/protos/address-book.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | package addressbook;
4 |
5 | message Person {
6 | optional int32 id = 1;
7 | optional string name = 2;
8 | optional string email = 3;
9 |
10 | enum PhoneType {
11 | PHONE_TYPE_UNSPECIFIED = 0;
12 | PHONE_TYPE_MOBILE = 1;
13 | PHONE_TYPE_HOME = 2;
14 | PHONE_TYPE_WORK = 3;
15 | }
16 |
17 | message PhoneNumber {
18 | optional string number = 1;
19 | optional PhoneType type = 2 [default = PHONE_TYPE_HOME];
20 | }
21 |
22 | repeated PhoneNumber phones = 4;
23 | }
24 |
25 | message AddressBook {
26 | repeated Person people = 1;
27 | }
28 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/protos/hello.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto2";
2 |
3 | package hello;
4 |
5 |
6 | // The greeting service definition.
7 | service Greeter {
8 | rpc Greet (HelloRequest) returns (HelloReply) {}
9 | // rpc GreetStream (stream HelloRequest) returns (stream HelloReply) {}
10 | }
11 |
12 |
13 | message Hello {
14 | required string text = 1;
15 | optional int64 number = 2;
16 | }
17 |
18 | message HelloRequest {
19 | required string name = 1;
20 | }
21 |
22 | message HelloReply {
23 | required Hello hello = 1;
24 | }
25 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | package-mode = false
3 |
4 | [tool.poetry.dependencies]
5 | python = "^3.12"
6 | protobuf = "^5.28.3"
7 | grpcio = "^1.67.1"
8 |
9 | [tool.poetry.group.dev.dependencies]
10 | grpcio-tools = "^1.67.1"
11 |
12 |
--------------------------------------------------------------------------------
/open-lessons/pb-and-grpc.07.11.2024/server_hello.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from concurrent import futures
3 | from random import randint
4 |
5 | import grpc
6 |
7 | from common import configure_logging
8 | from pb import hello_pb2, hello_pb2_grpc
9 |
10 | log = logging.getLogger(__name__)
11 |
12 |
13 | class GreeterServicer(hello_pb2_grpc.GreeterServicer):
14 |
15 | def Greet(
16 | self,
17 | request: hello_pb2.HelloRequest,
18 | context: grpc.ServicerContext,
19 | ) -> hello_pb2.HelloReply:
20 | log.info(
21 | "Received hello request for name %r",
22 | request.name,
23 | )
24 | hello = hello_pb2.Hello(
25 | text=f"Hello, {request.name}!",
26 | number=randint(1, 100),
27 | )
28 | reply = hello_pb2.HelloReply(hello=hello)
29 | log.info("Respond with %r", reply)
30 | return reply
31 |
32 |
33 | def serve() -> None:
34 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
35 | hello_pb2_grpc.add_GreeterServicer_to_server(
36 | GreeterServicer(),
37 | server,
38 | )
39 | server.add_insecure_port('[::]:50051')
40 | server.start()
41 | log.warning("Listening on port 50051")
42 | server.wait_for_termination()
43 |
44 |
45 | def main() -> None:
46 | configure_logging()
47 | try:
48 | serve()
49 | except KeyboardInterrupt:
50 | log.warning('Received keyboard interrupt.')
51 |
52 |
53 | if __name__ == '__main__':
54 | main()
55 |
56 |
--------------------------------------------------------------------------------
/open-lessons/pytest.18.11.2020/api.py:
--------------------------------------------------------------------------------
1 | API_URL = "weather.com"
2 |
3 |
4 | class WeatherApi:
5 | def __init__(self, base_url: str):
6 | self.base_url = base_url
7 |
8 | def fetch_weather(self, city: str) -> dict:
9 | print("got in fetch_weather")
10 | import requests
11 | response = requests.get(url=self.base_url, params={"city": city})
12 | return response.json()
13 |
14 |
15 | class Weather:
16 | def __init__(self, api: WeatherApi):
17 | self.api = api
18 |
19 | def get_temperature(self, city: str) -> int:
20 | res: dict = self.api.fetch_weather(city=city)
21 | temperature = res["temperature"]
22 | return temperature
23 |
24 | def get_humidity(self, city: str) -> float:
25 | res: dict = self.api.fetch_weather(city=city)
26 | humidity = res["humidity"]
27 | return humidity
28 |
--------------------------------------------------------------------------------
/open-lessons/pytest.18.11.2020/solver.py:
--------------------------------------------------------------------------------
1 |
2 | class Solver:
3 |
4 | @classmethod
5 | def add(cls, a, b):
6 | res = a + b
7 | print("result a + b", res)
8 | return res
9 |
10 |
11 | def mul(a, b):
12 | return a * b
13 |
14 |
15 | SUB_ERROR_TEXT = "All arguments have to be of type int or float"
16 |
17 |
18 | def sub(a, b):
19 | if not all(
20 | map(
21 | lambda x: isinstance(x, (int, float)),
22 | (a, b),
23 | )
24 | ):
25 | raise TypeError(SUB_ERROR_TEXT)
26 | return a - b
27 |
28 |
29 | if __name__ == '__main__':
30 | res = Solver.add(1, 2)
31 | print("expected 3, got", res)
32 |
--------------------------------------------------------------------------------
/open-lessons/pytest.18.11.2020/test_api.py:
--------------------------------------------------------------------------------
1 | from unittest import mock
2 |
3 | import pytest
4 | from api import API_URL, WeatherApi, Weather
5 |
6 |
7 | def get_prepared_weather_helper():
8 | api = WeatherApi(API_URL)
9 | weather = Weather(api=api)
10 | return weather
11 |
12 |
13 | @pytest.fixture
14 | def mocked_fetch_weather():
15 | with mock.patch.object(WeatherApi, 'fetch_weather', autospec=True) as mocked_fetch_weather:
16 | # print('mocked fetch_weather', WeatherApi.fetch_weather)
17 | yield mocked_fetch_weather
18 | # print('teardown api')
19 | # api.close()
20 | # print('not mocked fetch_weather', api.fetch_weather)
21 | # print('done teardown for api')
22 |
23 |
24 | @pytest.fixture
25 | def weather_api(mocked_fetch_weather):
26 | api = WeatherApi(API_URL)
27 | return api
28 |
29 |
30 | @pytest.fixture
31 | def weather(weather_api):
32 | weather = Weather(api=weather_api)
33 | return weather
34 |
35 |
36 | @pytest.fixture(params=["Moscow", "NYC", "Voronezh"])
37 | def city(request):
38 | return request.param
39 |
40 |
41 | class TestWeatherApi:
42 | def test_fetch_weather(self, weather_api, city):
43 | res = weather_api.fetch_weather(city=city)
44 | assert res is not None
45 |
46 |
47 | class TestWeather:
48 |
49 | def test_get_temperature(self, mocked_fetch_weather, weather, city):
50 | # weather = get_prepared_weather_helper()
51 | temperature = 13
52 | mocked_fetch_weather.return_value = {"temperature": temperature}
53 | res = weather.get_temperature(city)
54 | assert isinstance(res, int)
55 | assert res == temperature
56 | # weather_api.fetch_weather.assert_called()
57 | # weather_api.fetch_weather.assert_called_once()
58 | # call_result = weather_api.fetch_weather.asert_called_once_with(city=city)
59 | call_result = mocked_fetch_weather.assert_called_once_with(weather.api, city=city)
60 | assert call_result is None
61 |
62 | def test_get_humidity(self, mocked_fetch_weather, weather, city):
63 | humidity = 42.5
64 | mocked_fetch_weather.return_value = {"humidity": humidity}
65 | res = weather.get_humidity(city)
66 | assert isinstance(res, float)
67 | assert res == humidity
68 | # weather_api.fetch_weather.assert_called()
69 | # weather_api.fetch_weather.assert_called_once()
70 | mocked_fetch_weather.assert_called_once_with(weather.api, city=city)
71 |
--------------------------------------------------------------------------------
/open-lessons/pytest.18.11.2020/test_solver.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | import pytest
4 |
5 | from solver import Solver, mul, sub, SUB_ERROR_TEXT
6 |
7 |
8 | class TestSolver(TestCase):
9 |
10 | def test_add(self):
11 | res = Solver.add(1, 2)
12 | self.assertEqual(res, 3)
13 |
14 |
15 | @pytest.mark.parametrize('args, expected_result', [
16 | (('spam', 3), 'spamspamspam'),
17 | ((4, 3), 12),
18 | ])
19 | def test_mul(args, expected_result):
20 | res = mul(*args)
21 | assert res == expected_result
22 |
23 |
24 | class TestSub:
25 | def test_sub_raises(self):
26 | with pytest.raises(TypeError) as exc_info:
27 | sub('', 1)
28 |
29 | assert str(exc_info.value) == SUB_ERROR_TEXT
30 |
31 | @pytest.mark.parametrize('args, expected_result', [
32 | pytest.param(
33 | (3, 4), -1,
34 | id="sub_int",
35 | ),
36 | pytest.param(
37 | (5.6, .5), 5.1,
38 | id="sub_float",
39 | ),
40 | ])
41 | def test_sub_ok(self, args, expected_result):
42 | res = sub(*args)
43 | assert res == expected_result
44 |
--------------------------------------------------------------------------------