├── .gitignore ├── 10_lesson ├── 10_les_home_work.txt └── requests_study.py ├── 11_lesson └── 11_les.py ├── 12_lesson └── database.py ├── 13_lesson └── sql_app │ ├── __init__.py │ ├── database.py │ ├── main.py │ ├── models.py │ └── schemas.py ├── 14_lesson └── sql_app │ ├── __init__.py │ ├── crud.py │ ├── database.py │ ├── main.py │ ├── models.py │ └── schemas.py ├── 16_lesson ├── __init__.py ├── auth.py └── create_own_jwt_receiver.py ├── 17_lesson ├── app │ ├── app.py │ ├── db.py │ ├── schemas.py │ └── users.py ├── main.py ├── requirements.txt └── usefull_links.txt ├── 18_lesson ├── app │ ├── app.py │ ├── db.py │ ├── files.py │ ├── schemas.py │ ├── user_models.py │ ├── users.py │ └── utils.py ├── home_work_18_les.txt ├── main.py └── requirements.txt ├── 19_lesson ├── Dockerfile ├── app │ ├── app.py │ ├── db.py │ ├── default_pages.py │ ├── files.py │ ├── schemas.py │ ├── user_models.py │ ├── users.py │ └── utils.py ├── commands.txt ├── database.env ├── docker-compose.yml ├── main.py ├── requirements.txt └── src │ ├── data.json │ └── python.png ├── 1_lesson ├── 1_les_home_work.txt └── hello_world.py ├── 20_lesson ├── Dockerfile ├── __init__.py ├── app │ ├── __init__.py │ ├── app.py │ ├── config.py │ ├── database_utils.py │ ├── db.py │ ├── default_pages.py │ ├── files.py │ ├── schemas.py │ ├── user_models.py │ ├── users.py │ └── utils.py ├── commands.txt ├── database.env ├── docker-compose.yml ├── home_work.txt ├── main.py ├── pyproject.toml ├── requirements.txt ├── src │ ├── data.json │ └── python.png ├── tests │ ├── conftest.py │ └── test_app.py └── uploads │ ├── 4931adcd-7828-48e0-b7e2-4e0db058c71a │ └── python.png │ ├── d6588805-e7a6-4a6f-be80-519bba6cb630 │ └── python.png │ └── e23f9538-de38-4267-bd05-1cc672ca38cb │ └── python.png ├── 21_lesson ├── .DS_Store ├── Dockerfile ├── __init__.py ├── app │ ├── __init__.py │ ├── app.py │ ├── config.py │ ├── database_utils.py │ ├── db.py │ ├── default_pages.py │ ├── files.py │ ├── schemas.py │ ├── user_models.py │ ├── users.py │ └── utils.py ├── commands.txt ├── database.env ├── docker-compose.yml ├── main.py ├── nginx.conf ├── requirements.txt ├── src │ ├── data.json │ └── python.png └── useful_links.txt ├── 2_lesson ├── 2_les_home_work.txt ├── built-in_functions.py ├── combinations.py ├── data_types.py ├── operators.py └── vars.py ├── 3_lesson ├── 3_les_home_work.txt ├── if-elif-else.py └── loops.py ├── 4_lesson ├── 4_les_home_work.txt ├── dict.py ├── list.py └── tuple.py ├── 5_lesson ├── 5_les_home_work.txt ├── functions.py ├── module │ ├── main.py │ └── my_module.py └── turtle_lib_practice.py ├── 6_lesson ├── 6_les_home_work.txt ├── class.py └── class_init.py ├── 7_lesson ├── 7_les_home_work.txt ├── inheritance.py ├── interfaces_example.py └── polymorphism.py ├── 8_lesson ├── 8_les_home_work.txt ├── abstraction.py └── encapsulation.py ├── 9_lesson ├── 9_les_home_work.txt ├── dip.py ├── isp.py ├── lsp.py ├── ocp.py └── srp.py └── README.md /.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 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /10_lesson/10_les_home_work.txt: -------------------------------------------------------------------------------- 1 | Завдання 1: Основи HTTP 2 | 3 | Поясніть, що таке протокол HTTP і для чого він використовується в веб-розробці. Розкажіть про основні методи HTTP, такі як GET, POST, PUT і DELETE. 4 | 5 | Завдання 2: Клієнт та Сервер 6 | 7 | 2.1. Опишіть різницю між клієнтом і сервером у контексті веб-розробки. 8 | 9 | 2.2. Поясніть, як клієнт та сервер взаємодіють за допомогою HTTP-запитів та відповідей. 10 | 11 | Блок бібліотеки requests: 12 | 13 | Завдання 1: Виконання GET-запиту 14 | 15 | Створіть Python-сценарій, який використовує бібліотеку requests для виконання GET-запиту до веб-ресурсу та виведення вмісту веб-сторінки на екран. Використовуйте функцію requests.get() для виконання запиту. 16 | 17 | Завдання 2: Параметри запиту 18 | 19 | Розширте попереднє завдання, додаючи можливість вказати параметри запиту. Виконайте GET-запит до веб-ресурсу, передаючи параметри запиту, такі як параметри запиту у URL або параметри через словник. 20 | 21 | Завдання 3: POST-запит 22 | 23 | Створіть Python-сценарій для виконання POST-запиту до веб-ресурсу. Відправте дані на сервер, наприклад, форму з ім'ям користувача і паролем. 24 | 25 | Завдання 4: Обробка відповіді 26 | 27 | Після виконання запиту, розпарсьте вміст HTTP-відповіді та виведіть потрібну інформацію. Наприклад, виведіть заголовки відповіді або вміст сторінки. 28 | 29 | Завдання 5: Обробка помилок 30 | 31 | Додайте обробку помилок до вашого коду. Обробляйте можливі винятки, такі як requests.exceptions.RequestException, та виводьте відповідні повідомлення про помилку. 32 | 33 | Завдання 6: Збереження вмісту в файл 34 | 35 | Розширте ваш код, щоб зберегти отриманий вміст веб-сторінки у файл. Використайте функціонал Python для роботи з файлами для збереження вмісту. -------------------------------------------------------------------------------- /10_lesson/requests_study.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | windows_activate = ".\venv\Scripts\activate" 5 | linux_macos_activate = "source venv/bin/activate" 6 | 7 | 8 | site = "https://jsonplaceholder.typicode.com/posts/1" 9 | response_get = requests.get(url=site) 10 | 11 | for header, value in response_get.headers.items(): 12 | print(f"Header: {header} --> Value: {value}") 13 | 14 | print(response_get.text) 15 | print("------------") 16 | 17 | body = { 18 | "userId": 12, 19 | "title": "test", 20 | "body": "test" 21 | } 22 | headers = { 23 | 'Content-Type': 'application/json; charset=utf-8' 24 | } 25 | 26 | response_post = requests.post(url=site, json=body, headers=headers) 27 | print(response_post.status_code) 28 | print(response_post.reason) 29 | print(response_post.text) 30 | print("------------") 31 | 32 | data = { 33 | "title": "test_put" 34 | } 35 | response_put = requests.put(url=site, data=data) 36 | 37 | print(response_put.status_code) 38 | print(response_put.reason) 39 | print(response_put.text) 40 | 41 | print("------------") 42 | 43 | response_patch = requests.patch(url=site, data=data) 44 | print(response_patch.status_code) 45 | print(response_patch.reason) 46 | print(response_patch.text) 47 | 48 | print("------------") 49 | 50 | response_delete = requests.delete(url=site) 51 | print(response_delete.status_code) 52 | print(response_delete.reason) 53 | print(response_delete.text) 54 | 55 | -------------------------------------------------------------------------------- /11_lesson/11_les.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from fastapi import FastAPI 4 | 5 | 6 | app = FastAPI() 7 | 8 | 9 | @app.get("/") 10 | async def root(): 11 | return {"message": "Hello World"} 12 | 13 | 14 | class ModelName(str, Enum): 15 | alexnet = "alexnet" 16 | resnet = "resnet" 17 | lenet = "lenet" 18 | 19 | 20 | @app.get("/models/{model_name}") 21 | async def get_model(model_name: ModelName): 22 | if model_name is ModelName.alexnet: 23 | return {"model_name": model_name, "message": "Deep Learning FTW!"} 24 | 25 | if model_name.value == "lenet": 26 | return {"model_name": model_name, "message": "LeCNN all the images"} 27 | 28 | return {"model_name": model_name, "message": "Have some residuals"} 29 | 30 | 31 | @app.get("/items/{item_id}") 32 | async def read_user_item( 33 | item_id: str, needy: str, skip: int = 0, limit: int | None = None 34 | ): 35 | item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} 36 | return item 37 | -------------------------------------------------------------------------------- /12_lesson/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from sqlalchemy import create_engine 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | SQLALCHEMY_DATABASE_URL = os.getenv("POSTGRESQL_URL") 12 | 13 | engine = create_engine(SQLALCHEMY_DATABASE_URL) 14 | 15 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 16 | 17 | Base = declarative_base() 18 | 19 | conn = engine.connect() 20 | print(conn.get_isolation_level()) -------------------------------------------------------------------------------- /13_lesson/sql_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/13_lesson/sql_app/__init__.py -------------------------------------------------------------------------------- /13_lesson/sql_app/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from sqlalchemy import create_engine 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | SQLALCHEMY_DATABASE_URL = os.getenv("POSTGRESQL_URL") 12 | 13 | engine = create_engine(SQLALCHEMY_DATABASE_URL) 14 | 15 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 16 | 17 | Base = declarative_base() 18 | -------------------------------------------------------------------------------- /13_lesson/sql_app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from . import models 4 | from .database import engine 5 | 6 | models.Base.metadata.create_all(bind=engine) 7 | 8 | app = FastAPI() 9 | 10 | 11 | @app.get("/") 12 | async def root(): 13 | return {"message": "Hello World"} 14 | -------------------------------------------------------------------------------- /13_lesson/sql_app/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Boolean, Column, ForeignKey, Integer, String 2 | from sqlalchemy.orm import relationship 3 | 4 | from .database import Base 5 | 6 | 7 | class User(Base): 8 | __tablename__ = "users" 9 | 10 | id = Column(Integer, primary_key=True, index=True) 11 | email = Column(String, unique=True, index=True) 12 | hashed_password = Column(String) 13 | is_active = Column(Boolean, default=True) 14 | 15 | items = relationship("Item", back_populates="owner") 16 | 17 | 18 | class Item(Base): 19 | __tablename__ = "items" 20 | 21 | id = Column(Integer, primary_key=True, index=True) 22 | title = Column(String, index=True) 23 | description = Column(String, index=True) 24 | owner_id = Column(Integer, ForeignKey("users.id")) 25 | 26 | owner = relationship("User", back_populates="items") 27 | -------------------------------------------------------------------------------- /13_lesson/sql_app/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class ItemBase(BaseModel): 5 | title: str 6 | description: str | None = None 7 | 8 | 9 | class ItemCreate(ItemBase): 10 | pass 11 | 12 | 13 | class Item(ItemBase): 14 | id: int 15 | owner_id: int 16 | 17 | class Config: 18 | orm_mode = True 19 | 20 | 21 | class UserBase(BaseModel): 22 | email: str 23 | 24 | 25 | class UserCreate(UserBase): 26 | password: str 27 | 28 | 29 | class User(UserBase): 30 | id: int 31 | is_active: bool 32 | items: list[Item] = [] 33 | 34 | class Config: 35 | orm_mode = True 36 | -------------------------------------------------------------------------------- /14_lesson/sql_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/14_lesson/sql_app/__init__.py -------------------------------------------------------------------------------- /14_lesson/sql_app/crud.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | 3 | from . import models, schemas 4 | 5 | 6 | def get_user(db: Session, user_id: int): 7 | return db.query(models.User).filter(models.User.id == user_id).first() 8 | 9 | 10 | def get_user_by_email(db: Session, email: str): 11 | return db.query(models.User).filter(models.User.email == email).first() 12 | 13 | 14 | def get_users(db: Session, skip: int = 0, limit: int = 100): 15 | return db.query(models.User).offset(skip).limit(limit).all() 16 | 17 | 18 | def create_user(db: Session, user: schemas.UserCreate): 19 | fake_hashed_password = user.password + "notreallyhashed" 20 | db_user = models.User(email=user.email, hashed_password=fake_hashed_password) 21 | db.add(db_user) 22 | db.commit() 23 | db.refresh(db_user) 24 | return db_user 25 | 26 | 27 | def get_items(db: Session, skip: int = 0, limit: int = 100): 28 | return db.query(models.Item).offset(skip).limit(limit).all() 29 | 30 | 31 | def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int): 32 | db_item = models.Item(**item.model_dump(), owner_id=user_id) 33 | db.add(db_item) 34 | db.commit() 35 | db.refresh(db_item) 36 | return db_item 37 | 38 | 39 | def update_user_email(db: Session, user_id: int, update_data: schemas.UserBase): 40 | user = db.query(models.User).filter(models.User.id == user_id).first() 41 | user.email = update_data.email 42 | db.commit() 43 | db.refresh(user) 44 | return user 45 | 46 | 47 | def delete_user(db: Session, user: models.User): 48 | db.delete(user) 49 | db.commit() -------------------------------------------------------------------------------- /14_lesson/sql_app/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from sqlalchemy import create_engine 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | SQLALCHEMY_DATABASE_URL = os.getenv("POSTGRESQL_URL") 12 | 13 | engine = create_engine(SQLALCHEMY_DATABASE_URL) 14 | 15 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 16 | 17 | Base = declarative_base() 18 | -------------------------------------------------------------------------------- /14_lesson/sql_app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI, HTTPException 2 | from sqlalchemy.orm import Session 3 | 4 | from . import crud, models, schemas 5 | from .database import SessionLocal, engine 6 | 7 | models.Base.metadata.create_all(bind=engine) 8 | 9 | app = FastAPI() 10 | 11 | 12 | # Dependency 13 | def get_db(): 14 | db = SessionLocal() 15 | try: 16 | yield db 17 | finally: 18 | db.close() 19 | 20 | 21 | @app.post("/users/", response_model=schemas.User) 22 | def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)): 23 | db_user = crud.get_user_by_email(db, email=user.email) 24 | if db_user: 25 | raise HTTPException(status_code=400, detail="Email already registered") 26 | return crud.create_user(db=db, user=user) 27 | 28 | 29 | @app.get("/users/", response_model=list[schemas.User]) 30 | def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): 31 | users = crud.get_users(db, skip=skip, limit=limit) 32 | return users 33 | 34 | 35 | @app.get("/users/{user_id}", response_model=schemas.User) 36 | def read_user(user_id: int, db: Session = Depends(get_db)): 37 | db_user = crud.get_user(db, user_id=user_id) 38 | if db_user is None: 39 | raise HTTPException(status_code=404, detail="User not found") 40 | return db_user 41 | 42 | 43 | @app.post("/users/{user_id}/items/", response_model=schemas.Item) 44 | def create_item_for_user( 45 | user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db) 46 | ): 47 | return crud.create_user_item(db=db, item=item, user_id=user_id) 48 | 49 | 50 | @app.get("/items/", response_model=list[schemas.Item]) 51 | def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): 52 | items = crud.get_items(db, skip=skip, limit=limit) 53 | return items 54 | 55 | 56 | @app.patch("/update_email/{user_id}", response_model=schemas.UserBase) 57 | def update_user_email(user_id: int, new_email: schemas.UserBase, db: Session = Depends(get_db)): 58 | return crud.update_user_email(db=db, user_id=user_id, update_data=new_email) 59 | 60 | 61 | @app.delete("/delete_user/{user_id}") 62 | def delete_user(user_id: int, db: Session = Depends(get_db)): 63 | user = db.query(models.User).filter(models.User.id == user_id).first() 64 | if user is None: 65 | raise HTTPException(status_code=404, detail="User not found") 66 | crud.delete_user(db=db, user=user) 67 | return "User was deleted succsessfully" 68 | -------------------------------------------------------------------------------- /14_lesson/sql_app/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Boolean, Column, ForeignKey, Integer, String 2 | from sqlalchemy.orm import relationship 3 | 4 | from .database import Base 5 | 6 | 7 | class User(Base): 8 | __tablename__ = "users" 9 | 10 | id = Column(Integer, primary_key=True, index=True) 11 | email = Column(String, unique=True, index=True) 12 | hashed_password = Column(String) 13 | is_active = Column(Boolean, default=True) 14 | 15 | items = relationship("Item", back_populates="owner") 16 | 17 | 18 | class Item(Base): 19 | __tablename__ = "items" 20 | 21 | id = Column(Integer, primary_key=True, index=True) 22 | title = Column(String, index=True) 23 | description = Column(String, index=True) 24 | owner_id = Column(Integer, ForeignKey("users.id")) 25 | 26 | owner = relationship("User", back_populates="items") 27 | -------------------------------------------------------------------------------- /14_lesson/sql_app/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class ItemBase(BaseModel): 5 | title: str 6 | description: str | None = None 7 | 8 | 9 | class ItemCreate(ItemBase): 10 | pass 11 | 12 | 13 | class Item(ItemBase): 14 | id: int 15 | owner_id: int 16 | 17 | class Config: 18 | from_attributes = True 19 | 20 | 21 | class UserBase(BaseModel): 22 | email: str 23 | 24 | 25 | class UserCreate(UserBase): 26 | password: str 27 | 28 | 29 | class User(UserBase): 30 | id: int 31 | is_active: bool 32 | items: list[Item] = [] 33 | 34 | class Config: 35 | from_attributes = True 36 | -------------------------------------------------------------------------------- /16_lesson/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/16_lesson/__init__.py -------------------------------------------------------------------------------- /16_lesson/auth.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from typing import Annotated 3 | 4 | from fastapi import Depends, FastAPI, HTTPException, status 5 | from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm 6 | from jose import JWTError, jwt 7 | from passlib.context import CryptContext 8 | from pydantic import BaseModel 9 | 10 | SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" 11 | ALGORITHM = "HS256" 12 | ACCESS_TOKEN_EXPIRE_MINUTES = 30 13 | 14 | 15 | fake_users_db = { 16 | "johndoe": { 17 | "username": "johndoe", 18 | "full_name": "John Doe", 19 | "email": "johndoe@example.com", 20 | "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", 21 | "disabled": False, 22 | } 23 | } 24 | 25 | 26 | class Token(BaseModel): 27 | access_token: str 28 | token_type: str 29 | 30 | 31 | class TokenData(BaseModel): 32 | username: str | None = None 33 | 34 | 35 | class User(BaseModel): 36 | username: str 37 | email: str | None = None 38 | full_name: str | None = None 39 | disabled: bool | None = None 40 | 41 | 42 | class UserInDB(User): 43 | hashed_password: str 44 | 45 | 46 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 47 | 48 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") 49 | 50 | app = FastAPI() 51 | 52 | 53 | def verify_password(plain_password, hashed_password): 54 | return pwd_context.verify(plain_password, hashed_password) 55 | 56 | 57 | def get_password_hash(password): 58 | return pwd_context.hash(password) 59 | 60 | 61 | def get_user(db, username: str): 62 | if username in db: 63 | user_dict = db[username] 64 | return UserInDB(**user_dict) 65 | 66 | 67 | def authenticate_user(fake_db, username: str, password: str): 68 | user = get_user(fake_db, username) 69 | if not user: 70 | return False 71 | if not verify_password(password, user.hashed_password): 72 | return False 73 | return user 74 | 75 | 76 | def create_access_token(data: dict, expires_delta: timedelta | None = None): 77 | to_encode = data.copy() 78 | if expires_delta: 79 | expire = datetime.utcnow() + expires_delta 80 | else: 81 | expire = datetime.utcnow() + timedelta(minutes=15) 82 | to_encode.update({"exp": expire}) 83 | encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) 84 | return encoded_jwt 85 | 86 | 87 | async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): 88 | credentials_exception = HTTPException( 89 | status_code=status.HTTP_401_UNAUTHORIZED, 90 | detail="Could not validate credentials", 91 | headers={"WWW-Authenticate": "Bearer"}, 92 | ) 93 | try: 94 | payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 95 | username: str = payload.get("sub") 96 | if username is None: 97 | raise credentials_exception 98 | token_data = TokenData(username=username) 99 | except JWTError: 100 | raise credentials_exception 101 | user = get_user(fake_users_db, username=token_data.username) 102 | if user is None: 103 | raise credentials_exception 104 | return user 105 | 106 | 107 | async def get_current_active_user( 108 | current_user: Annotated[User, Depends(get_current_user)] 109 | ): 110 | if current_user.disabled: 111 | raise HTTPException(status_code=400, detail="Inactive user") 112 | return current_user 113 | 114 | 115 | @app.post("/token", response_model=Token) 116 | async def login_for_access_token( 117 | form_data: Annotated[OAuth2PasswordRequestForm, Depends()] 118 | ): 119 | user = authenticate_user(fake_users_db, form_data.username, form_data.password) 120 | if not user: 121 | raise HTTPException( 122 | status_code=status.HTTP_401_UNAUTHORIZED, 123 | detail="Incorrect username or password", 124 | headers={"WWW-Authenticate": "Bearer"}, 125 | ) 126 | access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 127 | access_token = create_access_token( 128 | data={"sub": user.username}, expires_delta=access_token_expires 129 | ) 130 | return {"access_token": access_token, "token_type": "bearer"} 131 | 132 | 133 | @app.get("/users/me/", response_model=User) 134 | async def read_users_me( 135 | current_user: Annotated[User, Depends(get_current_active_user)] 136 | ): 137 | return current_user 138 | 139 | 140 | @app.get("/users/me/items/") 141 | async def read_own_items( 142 | current_user: Annotated[User, Depends(get_current_active_user)] 143 | ): 144 | return [{"item_id": "Foo", "owner": current_user.username}] 145 | -------------------------------------------------------------------------------- /16_lesson/create_own_jwt_receiver.py: -------------------------------------------------------------------------------- 1 | import jwt 2 | 3 | 4 | headers = { 5 | 'alg': 'HS256', 6 | 'type': 'JWT' 7 | } 8 | 9 | payload = { 10 | 'username': 'tester_user', 11 | 'email': 'tester@gmail.com', 12 | 'is_active': False 13 | } 14 | 15 | secret = 'secret_1234' 16 | 17 | encoded_token = jwt.encode(headers=headers, payload=payload, key=secret) 18 | print(encoded_token) 19 | 20 | try: 21 | decoded_token = jwt.decode(encoded_token, secret, algorithms=['HS256']) 22 | print(decoded_token) 23 | except jwt.InvalidTokenError: 24 | print('Invalid token') 25 | except jwt.DecodeError: 26 | print('Decode error') -------------------------------------------------------------------------------- /17_lesson/app/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI 2 | 3 | from app.db import User, create_db_and_tables 4 | from app.schemas import UserCreate, UserRead, UserUpdate 5 | from app.users import auth_backend, current_active_user, fastapi_users 6 | 7 | app = FastAPI() 8 | 9 | app.include_router( 10 | fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] 11 | ) 12 | app.include_router( 13 | fastapi_users.get_register_router(UserRead, UserCreate), 14 | prefix="/auth", 15 | tags=["auth"], 16 | ) 17 | app.include_router( 18 | fastapi_users.get_reset_password_router(), 19 | prefix="/auth", 20 | tags=["auth"], 21 | ) 22 | app.include_router( 23 | fastapi_users.get_verify_router(UserRead), 24 | prefix="/auth", 25 | tags=["auth"], 26 | ) 27 | app.include_router( 28 | fastapi_users.get_users_router(UserRead, UserUpdate), 29 | prefix="/users", 30 | tags=["users"], 31 | ) 32 | 33 | 34 | @app.get("/authenticated-route") 35 | async def authenticated_route(user: User = Depends(current_active_user)): 36 | return {"message": f"Hello {user.email}!"} 37 | 38 | 39 | @app.on_event("startup") 40 | async def on_startup(): 41 | await create_db_and_tables() -------------------------------------------------------------------------------- /17_lesson/app/db.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator 2 | 3 | from fastapi import Depends 4 | from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase 5 | from fastapi_users_db_sqlalchemy.access_token import ( 6 | SQLAlchemyAccessTokenDatabase, 7 | SQLAlchemyBaseAccessTokenTableUUID, 8 | ) 9 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine 10 | from sqlalchemy.orm import DeclarativeBase 11 | 12 | DATABASE_URL = "postgresql+asyncpg://postgres:123321@localhost/fastapi_test" 13 | 14 | 15 | class Base(DeclarativeBase): 16 | pass 17 | 18 | 19 | class User(SQLAlchemyBaseUserTableUUID, Base): 20 | pass 21 | 22 | 23 | class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): 24 | pass 25 | 26 | 27 | engine = create_async_engine(DATABASE_URL) 28 | async_session_maker = async_sessionmaker(engine, expire_on_commit=False) 29 | 30 | 31 | async def create_db_and_tables(): 32 | async with engine.begin() as conn: 33 | await conn.run_sync(Base.metadata.create_all) 34 | 35 | 36 | async def get_async_session() -> AsyncGenerator[AsyncSession, None]: 37 | async with async_session_maker() as session: 38 | yield session 39 | 40 | 41 | async def get_user_db(session: AsyncSession = Depends(get_async_session)): 42 | yield SQLAlchemyUserDatabase(session, User) 43 | 44 | 45 | async def get_access_token_db( 46 | session: AsyncSession = Depends(get_async_session), 47 | ): 48 | yield SQLAlchemyAccessTokenDatabase(session, AccessToken) -------------------------------------------------------------------------------- /17_lesson/app/schemas.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from fastapi_users import schemas 4 | 5 | 6 | class UserRead(schemas.BaseUser[uuid.UUID]): 7 | pass 8 | 9 | 10 | class UserCreate(schemas.BaseUserCreate): 11 | pass 12 | 13 | 14 | class UserUpdate(schemas.BaseUserUpdate): 15 | pass -------------------------------------------------------------------------------- /17_lesson/app/users.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from fastapi import Depends, Request 5 | from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin 6 | from fastapi_users.authentication import ( 7 | AuthenticationBackend, 8 | BearerTransport, 9 | JWTStrategy, 10 | ) 11 | from fastapi_users.db import SQLAlchemyUserDatabase 12 | from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy 13 | 14 | from .db import AccessToken, User, get_user_db, get_access_token_db 15 | 16 | SECRET = "SECRET" 17 | 18 | 19 | class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): 20 | reset_password_token_secret = SECRET 21 | verification_token_secret = SECRET 22 | 23 | async def on_after_register(self, user: User, request: Optional[Request] = None): 24 | print(f"User {user.id} has registered.") 25 | 26 | async def on_after_forgot_password( 27 | self, user: User, token: str, request: Optional[Request] = None 28 | ): 29 | print(f"User {user.id} has forgot their password. Reset token: {token}") 30 | 31 | async def on_after_request_verify( 32 | self, user: User, token: str, request: Optional[Request] = None 33 | ): 34 | print(f"Verification requested for user {user.id}. Verification token: {token}") 35 | 36 | 37 | async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): 38 | yield UserManager(user_db) 39 | 40 | 41 | bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") 42 | 43 | 44 | def get_database_strategy( 45 | access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db), 46 | ) -> DatabaseStrategy: 47 | return DatabaseStrategy(access_token_db, lifetime_seconds=3600) 48 | 49 | 50 | auth_backend = AuthenticationBackend( 51 | name="jwt", 52 | transport=bearer_transport, 53 | get_strategy=get_database_strategy, 54 | ) 55 | 56 | fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) 57 | 58 | current_active_user = fastapi_users.current_user(active=True) -------------------------------------------------------------------------------- /17_lesson/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | if __name__ == "__main__": 4 | uvicorn.run("app.app:app", host="localhost", log_level="info") -------------------------------------------------------------------------------- /17_lesson/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | fastapi-users[sqlalchemy] 3 | uvicorn[standard] 4 | asyncpg -------------------------------------------------------------------------------- /17_lesson/usefull_links.txt: -------------------------------------------------------------------------------- 1 | https://fastapi-users.github.io/fastapi-users/12.1/ 2 | https://www.postman.com/downloads/ 3 | https://iteducenter.medium.com/утиліта-curl-полегшує-життя-49b69f64a930 -------------------------------------------------------------------------------- /18_lesson/app/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI 2 | 3 | from app.db import User, create_db_and_tables 4 | from app.schemas import UserCreate, UserRead, UserUpdate 5 | from app.users import auth_backend, current_active_user, fastapi_users 6 | from app.files import file_router 7 | 8 | app = FastAPI() 9 | 10 | app.include_router( 11 | fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] 12 | ) 13 | app.include_router( 14 | fastapi_users.get_register_router(UserRead, UserCreate), 15 | prefix="/auth", 16 | tags=["auth"], 17 | ) 18 | app.include_router( 19 | fastapi_users.get_reset_password_router(), 20 | prefix="/auth", 21 | tags=["auth"], 22 | ) 23 | app.include_router( 24 | fastapi_users.get_verify_router(UserRead), 25 | prefix="/auth", 26 | tags=["auth"], 27 | ) 28 | app.include_router( 29 | fastapi_users.get_users_router(UserRead, UserUpdate), 30 | prefix="/users", 31 | tags=["users"], 32 | ) 33 | 34 | app.include_router( 35 | file_router, 36 | prefix="/files", 37 | tags=["files"] 38 | ) 39 | 40 | @app.get("/authenticated-route") 41 | async def authenticated_route(user: User = Depends(current_active_user)): 42 | return {"message": f"Hello {user.email}!"} 43 | 44 | 45 | @app.on_event("startup") 46 | async def on_startup(): 47 | await create_db_and_tables() -------------------------------------------------------------------------------- /18_lesson/app/db.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator 2 | 3 | from fastapi import Depends 4 | from fastapi_users.db import SQLAlchemyUserDatabase 5 | from fastapi_users_db_sqlalchemy.access_token import ( 6 | SQLAlchemyAccessTokenDatabase, 7 | ) 8 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine 9 | from app.user_models import Base, User, AccessToken 10 | 11 | DATABASE_URL = "postgresql+asyncpg://postgres:123321@localhost/fastapi_test" 12 | 13 | 14 | engine = create_async_engine(DATABASE_URL) 15 | async_session_maker = async_sessionmaker(engine, expire_on_commit=False) 16 | 17 | 18 | async def create_db_and_tables(): 19 | async with engine.begin() as conn: 20 | await conn.run_sync(Base.metadata.create_all) 21 | 22 | 23 | async def get_async_session() -> AsyncGenerator[AsyncSession, None]: 24 | async with async_session_maker() as session: 25 | yield session 26 | 27 | 28 | async def get_user_db(session: AsyncSession = Depends(get_async_session)): 29 | yield SQLAlchemyUserDatabase(session, User) 30 | 31 | 32 | async def get_access_token_db( 33 | session: AsyncSession = Depends(get_async_session), 34 | ): 35 | yield SQLAlchemyAccessTokenDatabase(session, AccessToken) -------------------------------------------------------------------------------- /18_lesson/app/files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from fastapi import Depends, APIRouter, Form, File, UploadFile, HTTPException 5 | from fastapi.responses import FileResponse 6 | from app.schemas import ImageProcessingOptions 7 | from app.users import current_active_user 8 | from app.db import User 9 | from app.utils import process_image 10 | 11 | 12 | file_router = APIRouter() 13 | UPLOAD_DIR = "uploads" 14 | os.makedirs(UPLOAD_DIR, exist_ok=True) 15 | 16 | 17 | @file_router.post("/upload/") 18 | async def upload_file( 19 | file: UploadFile = File(...), 20 | resize: str = Form(None), 21 | convert_to: str = Form(None), 22 | grayscale: bool = Form(None), 23 | flip: str = Form(None), 24 | user: User = Depends(current_active_user) 25 | ): 26 | options = ImageProcessingOptions( 27 | resize=resize, 28 | convert_to=convert_to, 29 | grayscale=grayscale, 30 | flip=flip 31 | ) 32 | 33 | user_folder = f"{UPLOAD_DIR}/{user.id}" 34 | os.makedirs(user_folder, exist_ok=True) 35 | 36 | file_location = os.path.join(user_folder, file.filename) 37 | 38 | with open(file_location, "wb") as buffers: 39 | shutil.copyfileobj(file.file, buffers) 40 | 41 | processed_file_location = process_image(file_location, options) 42 | return {"filename": os.path.basename(processed_file_location), "location": processed_file_location} 43 | 44 | 45 | @file_router.get("/download/{filename}", response_class=FileResponse) 46 | async def download_file(filename: str, user: User = Depends(current_active_user)): 47 | user_folder = f"{UPLOAD_DIR}/{user.id}" 48 | file_location = os.path.join(user_folder, filename) 49 | if not os.path.exists(file_location): 50 | raise HTTPException(status_code=404, detail="File not found") 51 | return FileResponse(file_location) -------------------------------------------------------------------------------- /18_lesson/app/schemas.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from fastapi_users import schemas 4 | from pydantic import BaseModel, Field 5 | from typing import Optional 6 | 7 | class UserRead(schemas.BaseUser[uuid.UUID]): 8 | pass 9 | 10 | 11 | class UserCreate(schemas.BaseUserCreate): 12 | pass 13 | 14 | 15 | class UserUpdate(schemas.BaseUserUpdate): 16 | pass 17 | 18 | 19 | class ImageProcessingOptions(BaseModel): 20 | resize: Optional[str] = Field(None, description="Example: 1980x1200") 21 | convert_to: Optional[str] = Field(None, description="png, jpg, webp") 22 | grayscale: Optional[bool] = Field(None, description="Convert to grayscale") 23 | flip: Optional[str] = Field(None, description="horizontal or vertical") -------------------------------------------------------------------------------- /18_lesson/app/user_models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import DeclarativeBase 2 | from fastapi_users.db import SQLAlchemyBaseUserTableUUID 3 | from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyBaseAccessTokenTableUUID 4 | 5 | 6 | class Base(DeclarativeBase): 7 | pass 8 | 9 | 10 | class User(SQLAlchemyBaseUserTableUUID, Base): 11 | pass 12 | 13 | 14 | class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): 15 | pass -------------------------------------------------------------------------------- /18_lesson/app/users.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from fastapi import Depends, Request 5 | from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin 6 | from fastapi_users.authentication import ( 7 | AuthenticationBackend, 8 | BearerTransport, 9 | JWTStrategy, 10 | ) 11 | from fastapi_users.db import SQLAlchemyUserDatabase 12 | from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy 13 | 14 | from app.db import get_user_db, get_access_token_db 15 | from app.user_models import Base, User, AccessToken 16 | 17 | SECRET = "SECRET" 18 | 19 | 20 | class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): 21 | reset_password_token_secret = SECRET 22 | verification_token_secret = SECRET 23 | 24 | async def on_after_register(self, user: User, request: Optional[Request] = None): 25 | print(f"User {user.id} has registered.") 26 | 27 | async def on_after_forgot_password( 28 | self, user: User, token: str, request: Optional[Request] = None 29 | ): 30 | print(f"User {user.id} has forgot their password. Reset token: {token}") 31 | 32 | async def on_after_request_verify( 33 | self, user: User, token: str, request: Optional[Request] = None 34 | ): 35 | print(f"Verification requested for user {user.id}. Verification token: {token}") 36 | 37 | 38 | async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): 39 | yield UserManager(user_db) 40 | 41 | 42 | bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") 43 | 44 | 45 | def get_database_strategy( 46 | access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db), 47 | ) -> DatabaseStrategy: 48 | return DatabaseStrategy(access_token_db, lifetime_seconds=3600) 49 | 50 | 51 | auth_backend = AuthenticationBackend( 52 | name="jwt", 53 | transport=bearer_transport, 54 | get_strategy=get_database_strategy, 55 | ) 56 | 57 | fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) 58 | 59 | current_active_user = fastapi_users.current_user(active=True) -------------------------------------------------------------------------------- /18_lesson/app/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from PIL import Image, ImageOps 5 | from app.schemas import ImageProcessingOptions 6 | 7 | 8 | def process_image(file_location: str, options: ImageProcessingOptions): 9 | with Image.open(file_location) as img: 10 | if options.resize: 11 | match = re.match(r'(\d+)x(\d+)', options.resize) 12 | if match: 13 | width, height = map(int, match.groups()) 14 | img = img.resize((width, height)) 15 | 16 | if options.grayscale: 17 | img = ImageOps.grayscale(img) 18 | 19 | if options.flip: 20 | if options.flip == 'horizontal': 21 | img = ImageOps.mirror(img) 22 | elif options.flip == 'vertical': 23 | img = ImageOps.flip(img) 24 | 25 | if options.convert_to: 26 | file_location = f"{os.path.splitext(file_location)[0]}.{options.convert_to}" 27 | img.save(file_location, options.convert_to.upper()) 28 | else: 29 | img.save(file_location) 30 | 31 | return file_location 32 | -------------------------------------------------------------------------------- /18_lesson/home_work_18_les.txt: -------------------------------------------------------------------------------- 1 | 1) Зробити конвертор для csv у xlsx та навпаки 2 | 2) Зробити конвертор для docx або doc у pdf та навпаки 3 | 4 | Якщо стикаєтесь з труднощами, можете пошукати готові рішення на GitHub бо опенсоурс проєкти сильно допомагають як при навчанні так і при роботі -------------------------------------------------------------------------------- /18_lesson/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | if __name__ == "__main__": 4 | uvicorn.run("app.app:app", host="localhost", log_level="info") -------------------------------------------------------------------------------- /18_lesson/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | fastapi-users[sqlalchemy] 3 | uvicorn[standard] 4 | asyncpg 5 | pillow -------------------------------------------------------------------------------- /19_lesson/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.1-slim 2 | 3 | EXPOSE 8080 4 | 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | WORKDIR /fastapi 9 | 10 | COPY ./requirements.txt /fastapi/requirements.txt 11 | 12 | RUN pip install --no-cache-dir --upgrade -r /fastapi/requirements.txt 13 | 14 | COPY . /fastapi 15 | 16 | CMD ["python3", "main.py"] 17 | -------------------------------------------------------------------------------- /19_lesson/app/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI 2 | 3 | from app.db import User, create_db_and_tables 4 | from app.schemas import UserCreate, UserRead, UserUpdate 5 | from app.users import auth_backend, current_active_user, fastapi_users 6 | from app.files import file_router 7 | from app.default_pages import default_router 8 | 9 | app = FastAPI() 10 | 11 | app.include_router( 12 | fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] 13 | ) 14 | app.include_router( 15 | fastapi_users.get_register_router(UserRead, UserCreate), 16 | prefix="/auth", 17 | tags=["auth"], 18 | ) 19 | app.include_router( 20 | fastapi_users.get_reset_password_router(), 21 | prefix="/auth", 22 | tags=["auth"], 23 | ) 24 | app.include_router( 25 | fastapi_users.get_verify_router(UserRead), 26 | prefix="/auth", 27 | tags=["auth"], 28 | ) 29 | app.include_router( 30 | fastapi_users.get_users_router(UserRead, UserUpdate), 31 | prefix="/users", 32 | tags=["users"], 33 | ) 34 | 35 | app.include_router( 36 | file_router, 37 | prefix="/files", 38 | tags=["files"] 39 | ) 40 | 41 | app.include_router( 42 | default_router, 43 | tags=["default_pages"] 44 | ) 45 | 46 | @app.get("/authenticated-route") 47 | async def authenticated_route(user: User = Depends(current_active_user)): 48 | return {"message": f"Hello {user.email}!"} 49 | 50 | 51 | @app.on_event("startup") 52 | async def on_startup(): 53 | # Not needed if you setup a migration system like Alembic 54 | await create_db_and_tables() -------------------------------------------------------------------------------- /19_lesson/app/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from typing import AsyncGenerator 4 | 5 | from fastapi import Depends 6 | from fastapi_users.db import SQLAlchemyBaseUserTableUUID, SQLAlchemyUserDatabase 7 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine 8 | from fastapi_users_db_sqlalchemy.access_token import ( 9 | SQLAlchemyAccessTokenDatabase, 10 | SQLAlchemyBaseAccessTokenTableUUID, 11 | ) 12 | from sqlalchemy.orm import DeclarativeBase 13 | from app.user_models import Base, User, AccessToken 14 | 15 | from dotenv import load_dotenv 16 | 17 | current_script_path = os.path.dirname(os.path.abspath(__file__)) 18 | dotenv_path = os.path.join(current_script_path, '..', 'database.env') 19 | 20 | load_dotenv(dotenv_path) 21 | 22 | HOST_PG = os.getenv("POSTGRES_HOST") 23 | PORT_PG = os.getenv("POSTGRES_PORT") 24 | DATABASE_PG = os.getenv("POSTGRES_DB") 25 | USER_PG = os.getenv("POSTGRES_USER") 26 | PASSWORD_PG = os.getenv("POSTGRES_PASSWORD") 27 | 28 | DATABASE_URL = f"postgresql+asyncpg://{USER_PG}:{PASSWORD_PG}@{HOST_PG}:{PORT_PG}/{DATABASE_PG}" 29 | 30 | engine = create_async_engine(DATABASE_URL) 31 | 32 | async_session_maker = async_sessionmaker(engine, expire_on_commit=False) 33 | 34 | 35 | async def create_db_and_tables(): 36 | async with engine.begin() as conn: 37 | await conn.run_sync(Base.metadata.create_all) 38 | 39 | 40 | async def get_async_session() -> AsyncGenerator[AsyncSession, None]: 41 | async with async_session_maker() as session: 42 | yield session 43 | 44 | 45 | async def get_user_db(session: AsyncSession = Depends(get_async_session)): 46 | yield SQLAlchemyUserDatabase(session, User) 47 | 48 | 49 | async def get_access_token_db( 50 | session: AsyncSession = Depends(get_async_session), 51 | ): 52 | yield SQLAlchemyAccessTokenDatabase(session, AccessToken) 53 | -------------------------------------------------------------------------------- /19_lesson/app/default_pages.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from fastapi.responses import FileResponse 3 | from pathlib import Path 4 | import json 5 | 6 | 7 | default_router = APIRouter() 8 | 9 | 10 | current_file_path = Path(__file__) 11 | parent_directory = current_file_path.parent.parent 12 | 13 | json_file_path = parent_directory / "src" / "data.json" 14 | 15 | 16 | @default_router.get("/") 17 | def read_root(): 18 | with open(json_file_path, 'r') as file: 19 | data = json.load(file) 20 | 21 | return data 22 | # return FileResponse("src/python.png") 23 | 24 | 25 | -------------------------------------------------------------------------------- /19_lesson/app/files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from fastapi import Depends, APIRouter, Form, File, UploadFile, HTTPException 5 | from fastapi.responses import FileResponse 6 | from app.schemas import ImageProcessingOptions 7 | from app.users import current_active_user 8 | from app.db import User 9 | from app.utils import process_image 10 | 11 | 12 | file_router = APIRouter() 13 | UPLOAD_DIR = "uploads" 14 | os.makedirs(UPLOAD_DIR, exist_ok=True) 15 | 16 | 17 | @file_router.post("/upload/") 18 | async def upload_file( 19 | file: UploadFile = File(...), 20 | resize: str = Form(None), 21 | convert_to: str = Form(None), 22 | grayscale: bool = Form(None), 23 | flip: str = Form(None), 24 | user: User = Depends(current_active_user) 25 | ): 26 | options = ImageProcessingOptions( 27 | resize=resize, 28 | convert_to=convert_to, 29 | grayscale=grayscale, 30 | flip=flip 31 | ) 32 | 33 | user_folder = f"{UPLOAD_DIR}/{user.id}" 34 | os.makedirs(user_folder, exist_ok=True) 35 | 36 | file_location = os.path.join(user_folder, file.filename) 37 | 38 | with open(file_location, "wb") as buffers: 39 | shutil.copyfileobj(file.file, buffers) 40 | 41 | processed_file_location = process_image(file_location, options) 42 | return {"filename": os.path.basename(processed_file_location), "location": processed_file_location} 43 | 44 | 45 | @file_router.get("/download/{filename}", response_class=FileResponse) 46 | async def download_file(filename: str, user: User = Depends(current_active_user)): 47 | user_folder = f"{UPLOAD_DIR}/{user.id}" 48 | file_location = os.path.join(user_folder, filename) 49 | if not os.path.exists(file_location): 50 | raise HTTPException(status_code=404, detail="File not found") 51 | return FileResponse(file_location) -------------------------------------------------------------------------------- /19_lesson/app/schemas.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from fastapi_users import schemas 4 | from pydantic import BaseModel, Field 5 | from typing import Optional 6 | 7 | class UserRead(schemas.BaseUser[uuid.UUID]): 8 | pass 9 | 10 | 11 | class UserCreate(schemas.BaseUserCreate): 12 | pass 13 | 14 | 15 | class UserUpdate(schemas.BaseUserUpdate): 16 | pass 17 | 18 | 19 | class ImageProcessingOptions(BaseModel): 20 | resize: Optional[str] = Field(None, description="Example: 1980x1200") 21 | convert_to: Optional[str] = Field(None, description="png, jpg, webp") 22 | grayscale: Optional[bool] = Field(None, description="Convert to grayscale") 23 | flip: Optional[str] = Field(None, description="horizontal or vertical") 24 | -------------------------------------------------------------------------------- /19_lesson/app/user_models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import DeclarativeBase 2 | from fastapi_users.db import SQLAlchemyBaseUserTableUUID 3 | from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyBaseAccessTokenTableUUID 4 | 5 | 6 | class Base(DeclarativeBase): 7 | pass 8 | 9 | 10 | class User(SQLAlchemyBaseUserTableUUID, Base): 11 | pass 12 | 13 | 14 | class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): 15 | pass -------------------------------------------------------------------------------- /19_lesson/app/users.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from fastapi import Depends, Request 5 | from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin 6 | from fastapi_users.authentication import ( 7 | AuthenticationBackend, 8 | BearerTransport, 9 | ) 10 | from fastapi_users.db import SQLAlchemyUserDatabase 11 | from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy 12 | 13 | from app.db import get_user_db, get_access_token_db 14 | from app.user_models import User, AccessToken 15 | 16 | 17 | SECRET = "SECRET" 18 | 19 | 20 | class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): 21 | reset_password_token_secret = SECRET 22 | verification_token_secret = SECRET 23 | 24 | async def on_after_register(self, user: User, request: Optional[Request] = None): 25 | print(f"User {user.id} has registered.") 26 | 27 | async def on_after_forgot_password( 28 | self, user: User, token: str, request: Optional[Request] = None 29 | ): 30 | print(f"User {user.id} has forgot their password. Reset token: {token}") 31 | 32 | async def on_after_request_verify( 33 | self, user: User, token: str, request: Optional[Request] = None 34 | ): 35 | print(f"Verification requested for user {user.id}. Verification token: {token}") 36 | 37 | 38 | async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): 39 | yield UserManager(user_db) 40 | 41 | 42 | bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") 43 | 44 | 45 | def get_database_strategy( 46 | access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db), 47 | ) -> DatabaseStrategy: 48 | return DatabaseStrategy(access_token_db, lifetime_seconds=3600) 49 | 50 | 51 | auth_backend = AuthenticationBackend( 52 | name="jwt", 53 | transport=bearer_transport, 54 | get_strategy=get_database_strategy, 55 | ) 56 | 57 | fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) 58 | 59 | current_active_user = fastapi_users.current_user(active=True) -------------------------------------------------------------------------------- /19_lesson/app/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from PIL import Image, ImageOps 5 | from app.schemas import ImageProcessingOptions 6 | 7 | 8 | def process_image(file_location: str, options: ImageProcessingOptions): 9 | with Image.open(file_location) as img: 10 | if options.resize: 11 | match = re.match(r'(\d+)x(\d+)', options.resize) 12 | if match: 13 | width, height = map(int, match.groups()) 14 | img = img.resize((width, height)) 15 | 16 | if options.grayscale: 17 | img = ImageOps.grayscale(img) 18 | 19 | if options.flip: 20 | if options.flip == 'horizontal': 21 | img = ImageOps.mirror(img) 22 | elif options.flip == 'vertical': 23 | img = ImageOps.flip(img) 24 | 25 | if options.convert_to: 26 | file_location = f"{os.path.splitext(file_location)[0]}.{options.convert_to}" 27 | img.save(file_location, options.convert_to.upper()) 28 | else: 29 | img.save(file_location) 30 | 31 | return file_location 32 | -------------------------------------------------------------------------------- /19_lesson/commands.txt: -------------------------------------------------------------------------------- 1 | docker image ls 2 | docker rm image_name or id / docker rmi $(docker images -a -q) 3 | docker pull postgres 4 | docker run -itd -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=123321 -p 5432:5432 -v /Users/dmytro.buzoveria/Desktop/pg_data:/var/lib/postgresql/data --name postgresql postgres 5 | docker exec -it postgresql bash 6 | psql -h localhost -U postgres 7 | 8 | docker inspect postgresql -------------------------------------------------------------------------------- /19_lesson/database.env: -------------------------------------------------------------------------------- 1 | POSTGRES_HOST=db 2 | POSTGRES_PORT=5432 3 | POSTGRES_DB=fastapi_test 4 | POSTGRES_USER=postgres 5 | POSTGRES_PASSWORD=123321 -------------------------------------------------------------------------------- /19_lesson/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | version: '3.8' 3 | 4 | services: 5 | web: 6 | build: . 7 | volumes: 8 | - .:/fastapi 9 | ports: 10 | - "8080:8000" 11 | depends_on: 12 | - database 13 | 14 | database: 15 | image: postgres 16 | container_name: db 17 | volumes: 18 | - postgres_data:/var/lib/postgresql/data/ 19 | env_file: 20 | - database_prod.env 21 | ports: 22 | - "5432:5432" 23 | 24 | volumes: 25 | postgres_data: -------------------------------------------------------------------------------- /19_lesson/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | if __name__ == "__main__": 4 | uvicorn.run("app.app:app", host="0.0.0.0", log_level="info", reload=True) -------------------------------------------------------------------------------- /19_lesson/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | fastapi-users[sqlalchemy] 3 | uvicorn[standard] 4 | asyncpg 5 | Pillow 6 | python-dotenv -------------------------------------------------------------------------------- /19_lesson/src/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "say_hello": "text test", 3 | "name": "dima" 4 | } -------------------------------------------------------------------------------- /19_lesson/src/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/19_lesson/src/python.png -------------------------------------------------------------------------------- /1_lesson/1_les_home_work.txt: -------------------------------------------------------------------------------- 1 | 1. Встановити Python на свій компʼютер 2 | 2. Встановити середовище розробки: Pycharm або VSCode 3 | 3. Створити або використати готовий файл з розширенням .py 4 | 4. Написати свою першу програму print('Hello world!') 5 | 6 | Посилання: 7 | Встановлення на Windows - https://docs.python.org/uk/3.12/using/windows.html 8 | Встановлення на macOS - https://docs.python.org/uk/3.12/using/mac.html 9 | Корисний путівник - https://pythonguide.rozh2sch.org.ua -------------------------------------------------------------------------------- /1_lesson/hello_world.py: -------------------------------------------------------------------------------- 1 | print("Hello world!") -------------------------------------------------------------------------------- /20_lesson/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.1-slim 2 | 3 | EXPOSE 8080 4 | 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | WORKDIR /fastapi 9 | 10 | COPY ./requirements.txt /fastapi/requirements.txt 11 | 12 | RUN pip install --no-cache-dir --upgrade -r /fastapi/requirements.txt 13 | 14 | COPY . /fastapi 15 | 16 | CMD ["python3", "main.py"] 17 | -------------------------------------------------------------------------------- /20_lesson/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/20_lesson/__init__.py -------------------------------------------------------------------------------- /20_lesson/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/20_lesson/app/__init__.py -------------------------------------------------------------------------------- /20_lesson/app/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI 2 | 3 | from app.db import create_db_and_tables, drop_all_tables 4 | from app.user_models import User 5 | from app.schemas import UserCreate, UserRead, UserUpdate 6 | from app.users import auth_backend, current_active_user, fastapi_users 7 | from app.files import file_router 8 | from app.default_pages import default_router 9 | 10 | app = FastAPI() 11 | 12 | app.include_router( 13 | fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] 14 | ) 15 | app.include_router( 16 | fastapi_users.get_register_router(UserRead, UserCreate), 17 | prefix="/auth", 18 | tags=["auth"], 19 | ) 20 | app.include_router( 21 | fastapi_users.get_reset_password_router(), 22 | prefix="/auth", 23 | tags=["auth"], 24 | ) 25 | app.include_router( 26 | fastapi_users.get_verify_router(UserRead), 27 | prefix="/auth", 28 | tags=["auth"], 29 | ) 30 | app.include_router( 31 | fastapi_users.get_users_router(UserRead, UserUpdate), 32 | prefix="/users", 33 | tags=["users"], 34 | ) 35 | 36 | app.include_router( 37 | file_router, 38 | prefix="/files", 39 | tags=["files"] 40 | ) 41 | 42 | app.include_router( 43 | default_router, 44 | tags=["default_pages"] 45 | ) 46 | 47 | @app.get("/authenticated-route") 48 | async def authenticated_route(user: User = Depends(current_active_user)): 49 | return {"message": f"Hello {user.email}!"} 50 | 51 | 52 | @app.on_event("startup") 53 | async def on_startup(): 54 | await create_db_and_tables() 55 | 56 | 57 | @app.on_event("shutdown") 58 | async def on_shutdown(): 59 | await drop_all_tables() 60 | -------------------------------------------------------------------------------- /20_lesson/app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from pydantic_settings import BaseSettings 5 | 6 | current_script_path = os.path.dirname(os.path.abspath(__file__)) 7 | dotenv_path = os.path.join(current_script_path, '..', 'database.env') 8 | 9 | load_dotenv(dotenv_path) 10 | 11 | 12 | class TestSettings(): 13 | DB_HOST_TEST: str = os.environ.get("DB_HOST_TEST") 14 | DB_PORT_TEST: str = os.environ.get("DB_PORT_TEST") 15 | DB_NAME_TEST: str = os.environ.get("DB_NAME_TEST") 16 | DB_USER_TEST: str = os.environ.get("DB_USER_TEST") 17 | DB_PASS_TEST: str = os.environ.get("DB_PASS_TEST") 18 | DATABASE_URL: str = f"postgresql+asyncpg://{DB_USER_TEST}:{DB_PASS_TEST}@{DB_HOST_TEST}:{DB_PORT_TEST}/{DB_NAME_TEST}" 19 | 20 | 21 | class ProdSettings(): 22 | DB_HOST: str = os.environ.get("DB_HOST") 23 | DB_PORT: str = os.environ.get("DB_PORT") 24 | DB_NAME: str = os.environ.get("DB_NAME") 25 | DB_USER: str = os.environ.get("DB_USER") 26 | DB_PASS: str = os.environ.get("DB_PASS") 27 | DATABASE_URL: str = f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}" 28 | 29 | def get_settings(): 30 | env = os.getenv("ENVIRONMENT", "Not database").lower() 31 | if env == "testing": 32 | return TestSettings() 33 | elif env == "production": 34 | return ProdSettings() 35 | else: 36 | raise ValueError(f"Unknown environment: {env}") 37 | 38 | 39 | settings = get_settings() 40 | -------------------------------------------------------------------------------- /20_lesson/app/database_utils.py: -------------------------------------------------------------------------------- 1 | from app.user_models import User, AccessToken 2 | from sqlalchemy.ext.asyncio import AsyncSession 3 | from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyAccessTokenDatabase 4 | from fastapi_users.db import SQLAlchemyUserDatabase 5 | from fastapi import Depends 6 | from app.db import get_async_session 7 | 8 | 9 | async def get_user_db(session: AsyncSession = Depends(get_async_session)): 10 | yield SQLAlchemyUserDatabase(session, User) 11 | 12 | async def get_access_token_db(session: AsyncSession = Depends(get_async_session)): 13 | yield SQLAlchemyAccessTokenDatabase(session, AccessToken) -------------------------------------------------------------------------------- /20_lesson/app/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import AsyncGenerator 3 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine 4 | from sqlalchemy.orm import DeclarativeBase 5 | from app.config import settings 6 | 7 | from sqlalchemy.ext.declarative import declarative_base 8 | from sqlalchemy.orm import sessionmaker 9 | from sqlalchemy.pool import NullPool 10 | from app.user_models import Base 11 | 12 | 13 | DATABASE_URL = settings.DATABASE_URL 14 | 15 | 16 | engine = create_async_engine(DATABASE_URL, poolclass=NullPool) 17 | async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) 18 | 19 | 20 | async def create_db_and_tables(): 21 | async with engine.begin() as conn: 22 | await conn.run_sync(Base.metadata.create_all) 23 | 24 | 25 | async def get_async_session() -> AsyncGenerator[AsyncSession, None]: 26 | async with async_session_maker() as session: 27 | yield session 28 | 29 | 30 | async def drop_all_tables(): 31 | async with engine.begin() as conn: 32 | await conn.run_sync(Base.metadata.drop_all) 33 | -------------------------------------------------------------------------------- /20_lesson/app/default_pages.py: -------------------------------------------------------------------------------- 1 | import json 2 | import aiofiles 3 | 4 | from fastapi import APIRouter 5 | from fastapi.responses import FileResponse 6 | from pathlib import Path 7 | 8 | 9 | default_router = APIRouter() 10 | 11 | 12 | current_file_path = Path(__file__) 13 | parent_directory = current_file_path.parent.parent 14 | 15 | json_file_path = parent_directory / "src" / "data.json" 16 | 17 | 18 | @default_router.get("/") 19 | async def read_root(): 20 | async with aiofiles.open(json_file_path, mode='r') as file: 21 | contents = await file.read() 22 | 23 | message = json.loads(contents)["message"] 24 | return message 25 | 26 | 27 | -------------------------------------------------------------------------------- /20_lesson/app/files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from fastapi import Depends, APIRouter, Form, File, UploadFile, HTTPException 5 | from fastapi.responses import FileResponse 6 | from app.schemas import ImageProcessingOptions 7 | from app.users import current_active_user 8 | from app.user_models import User 9 | from app.utils import process_image 10 | 11 | 12 | file_router = APIRouter() 13 | UPLOAD_DIR = "uploads" 14 | os.makedirs(UPLOAD_DIR, exist_ok=True) 15 | 16 | 17 | @file_router.post("/upload/") 18 | async def upload_file( 19 | file: UploadFile = File(...), 20 | resize: str = Form(None), 21 | convert_to: str = Form(None), 22 | grayscale: bool = Form(None), 23 | flip: str = Form(None), 24 | user: User = Depends(current_active_user) 25 | ): 26 | options = ImageProcessingOptions( 27 | resize=resize, 28 | convert_to=convert_to, 29 | grayscale=grayscale, 30 | flip=flip 31 | ) 32 | 33 | user_folder = f"{UPLOAD_DIR}/{user.id}" 34 | os.makedirs(user_folder, exist_ok=True) 35 | 36 | file_location = os.path.join(user_folder, file.filename) 37 | 38 | with open(file_location, "wb") as buffers: 39 | shutil.copyfileobj(file.file, buffers) 40 | 41 | processed_file_location = process_image(file_location, options) 42 | return {"filename": os.path.basename(processed_file_location), "location": processed_file_location} 43 | 44 | 45 | @file_router.get("/download/{filename}", response_class=FileResponse) 46 | async def download_file(filename: str, user: User = Depends(current_active_user)): 47 | user_folder = f"{UPLOAD_DIR}/{user.id}" 48 | file_location = os.path.join(user_folder, filename) 49 | if not os.path.exists(file_location): 50 | raise HTTPException(status_code=404, detail="File not found") 51 | return FileResponse(file_location) -------------------------------------------------------------------------------- /20_lesson/app/schemas.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from fastapi_users import schemas 4 | from pydantic import BaseModel, Field 5 | from typing import Optional 6 | 7 | 8 | class UserRead(schemas.BaseUser[uuid.UUID]): 9 | pass 10 | 11 | 12 | class UserCreate(schemas.BaseUserCreate): 13 | pass 14 | 15 | 16 | class UserUpdate(schemas.BaseUserUpdate): 17 | pass 18 | 19 | 20 | class ImageProcessingOptions(BaseModel): 21 | resize: Optional[str] = Field(None, description="Example: 1980x1200") 22 | convert_to: Optional[str] = Field(None, description="png, jpg, webp") 23 | grayscale: Optional[bool] = Field(None, description="Convert to grayscale") 24 | flip: Optional[str] = Field(None, description="horizontal or vertical") 25 | -------------------------------------------------------------------------------- /20_lesson/app/user_models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import DeclarativeBase 2 | from fastapi_users.db import SQLAlchemyBaseUserTableUUID 3 | from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyBaseAccessTokenTableUUID 4 | from sqlalchemy.orm import DeclarativeBase 5 | 6 | 7 | class Base(DeclarativeBase): 8 | pass 9 | 10 | 11 | class User(SQLAlchemyBaseUserTableUUID, Base): 12 | pass 13 | 14 | 15 | class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): 16 | pass 17 | -------------------------------------------------------------------------------- /20_lesson/app/users.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from fastapi import Depends, Request 5 | from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin 6 | from fastapi_users.authentication import ( 7 | AuthenticationBackend, 8 | BearerTransport, 9 | ) 10 | from fastapi_users.db import SQLAlchemyUserDatabase 11 | from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy 12 | 13 | from app.database_utils import get_user_db, get_access_token_db 14 | from app.user_models import User, AccessToken 15 | 16 | 17 | SECRET = "SECRET" 18 | 19 | 20 | class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): 21 | reset_password_token_secret = SECRET 22 | verification_token_secret = SECRET 23 | 24 | async def on_after_register(self, user: User, request: Optional[Request] = None): 25 | print(f"User {user.id} has registered.") 26 | 27 | async def on_after_forgot_password( 28 | self, user: User, token: str, request: Optional[Request] = None 29 | ): 30 | print(f"User {user.id} has forgot their password. Reset token: {token}") 31 | 32 | async def on_after_request_verify( 33 | self, user: User, token: str, request: Optional[Request] = None 34 | ): 35 | print(f"Verification requested for user {user.id}. Verification token: {token}") 36 | 37 | 38 | async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): 39 | yield UserManager(user_db) 40 | 41 | 42 | bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") 43 | 44 | 45 | def get_database_strategy( 46 | access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db), 47 | ) -> DatabaseStrategy: 48 | return DatabaseStrategy(access_token_db, lifetime_seconds=3600) 49 | 50 | 51 | auth_backend = AuthenticationBackend( 52 | name="jwt", 53 | transport=bearer_transport, 54 | get_strategy=get_database_strategy, 55 | ) 56 | 57 | fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) 58 | 59 | current_active_user = fastapi_users.current_user(active=True) -------------------------------------------------------------------------------- /20_lesson/app/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from PIL import Image, ImageOps 5 | from app.schemas import ImageProcessingOptions 6 | 7 | 8 | def process_image(file_location: str, options: ImageProcessingOptions): 9 | with Image.open(file_location) as img: 10 | if options.resize: 11 | match = re.match(r'(\d+)x(\d+)', options.resize) 12 | if match: 13 | width, height = map(int, match.groups()) 14 | img = img.resize((width, height)) 15 | 16 | if options.grayscale: 17 | img = ImageOps.grayscale(img) 18 | 19 | if options.flip: 20 | if options.flip == 'horizontal': 21 | img = ImageOps.mirror(img) 22 | elif options.flip == 'vertical': 23 | img = ImageOps.flip(img) 24 | 25 | if options.convert_to: 26 | file_location = f"{os.path.splitext(file_location)[0]}.{options.convert_to}" 27 | img.save(file_location, options.convert_to.upper()) 28 | else: 29 | img.save(file_location) 30 | 31 | return file_location 32 | -------------------------------------------------------------------------------- /20_lesson/commands.txt: -------------------------------------------------------------------------------- 1 | docker image ls 2 | docker rm image_name or id / docker rmi $(docker images -a -q) 3 | docker pull postgres 4 | docker run -itd -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=123321 -p 5432:5432 -v /Users/dmytro.buzoveria/Desktop/pg_data:/var/lib/postgresql/data --name postgresql postgres 5 | docker exec -it postgresql bash 6 | psql -h localhost -U postgres 7 | 8 | docker inspect postgresql -------------------------------------------------------------------------------- /20_lesson/database.env: -------------------------------------------------------------------------------- 1 | DB_HOST=localhost 2 | DB_PORT=5432 3 | DB_NAME=fastapi_test 4 | DB_USER=postgres 5 | DB_PASS=123321 6 | 7 | DB_HOST_TEST=localhost 8 | DB_PORT_TEST=5432 9 | DB_NAME_TEST=test_db 10 | DB_USER_TEST=postgres 11 | DB_PASS_TEST=123321 -------------------------------------------------------------------------------- /20_lesson/docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | version: '3.8' 3 | 4 | services: 5 | web: 6 | build: . 7 | volumes: 8 | - .:/fastapi 9 | ports: 10 | - "8080:8000" 11 | depends_on: 12 | - database 13 | 14 | database: 15 | image: postgres 16 | container_name: db 17 | volumes: 18 | - postgres_data:/var/lib/postgresql/data/ 19 | env_file: 20 | - database.env 21 | ports: 22 | - "5432:5432" 23 | 24 | volumes: 25 | postgres_data: -------------------------------------------------------------------------------- /20_lesson/home_work.txt: -------------------------------------------------------------------------------- 1 | Написали тести, які перевіряють випадки поганого сценарію, як було розглянуто в кінці уроку, за основу для тест кейсів брати документацію бібліотеки fastaapi users 2 | 3 | Корисні посилання 4 | https://fastapi-users.github.io/fastapi-users/12.1/usage/routes/ 5 | https://docs.pytest.org/en/7.1.x/getting-started.html -------------------------------------------------------------------------------- /20_lesson/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | if __name__ == "__main__": 4 | uvicorn.run("app.app:app", host="localhost", log_level="info", reload=True) -------------------------------------------------------------------------------- /20_lesson/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pytest.ini_options] 2 | pythonpath = [ 3 | ".", "app", 4 | ] 5 | asyncio_mode="auto" -------------------------------------------------------------------------------- /20_lesson/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | fastapi-users[sqlalchemy] 3 | uvicorn[standard] 4 | asyncpg 5 | Pillow 6 | python-dotenv 7 | httpx 8 | pytest 9 | pydantic-settings 10 | aiofiles 11 | pytest-asyncio -------------------------------------------------------------------------------- /20_lesson/src/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Hello World" 3 | } -------------------------------------------------------------------------------- /20_lesson/src/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/20_lesson/src/python.png -------------------------------------------------------------------------------- /20_lesson/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession 3 | from sqlalchemy.orm import sessionmaker 4 | from app.db import engine, get_async_session 5 | from app.app import app 6 | from app.user_models import Base 7 | from app.config import settings 8 | from httpx import AsyncClient 9 | import asyncio 10 | from typing import AsyncGenerator 11 | from sqlalchemy.pool import NullPool 12 | 13 | from sqlalchemy import select 14 | from app.user_models import User 15 | 16 | 17 | DATABASE_URL = settings.DATABASE_URL 18 | engine_test = create_async_engine(DATABASE_URL, poolclass=NullPool, echo=False) 19 | async_session_maker = sessionmaker(engine_test, class_=AsyncSession, expire_on_commit=False) 20 | 21 | 22 | async def override_get_async_session() -> AsyncGenerator[AsyncSession, None]: 23 | async with async_session_maker() as session: 24 | yield session 25 | 26 | app.dependency_overrides[get_async_session] = override_get_async_session 27 | 28 | @pytest.fixture(autouse=True, scope='session') 29 | async def prepare_database(): 30 | async with engine_test.begin() as conn: 31 | await conn.run_sync(Base.metadata.create_all) 32 | yield 33 | async with engine_test.begin() as conn: 34 | await conn.run_sync(Base.metadata.drop_all) 35 | 36 | 37 | @pytest.fixture(scope="module") 38 | def event_loop(): 39 | loop = asyncio.get_event_loop_policy().new_event_loop() 40 | yield loop 41 | loop.close() 42 | 43 | 44 | @pytest.fixture(scope="session") 45 | async def async_client(): 46 | async with AsyncClient(app=app, base_url="http://test") as ac: 47 | yield ac 48 | 49 | 50 | @pytest.fixture(scope="session") 51 | def token_storage(): 52 | return {} 53 | 54 | 55 | @pytest.fixture(scope="session") 56 | def user_id_storage(): 57 | return {} 58 | 59 | 60 | @pytest.fixture(scope="session") 61 | async def get_token(token_storage): 62 | token = token_storage.get("token") 63 | assert token is not None 64 | yield token 65 | 66 | 67 | @pytest.fixture(scope="session") 68 | async def get_user_id(user_id_storage): 69 | user_id = user_id_storage.get("id") 70 | assert user_id is not None 71 | yield user_id 72 | 73 | 74 | @pytest.fixture 75 | async def get_user_by_field(): 76 | async def _get_user_by_field(field, value): 77 | async with async_session_maker() as session: 78 | filter_condition = getattr(User, field) == value 79 | result = await session.execute(select(User).filter(filter_condition)) 80 | return result.scalar_one_or_none() 81 | return _get_user_by_field -------------------------------------------------------------------------------- /20_lesson/tests/test_app.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pytest 3 | 4 | from fastapi import status 5 | from conftest import async_session_maker 6 | 7 | from sqlalchemy import select 8 | from app.user_models import User 9 | 10 | 11 | async def test_user_registration_success(async_client, get_user_by_field): 12 | payload = { 13 | "email": "test@example.com", 14 | "password": "password123", 15 | } 16 | response = await async_client.post("/auth/register", json=payload) 17 | assert response.status_code == status.HTTP_201_CREATED, f"Registration failed: {response.text}" 18 | 19 | user = await get_user_by_field("email", payload["email"]) 20 | assert user, "User not found" 21 | 22 | 23 | async def test_user_registration_wrong_email(async_client): 24 | payload = { 25 | "email": "test", 26 | "password": "password123", 27 | } 28 | response = await async_client.post("/auth/register", json=payload) 29 | assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY, f"Registration failed: {response.text}" 30 | 31 | 32 | async def test_user_registration_wrong_password(async_client): 33 | payload = { 34 | "email": "test@example.com", 35 | "password": "p", 36 | } 37 | response = await async_client.post("/auth/register", json=payload) 38 | assert response.status_code == status.HTTP_400_BAD_REQUEST, f"Registration failed: {response.text}" 39 | 40 | 41 | async def test_login(async_client, token_storage): 42 | login_response = await async_client.post( 43 | "/auth/jwt/login", 44 | data={"username": "test@example.com", "password": "password123"} 45 | ) 46 | assert login_response.status_code == status.HTTP_200_OK, "Login failed: " + login_response.text 47 | token = login_response.json().get("access_token") 48 | assert token is not None 49 | token_storage["token"] = token 50 | 51 | 52 | async def test_get_user(async_client, get_token): 53 | headers = {"Authorization": f"Bearer {get_token}", "Content-Type": "application/json"} 54 | get_user_response = await async_client.get("users/me", headers=headers) 55 | assert get_user_response.status_code == status.HTTP_200_OK, "Get user failed: " + get_user_response.text 56 | 57 | 58 | async def test_change_user_data(async_client, get_token, get_user_by_field): 59 | email = "test@example.com" 60 | new_password = "newpass123" 61 | headers = {"Authorization": f"Bearer {get_token}", "Content-Type": "application/json"} 62 | change_user_response = await async_client.patch("users/me", headers=headers, json={"password": new_password}) 63 | 64 | assert change_user_response.status_code == status.HTTP_200_OK, "Change user failed: " + change_user_response.text 65 | 66 | async with async_session_maker() as session: 67 | async with session.begin(): 68 | result = await session.execute(select(User).filter_by(email=email)) 69 | user = result.scalar_one_or_none() 70 | assert user, "User not found" 71 | 72 | user.is_superuser = True 73 | 74 | await session.commit() 75 | 76 | user = await get_user_by_field("email", email) 77 | assert user.is_superuser, "User was not set as superuser" 78 | 79 | 80 | async def test_registration_another_user(async_client, user_id_storage, get_user_by_field): 81 | registration_response = await async_client.post("/auth/register", json={ 82 | "email": "fastapi@example.com", 83 | "password": "password123", 84 | }) 85 | assert registration_response.status_code == status.HTTP_201_CREATED, "Registration failed: " + registration_response.text 86 | user_id = registration_response.json().get("id") 87 | 88 | assert user_id is not None 89 | user_id_storage["id"] = user_id 90 | 91 | user = await get_user_by_field("email", "fastapi@example.com") 92 | assert user, "Another user was not created in the database" 93 | 94 | 95 | async def test_get_another_user_data(async_client, get_token, get_user_id): 96 | headers = {"Authorization": f"Bearer {get_token}", "Content-Type": "application/json"} 97 | get_user_response = await async_client.get(f"users/{get_user_id}", headers=headers) 98 | assert get_user_response.status_code == status.HTTP_200_OK, "Get user failed: " + get_user_response.text 99 | 100 | 101 | async def test_change_another_user_data(async_client, get_token, get_user_id, get_user_by_field): 102 | headers = {"Authorization": f"Bearer {get_token}", "Content-Type": "application/json"} 103 | change_user_response = await async_client.patch(f"users/{get_user_id}", headers=headers, json={"is_superuser": "true"}) 104 | 105 | assert change_user_response.status_code == status.HTTP_200_OK, "Change user failed: " + change_user_response.text 106 | 107 | user = await get_user_by_field("id", get_user_id) 108 | assert user.is_superuser, "User was not set as superuser" 109 | 110 | 111 | async def test_delete_another_user_data(async_client, get_token, get_user_id, get_user_by_field): 112 | headers = {"Authorization": f"Bearer {get_token}", "Content-Type": "application/json"} 113 | delete_user_response = await async_client.delete(f"users/{get_user_id}", headers=headers) 114 | 115 | assert delete_user_response.status_code == status.HTTP_204_NO_CONTENT, "Delete user failed: " + delete_user_response.text 116 | user = await get_user_by_field("id", get_user_id) 117 | 118 | assert not user, "User was not deleted from the database" 119 | 120 | 121 | async def test_upload_file(async_client, get_token): 122 | with open('src/python.png', 'rb') as f: 123 | files = {'file': ('python.png', f, 'image/png')} 124 | data = {'resize': '500x500'} 125 | 126 | headers = {"Authorization": f"Bearer {get_token}"} 127 | image_upload_response = await async_client.post("/files/upload/", files=files, data=data, headers=headers) 128 | 129 | assert image_upload_response.status_code == status.HTTP_200_OK, "Upload file failed: " + image_upload_response.text 130 | 131 | 132 | async def test_logout(async_client, get_token): 133 | headers = {"Authorization": f"Bearer {get_token}", "Content-Type": "application/json"} 134 | logout_response = await async_client.post("/auth/jwt/logout", headers=headers) 135 | assert logout_response.status_code == status.HTTP_204_NO_CONTENT, "Logout failed: " + logout_response.text 136 | -------------------------------------------------------------------------------- /20_lesson/uploads/4931adcd-7828-48e0-b7e2-4e0db058c71a/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/20_lesson/uploads/4931adcd-7828-48e0-b7e2-4e0db058c71a/python.png -------------------------------------------------------------------------------- /20_lesson/uploads/d6588805-e7a6-4a6f-be80-519bba6cb630/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/20_lesson/uploads/d6588805-e7a6-4a6f-be80-519bba6cb630/python.png -------------------------------------------------------------------------------- /20_lesson/uploads/e23f9538-de38-4267-bd05-1cc672ca38cb/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/20_lesson/uploads/e23f9538-de38-4267-bd05-1cc672ca38cb/python.png -------------------------------------------------------------------------------- /21_lesson/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/21_lesson/.DS_Store -------------------------------------------------------------------------------- /21_lesson/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.1-slim 2 | 3 | ENV PYTHONDONTWRITEBYTECODE 1 4 | ENV PYTHONUNBUFFERED 1 5 | 6 | WORKDIR /fastapi 7 | 8 | COPY ./requirements.txt /fastapi/requirements.txt 9 | 10 | RUN pip install --no-cache-dir --upgrade -r /fastapi/requirements.txt 11 | 12 | COPY . /fastapi 13 | -------------------------------------------------------------------------------- /21_lesson/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/21_lesson/__init__.py -------------------------------------------------------------------------------- /21_lesson/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/21_lesson/app/__init__.py -------------------------------------------------------------------------------- /21_lesson/app/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, FastAPI 2 | 3 | from .db import create_db_and_tables, drop_all_tables 4 | from .user_models import User 5 | from .schemas import UserCreate, UserRead, UserUpdate 6 | from .users import auth_backend, current_active_user, fastapi_users 7 | from .files import file_router 8 | from .default_pages import default_router 9 | 10 | app = FastAPI() 11 | 12 | app.include_router( 13 | fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"] 14 | ) 15 | app.include_router( 16 | fastapi_users.get_register_router(UserRead, UserCreate), 17 | prefix="/auth", 18 | tags=["auth"], 19 | ) 20 | app.include_router( 21 | fastapi_users.get_reset_password_router(), 22 | prefix="/auth", 23 | tags=["auth"], 24 | ) 25 | app.include_router( 26 | fastapi_users.get_verify_router(UserRead), 27 | prefix="/auth", 28 | tags=["auth"], 29 | ) 30 | app.include_router( 31 | fastapi_users.get_users_router(UserRead, UserUpdate), 32 | prefix="/users", 33 | tags=["users"], 34 | ) 35 | 36 | app.include_router( 37 | file_router, 38 | prefix="/files", 39 | tags=["files"] 40 | ) 41 | 42 | app.include_router( 43 | default_router, 44 | tags=["default_pages"] 45 | ) 46 | 47 | @app.get("/authenticated-route") 48 | async def authenticated_route(user: User = Depends(current_active_user)): 49 | return {"message": f"Hello {user.email}!"} 50 | 51 | 52 | @app.on_event("startup") 53 | async def on_startup(): 54 | await create_db_and_tables() 55 | 56 | 57 | @app.on_event("shutdown") 58 | async def on_shutdown(): 59 | await drop_all_tables() 60 | -------------------------------------------------------------------------------- /21_lesson/app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from pydantic_settings import BaseSettings 5 | 6 | current_script_path = os.path.dirname(os.path.abspath(__file__)) 7 | dotenv_path = os.path.join(current_script_path, '..', 'database.env') 8 | 9 | load_dotenv(dotenv_path) 10 | 11 | 12 | class TestSettings(): 13 | DB_HOST_TEST: str = os.environ.get("DB_HOST_TEST") 14 | DB_PORT_TEST: str = os.environ.get("DB_PORT_TEST") 15 | DB_NAME_TEST: str = os.environ.get("DB_NAME_TEST") 16 | DB_USER_TEST: str = os.environ.get("DB_USER_TEST") 17 | DB_PASS_TEST: str = os.environ.get("DB_PASS_TEST") 18 | DATABASE_URL: str = f"postgresql+asyncpg://{DB_USER_TEST}:{DB_PASS_TEST}@{DB_HOST_TEST}:{DB_PORT_TEST}/{DB_NAME_TEST}" 19 | 20 | 21 | class ProdSettings(): 22 | DB_HOST: str = os.environ.get("POSTGRES_HOST") 23 | DB_PORT: str = os.environ.get("POSTGRES_PORT") 24 | DB_NAME: str = os.environ.get("POSTGRES_DB") 25 | DB_USER: str = os.environ.get("POSTGRES_USER") 26 | DB_PASS: str = os.environ.get("POSTGRES_PASSWORD") 27 | DATABASE_URL: str = f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}" 28 | 29 | def get_settings(): 30 | env = os.getenv("ENVIRONMENT", "Not database").lower() 31 | if env == "testing": 32 | return TestSettings() 33 | elif env == "production": 34 | return ProdSettings() 35 | else: 36 | raise ValueError(f"Unknown environment: {env}") 37 | 38 | 39 | settings = get_settings() 40 | -------------------------------------------------------------------------------- /21_lesson/app/database_utils.py: -------------------------------------------------------------------------------- 1 | from .user_models import User, AccessToken 2 | from sqlalchemy.ext.asyncio import AsyncSession 3 | from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyAccessTokenDatabase 4 | from fastapi_users.db import SQLAlchemyUserDatabase 5 | from fastapi import Depends 6 | from .db import get_async_session 7 | 8 | 9 | async def get_user_db(session: AsyncSession = Depends(get_async_session)): 10 | yield SQLAlchemyUserDatabase(session, User) 11 | 12 | async def get_access_token_db(session: AsyncSession = Depends(get_async_session)): 13 | yield SQLAlchemyAccessTokenDatabase(session, AccessToken) -------------------------------------------------------------------------------- /21_lesson/app/db.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import AsyncGenerator 3 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine 4 | from sqlalchemy.orm import DeclarativeBase 5 | from .config import settings 6 | 7 | from sqlalchemy.ext.declarative import declarative_base 8 | from sqlalchemy.orm import sessionmaker 9 | from sqlalchemy.pool import NullPool 10 | from .user_models import Base 11 | 12 | 13 | DATABASE_URL = settings.DATABASE_URL 14 | 15 | 16 | engine = create_async_engine(DATABASE_URL, poolclass=NullPool) 17 | async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) 18 | 19 | 20 | async def create_db_and_tables(): 21 | async with engine.begin() as conn: 22 | await conn.run_sync(Base.metadata.create_all) 23 | 24 | 25 | async def get_async_session() -> AsyncGenerator[AsyncSession, None]: 26 | async with async_session_maker() as session: 27 | yield session 28 | 29 | 30 | async def drop_all_tables(): 31 | async with engine.begin() as conn: 32 | await conn.run_sync(Base.metadata.drop_all) 33 | -------------------------------------------------------------------------------- /21_lesson/app/default_pages.py: -------------------------------------------------------------------------------- 1 | import json 2 | import aiofiles 3 | 4 | from fastapi import APIRouter 5 | from fastapi.responses import FileResponse 6 | from pathlib import Path 7 | 8 | 9 | default_router = APIRouter() 10 | 11 | 12 | current_file_path = Path(__file__) 13 | parent_directory = current_file_path.parent.parent 14 | 15 | json_file_path = parent_directory / "src" / "data.json" 16 | 17 | 18 | @default_router.get("/") 19 | async def read_root(): 20 | async with aiofiles.open(json_file_path, mode='r') as file: 21 | contents = await file.read() 22 | 23 | message = json.loads(contents)["message"] 24 | return message 25 | 26 | 27 | -------------------------------------------------------------------------------- /21_lesson/app/files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from fastapi import Depends, APIRouter, Form, File, UploadFile, HTTPException 5 | from fastapi.responses import FileResponse 6 | from .schemas import ImageProcessingOptions 7 | from .users import current_active_user 8 | from .user_models import User 9 | from .utils import process_image 10 | 11 | 12 | file_router = APIRouter() 13 | UPLOAD_DIR = "uploads" 14 | os.makedirs(UPLOAD_DIR, exist_ok=True) 15 | 16 | 17 | @file_router.post("/upload/") 18 | async def upload_file( 19 | file: UploadFile = File(...), 20 | resize: str = Form(None), 21 | convert_to: str = Form(None), 22 | grayscale: bool = Form(None), 23 | flip: str = Form(None), 24 | user: User = Depends(current_active_user) 25 | ): 26 | options = ImageProcessingOptions( 27 | resize=resize, 28 | convert_to=convert_to, 29 | grayscale=grayscale, 30 | flip=flip 31 | ) 32 | 33 | user_folder = f"{UPLOAD_DIR}/{user.id}" 34 | os.makedirs(user_folder, exist_ok=True) 35 | 36 | file_location = os.path.join(user_folder, file.filename) 37 | 38 | with open(file_location, "wb") as buffers: 39 | shutil.copyfileobj(file.file, buffers) 40 | 41 | processed_file_location = process_image(file_location, options) 42 | return {"filename": os.path.basename(processed_file_location), "location": processed_file_location} 43 | 44 | 45 | @file_router.get("/download/{filename}", response_class=FileResponse) 46 | async def download_file(filename: str, user: User = Depends(current_active_user)): 47 | user_folder = f"{UPLOAD_DIR}/{user.id}" 48 | file_location = os.path.join(user_folder, filename) 49 | if not os.path.exists(file_location): 50 | raise HTTPException(status_code=404, detail="File not found") 51 | return FileResponse(file_location) -------------------------------------------------------------------------------- /21_lesson/app/schemas.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from fastapi_users import schemas 4 | from pydantic import BaseModel, Field 5 | from typing import Optional 6 | 7 | 8 | class UserRead(schemas.BaseUser[uuid.UUID]): 9 | pass 10 | 11 | 12 | class UserCreate(schemas.BaseUserCreate): 13 | pass 14 | 15 | 16 | class UserUpdate(schemas.BaseUserUpdate): 17 | pass 18 | 19 | 20 | class ImageProcessingOptions(BaseModel): 21 | resize: Optional[str] = Field(None, description="Example: 1980x1200") 22 | convert_to: Optional[str] = Field(None, description="png, jpg, webp") 23 | grayscale: Optional[bool] = Field(None, description="Convert to grayscale") 24 | flip: Optional[str] = Field(None, description="horizontal or vertical") 25 | -------------------------------------------------------------------------------- /21_lesson/app/user_models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import DeclarativeBase 2 | from fastapi_users.db import SQLAlchemyBaseUserTableUUID 3 | from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyBaseAccessTokenTableUUID 4 | from sqlalchemy.orm import DeclarativeBase 5 | 6 | 7 | class Base(DeclarativeBase): 8 | pass 9 | 10 | 11 | class User(SQLAlchemyBaseUserTableUUID, Base): 12 | pass 13 | 14 | 15 | class AccessToken(SQLAlchemyBaseAccessTokenTableUUID, Base): 16 | pass 17 | -------------------------------------------------------------------------------- /21_lesson/app/users.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from typing import Optional 3 | 4 | from fastapi import Depends, Request 5 | from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin 6 | from fastapi_users.authentication import ( 7 | AuthenticationBackend, 8 | BearerTransport, 9 | ) 10 | from fastapi_users.db import SQLAlchemyUserDatabase 11 | from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy 12 | 13 | from .database_utils import get_user_db, get_access_token_db 14 | from .user_models import User, AccessToken 15 | 16 | 17 | SECRET = "SECRET" 18 | 19 | 20 | class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): 21 | reset_password_token_secret = SECRET 22 | verification_token_secret = SECRET 23 | 24 | async def on_after_register(self, user: User, request: Optional[Request] = None): 25 | print(f"User {user.id} has registered.") 26 | 27 | async def on_after_forgot_password( 28 | self, user: User, token: str, request: Optional[Request] = None 29 | ): 30 | print(f"User {user.id} has forgot their password. Reset token: {token}") 31 | 32 | async def on_after_request_verify( 33 | self, user: User, token: str, request: Optional[Request] = None 34 | ): 35 | print(f"Verification requested for user {user.id}. Verification token: {token}") 36 | 37 | 38 | async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)): 39 | yield UserManager(user_db) 40 | 41 | 42 | bearer_transport = BearerTransport(tokenUrl="auth/jwt/login") 43 | 44 | 45 | def get_database_strategy( 46 | access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db), 47 | ) -> DatabaseStrategy: 48 | return DatabaseStrategy(access_token_db, lifetime_seconds=3600) 49 | 50 | 51 | auth_backend = AuthenticationBackend( 52 | name="jwt", 53 | transport=bearer_transport, 54 | get_strategy=get_database_strategy, 55 | ) 56 | 57 | fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend]) 58 | 59 | current_active_user = fastapi_users.current_user(active=True) -------------------------------------------------------------------------------- /21_lesson/app/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from PIL import Image, ImageOps 5 | from .schemas import ImageProcessingOptions 6 | 7 | 8 | def process_image(file_location: str, options: ImageProcessingOptions): 9 | with Image.open(file_location) as img: 10 | if options.resize: 11 | match = re.match(r'(\d+)x(\d+)', options.resize) 12 | if match: 13 | width, height = map(int, match.groups()) 14 | img = img.resize((width, height)) 15 | 16 | if options.grayscale: 17 | img = ImageOps.grayscale(img) 18 | 19 | if options.flip: 20 | if options.flip == 'horizontal': 21 | img = ImageOps.mirror(img) 22 | elif options.flip == 'vertical': 23 | img = ImageOps.flip(img) 24 | 25 | if options.convert_to: 26 | file_location = f"{os.path.splitext(file_location)[0]}.{options.convert_to}" 27 | img.save(file_location, options.convert_to.upper()) 28 | else: 29 | img.save(file_location) 30 | 31 | return file_location 32 | -------------------------------------------------------------------------------- /21_lesson/commands.txt: -------------------------------------------------------------------------------- 1 | docker image ls 2 | docker rm image_name or id / docker rmi $(docker images -a -q) 3 | docker pull postgres 4 | docker run -itd -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=123321 -p 5432:5432 -v /Users/dmytro.buzoveria/Desktop/pg_data:/var/lib/postgresql/data --name postgresql postgres 5 | docker exec -it postgresql bash 6 | psql -h localhost -U postgres 7 | 8 | docker inspect postgresql -------------------------------------------------------------------------------- /21_lesson/database.env: -------------------------------------------------------------------------------- 1 | POSTGRES_HOST=db 2 | POSTGRES_PORT=5432 3 | POSTGRES_DB=fastapi_test 4 | POSTGRES_USER=postgres 5 | POSTGRES_PASSWORD=123321 -------------------------------------------------------------------------------- /21_lesson/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | container_name: app 6 | image: app 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | command: python main.py 11 | volumes: 12 | - .:/fastapi 13 | environment: 14 | ENVIRONMENT: "production" 15 | networks: 16 | - custom 17 | depends_on: 18 | - database 19 | 20 | database: 21 | image: postgres 22 | container_name: db 23 | volumes: 24 | - postgres_data:/var/lib/postgresql/data/ 25 | env_file: 26 | - database.env 27 | ports: 28 | - "5432:5432" 29 | networks: 30 | - custom 31 | 32 | nginx: 33 | image: nginx:alpine 34 | container_name: nginx 35 | ports: 36 | - "8080:80" 37 | volumes: 38 | - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro 39 | depends_on: 40 | - app 41 | networks: 42 | - custom 43 | 44 | networks: 45 | custom: 46 | driver: bridge 47 | 48 | volumes: 49 | postgres_data: 50 | -------------------------------------------------------------------------------- /21_lesson/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | 3 | if __name__ == "__main__": 4 | uvicorn.run("app.app:app", host="0.0.0.0", log_level="info", reload=True) -------------------------------------------------------------------------------- /21_lesson/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location / { 5 | proxy_pass http://app:8000; 6 | proxy_set_header Host $host; 7 | proxy_set_header X-Real-IP $remote_addr; 8 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 9 | proxy_set_header X-Forwarded-Proto $scheme; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /21_lesson/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | fastapi-users[sqlalchemy] 3 | uvicorn[standard] 4 | asyncpg 5 | Pillow 6 | python-dotenv 7 | httpx 8 | pytest 9 | pydantic-settings 10 | aiofiles 11 | pytest-asyncio -------------------------------------------------------------------------------- /21_lesson/src/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Hello World" 3 | } -------------------------------------------------------------------------------- /21_lesson/src/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genius-space/python-core/69fb19fe2a8d3caf5377ba0ed1918301016e8a9b/21_lesson/src/python.png -------------------------------------------------------------------------------- /21_lesson/useful_links.txt: -------------------------------------------------------------------------------- 1 | Сайт для тестової оренди VPS - https://vps.ua/ukr/ 2 | Встановлення Docker на Ubuntu - https://docs.docker.com/desktop/install/ubuntu/ 3 | Створення і додавання ssh ключа - https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent 4 | -------------------------------------------------------------------------------- /2_lesson/2_les_home_work.txt: -------------------------------------------------------------------------------- 1 | 1. Створити змінні таких типів: string, integer, float, bool, list, dict, tuple, None 2 | 2. Використати вивчені оператори та порівняти між собою числа, рядки, булеві значення, списки, словники і кортежі 3 | 3. Використати вивчені функції Python: 4 | Робота з рядками: 5 | 1. Cтворити змінну num_str = 125, перевести її в тип string за допогою функції str() 6 | 2. Cтворити зміну message = 'Hi, my name is Python!' За допомогою функції replace() замінити 7 | усі букви 'y' на '0' та 'i' на '1'. 8 | 3. Cтворити зміну split_test = 'This is a split test' і розділити її по пробілах за 9 | допомогою функції split(), а потім знову обʼєднати у строку за допомогою функції join() у змінну string_join 10 | 4. Визначити довжину рядку string_join за допомогою функції len() 11 | Робота зі списками: 12 | 1. Cтворити змінну list_append = [1, 2, 3] і за допомогою функції append() додати туди спочатку 4, а потім 5 13 | 2. Cтворити змінну list_extend = [4, 5, 6] і розширити цей список іншим списком [7, 8, 9] за допомогою функції extend() 14 | 3. Визначити індекс елемента 6 у списку list_extend за допомогою функції index() 15 | 4. Визначити довжину списку list_append за допомогою функції len() 16 | Робота зі словниками: 17 | 1. Cтворити змінну dict_test = {'car': 'Toyota', 'price': 4900, 'where': 'EU'} та вивести на екран дані, які знаходяться в ключах car та where 18 | 2. За допомогою функцій keys() і values() вивести на екран ключі та їх значення 19 | 3. За допомогою функції items() вивести на екран пари ключ - значення 20 | 21 | Посилання: 22 | Дуже класний туторіал з онлайн тренажером - https://w3schoolsua.github.io/python/python_syntax.html 23 | -------------------------------------------------------------------------------- /2_lesson/built-in_functions.py: -------------------------------------------------------------------------------- 1 | # Funcs for int 2 | num_1 = "1" 3 | print(type(num_1)) 4 | 5 | num_1 = int(num_1) 6 | print(type(num_1)) 7 | 8 | num_1 = float(num_1) 9 | print(type(num_1)) 10 | 11 | # Funcs for string 12 | string = "hello world!" 13 | print(len(string)) 14 | 15 | string = string.upper() 16 | print(string) 17 | 18 | string = string.lower() 19 | print(string) 20 | 21 | string = string.capitalize() 22 | print(string) 23 | 24 | string = string.replace("!", ".") 25 | print(string) 26 | 27 | string = string.split() 28 | print(string) 29 | 30 | string = " ".join(string) 31 | print(string) 32 | 33 | string = string.count("o") 34 | print(string) 35 | 36 | string = 1 37 | string = str(string) 38 | print(type(string)) 39 | 40 | # Funcs for list 41 | base_list = [1, 2, 3] 42 | print(len(base_list)) 43 | 44 | base_list.append(4) 45 | print(base_list) 46 | 47 | base_list.extend([5, 6, 7]) 48 | print(base_list) 49 | 50 | print(base_list.index(4)) 51 | 52 | # Funcs for dict 53 | base_dict = {"name": "Tom", "age": 40, "high": 180} 54 | print(base_dict.keys()) 55 | print(base_dict.values()) 56 | print(base_dict.items()) 57 | 58 | print(base_dict["name"], base_dict.get("name"), base_dict.get("is_animal", "No")) 59 | print(base_dict["is_animal"]) -------------------------------------------------------------------------------- /2_lesson/combinations.py: -------------------------------------------------------------------------------- 1 | lst = [1, 2, 3, 4, 5] 2 | dct = {"name": "Tom", "age": 5} 3 | name = "Tom" 4 | tpl = ("n", "a", "g") 5 | 6 | result = dct["age"] in lst 7 | print(result) 8 | 9 | result = dct["age"] in lst and dct["name"] in tpl 10 | print(result) 11 | 12 | check = None 13 | 14 | print(dct["name"] == name and dct["age"] in lst) -------------------------------------------------------------------------------- /2_lesson/data_types.py: -------------------------------------------------------------------------------- 1 | int = 5 2 | print(type(int)) 3 | 4 | num_1 = 5 5 | print(type(num_1)) 6 | 7 | num_2 = 3.14 8 | print(type(num_2)) 9 | 10 | string = "hello" 11 | print(type(string)) 12 | 13 | check = True 14 | print(type(check)) 15 | 16 | lst = ["hello", "my", "name", "is", "Tom"] 17 | print(type(lst)) 18 | 19 | tpl = (1, 2, 3) 20 | print(type(tpl)) 21 | 22 | dct = {"name": "John", "age": 23} 23 | print(type(dct)) 24 | 25 | set_ex = {1, 2, 3} 26 | print(type(set_ex)) 27 | 28 | none_var = None 29 | print(type(none_var)) 30 | 31 | class Person: 32 | pass 33 | 34 | a = Person() 35 | print(type(a)) 36 | -------------------------------------------------------------------------------- /2_lesson/operators.py: -------------------------------------------------------------------------------- 1 | num_1 = 100 2 | num_2 = 10 3 | 4 | num_3 = num_1 + num_2 5 | print(num_3) 6 | 7 | num_3 = num_1 - num_2 8 | print(num_3) 9 | 10 | num_3 = num_1 * num_2 11 | print(num_3) 12 | 13 | num_3 = num_1 / num_2 14 | print(num_3) 15 | 16 | num_1 = 7 17 | num_2 = 2 18 | 19 | num_3 = num_1 / num_2 20 | num_4 = num_1 // num_2 21 | print(num_3, num_4) 22 | 23 | num_1 = 5 24 | num_2 = 2 25 | 26 | num_3 = num_1 ** num_2 27 | print(num_3) 28 | 29 | num_1 = 7 30 | num_2 = 2 31 | 32 | num_3 = num_1 % num_2 33 | print(num_3) 34 | 35 | num_1 = 10 36 | num_2 = 3 37 | 38 | num_3 = num_1 % num_2 39 | print(num_3) 40 | 41 | num_1 = 10 42 | num_2 = 5 43 | 44 | num_3 = num_1 == num_2 45 | print(num_3) 46 | 47 | num_3 = num_1 != num_2 48 | print(num_3) 49 | 50 | num_3 = num_1 < num_2 51 | print(num_3) 52 | 53 | num_3 = num_1 > num_2 54 | print(num_3) 55 | 56 | num = 10 57 | name = "Tom" 58 | 59 | result = num > 5 and name == "Tom" 60 | print(result) 61 | 62 | result = num < 5 or name == "Tom" 63 | print(result) 64 | 65 | result = num < 5 and name == "Tom" 66 | print(result) 67 | 68 | message = "Tom get some money" 69 | print(name in message) 70 | print(name not in message) 71 | 72 | name = "John" 73 | message = "You won!" 74 | print(name in message) 75 | print(name not in message) 76 | 77 | age = 50 78 | name = "Ira" 79 | animal = "Cat" 80 | 81 | print(age == 50 and "Ira" in name and animal != "dog") 82 | print(age == 50 and "I" in name or animal == "dog") 83 | print(age == 50 and "F" in name and animal != "dog") 84 | -------------------------------------------------------------------------------- /2_lesson/vars.py: -------------------------------------------------------------------------------- 1 | reserve_words = """ 2 | False await else import pass 3 | None break except in raise 4 | True class finally is return 5 | and continue for lambda try 6 | as def from nonlocal while 7 | assert del global not with 8 | async elif if or yield 9 | """ 10 | 11 | name = "Tom" 12 | Name = "Tom" 13 | print(name, Name) 14 | 15 | user_email = "example@gmail.com" 16 | userEmail = "example@gmail.com" 17 | print(user_email, userEmail) 18 | 19 | name = "Jon" 20 | print(name) 21 | name = "Tom" 22 | print(name) 23 | -------------------------------------------------------------------------------- /3_lesson/3_les_home_work.txt: -------------------------------------------------------------------------------- 1 | ### Умовні конструкції: 2 | 3 | 1. **Перевірка паролю:** 4 | Завдання: Напишіть програму, яка встановлює початковий пароль і перевіряє, чи введений користувачем пароль співпадає з ним. Якщо пароль дорівнює "password123", виведіть повідомлення "Ви увійшли в систему". В іншому випадку виведіть повідомлення "Неправильний пароль". 5 | 6 | 2. **Визначення днів тижня:** 7 | Завдання: Створіть програму, яка встановлює номер дня тижня і виводить назву відповідного дня тижня. Якщо номер дня недійсний (менше 1 або більше 7), виведіть повідомлення про помилку. 8 | 9 | ### Цикли: 10 | 11 | 1. **Таблиця множення:** 12 | Завдання: Виведіть таблицю множення для заданого числа від 1 до 10. 13 | 14 | 2. **Сума чисел:** 15 | Завдання: Визначте список чисел і обчисліть їх суму. 16 | 17 | 3. **Факторіал числа:** 18 | Завдання: Обчисліть факторіал заданого числа. 19 | 20 | 4. **Парні числа:** 21 | Завдання: Виведіть всі парні числа від 1 до 50. 22 | 23 | 5. **Пошук простих чисел:** 24 | Завдання: Знайдіть всі прості числа в заданому діапазоні. 25 | 26 | Створіть власні змінні або встановіть початкові значення, щоб виконати ці завдання без використання `input`. Використовуйте умовні конструкції і цикли для розв'язання кожного завдання. Бажаю успіхів у виконанні цих завдань! 27 | 28 | Якщо хочете використати input(), то ось кілька посилань: 29 | http://nikolay.in.ua/navchaemos/python-3/613-vvedennya-ta-vivedennya-danikh-funktsiji-input-ta-print 30 | https://webportal.com.ua/input-print-in-python/ -------------------------------------------------------------------------------- /3_lesson/if-elif-else.py: -------------------------------------------------------------------------------- 1 | string = "Hello world!" 2 | if "Hello" not in string: 3 | print("Hello in string") 4 | elif "world" in string: 5 | print("World in string") 6 | else: 7 | print("Word not in string") 8 | 9 | 10 | a = 10 11 | b = 20 12 | 13 | if a == 11 and b == 20 or b <30: 14 | print(a + b) 15 | else: 16 | print("Wrong condition") 17 | 18 | test_list = ["hello", "test", 1, 2, 3] 19 | 20 | if "hello" in test_list and 1 in test_list: 21 | print("Hello 1") 22 | elif "test" in test_list and 4 not in test_list: 23 | print("Test not 4") 24 | else: 25 | print("Your conditions were wrong") 26 | 27 | 28 | a = 10 29 | b = 20 30 | c = "chat is active" 31 | d = "count of users" 32 | print(len(c), len(d), "--------<") 33 | 34 | if len(c) >= b: 35 | print(c) 36 | elif len(d) <= a: 37 | print(d) 38 | else: 39 | print("Wrong conditions") 40 | 41 | 42 | user_1 = { 43 | "name": "Tom", 44 | "age": 21, 45 | "balance": 20000, 46 | "currency": "USD", 47 | "status": True 48 | } 49 | 50 | user_2 = { 51 | "name": "John", 52 | "age": 17, 53 | "balance": 5000, 54 | "currency": "EUR", 55 | "status": False 56 | } 57 | 58 | user_3 = { 59 | "name": "Karine", 60 | "age": 30, 61 | "balance": 100000, 62 | "currency": "UAH", 63 | "status": True 64 | } 65 | 66 | list_of_currency = ["USD", "GBR", "UAH", "EUR"] 67 | 68 | if user_1.get("name", None) and user_1["age"] >= 18 and user_1["status"]: 69 | if user_1["balance"] >= 10000 and user_1["currency"] in list_of_currency: 70 | print(f"Hello! You can create your binance account, welcome {user_1['name']}") 71 | elif user_1["balance"] >= 1000 and user_1["currency"] in list_of_currency: 72 | print("You need more money!") 73 | else: 74 | print ("Money critical not enough") 75 | elif not user_1.get("name", None): 76 | print("Please. unite voun name in voun account desenintion") 77 | elif user_1["age"] < 18: 78 | print("For registry binance account you have to be 18 year old") 79 | else: 80 | print("Something went wrong") -------------------------------------------------------------------------------- /3_lesson/loops.py: -------------------------------------------------------------------------------- 1 | test_list = [1, 2, 3, 4, 5, 6] 2 | for num in test_list: 3 | print(f"You got a {num} ---<") 4 | print(num ** 2) 5 | 6 | print("-------------------------") 7 | 8 | a = 0 9 | while a < 10: 10 | print(a, "-------<") 11 | a += 1 12 | 13 | print("-------------------------") 14 | 15 | test_list = [1, 2, 3, 4, 5, 6] 16 | while len(test_list) < 10: 17 | test_list.append(3) 18 | print(test_list) 19 | 20 | print("-------------------------") 21 | 22 | test_list = ["test", "python", "code"] 23 | for s in test_list: 24 | print(s, "-------<") 25 | if s == "test": 26 | print(s) 27 | elif s == "python": 28 | print(s) 29 | else: 30 | print(s) 31 | 32 | print("-------------------------") 33 | 34 | a = 0 35 | add_list = [] 36 | while len(add_list) < 10: 37 | add_list.append(a) 38 | a += 1 39 | if len(add_list) == 5: 40 | print("Yout are at middle of list") 41 | 42 | print("-------------------------") 43 | 44 | a = 0 45 | add_list = [] 46 | while len(add_list) < 100: 47 | print("len of list: ", len(add_list)) 48 | add_list.append(a) 49 | a += 1 50 | if len(add_list) == 50: 51 | print("Yout are at middle of list") 52 | 53 | print("-------------------------") 54 | 55 | user_1 = { 56 | "user_name": "tester", 57 | "role": "admin", 58 | "account_connection": True 59 | } 60 | 61 | user_2 = { 62 | "user_name": "junior", 63 | "role": "user", 64 | "account_connection": False 65 | } 66 | 67 | user_3 = { 68 | "user_name": "middle", 69 | "role": "pro_user", 70 | "account_connection": True 71 | } 72 | 73 | list_of_users = [user_1, user_2, user_3] 74 | 75 | for user in list_of_users: 76 | print(f"Work with {user['user_name']} account -----<<<<") 77 | if not user["account_connection"]: 78 | count_of_tries = 10 79 | while count_of_tries != 0: 80 | print("Try ty connect to user account") 81 | count_of_tries -= 1 82 | print("Count of tries left: ", count_of_tries) 83 | if count_of_tries == 5: 84 | print("Middle of tries") 85 | continue 86 | elif user["role"] == "admin": 87 | print(f"Hello in system {user['user_name']}") 88 | else: 89 | print("Welcome on the board") 90 | 91 | print("All users were checked!!") -------------------------------------------------------------------------------- /4_lesson/4_les_home_work.txt: -------------------------------------------------------------------------------- 1 | ### Списки: 2 | 3 | 1. **Робота із списками:** 4 | Завдання: Створіть список чисел. Додайте до списку числа 10 і 20, видаліть число 10 і виведіть отриманий список. 5 | 6 | 2. **Знаходження суми:** 7 | Завдання: Створіть список чисел. Знайдіть та виведіть суму всіх чисел у списку. 8 | 9 | 3. **Подвійні значення:** 10 | Завдання: Створіть список чисел. Подвойте кожне число у списку та виведіть результат. 11 | 12 | ### Кортежі: 13 | 14 | 1. **Робота із кортежами:** 15 | Завдання: Створіть кортеж з трьох різних предметів, таких як ("яблуко", "банан", "апельсин"). Виведіть кожен елемент кортежу окремо. 16 | 17 | 2. **Об'єднання кортежів:** 18 | Завдання: Створіть два кортежі з числами і об'єднайте їх у новий кортеж. Виведіть отриманий кортеж. 19 | 20 | ### Словники: 21 | 22 | 1. **Робота із словниками:** 23 | Завдання: Створіть словник, що містить інформацію про вашого улюбленого спортсмена (ім'я, вік, спорт, команда тощо). Виведіть цю інформацію на екран. 24 | 25 | 2. **Оновлення словника:** 26 | Завдання: Створіть словник, що містить ваші улюблені книги (назва книги та рік видання). Додайте до словника нову улюблену книгу та виведіть оновлений словник. 27 | 28 | 3. **Пошук значення:** 29 | Завдання: Створіть словник, що містить інформацію про країни та їх столиці. Запитайте користувача про назву країни і виведіть столицю цієї країни (якщо така країна є у словнику). 30 | 31 | Завершіть кожне завдання, використовуючи вбудовані методи для списків, кортежів та словників. Бажаю успіхів у виконанні цих завдань! -------------------------------------------------------------------------------- /4_lesson/dict.py: -------------------------------------------------------------------------------- 1 | test_dict = {"user": "Oleg", "age": 21, "country": "Poland"} 2 | print(test_dict["user"], test_dict["age"], test_dict.get("country")) 3 | print(test_dict.get("animal", "key not found")) 4 | test_dict["age"] = 30 5 | print(test_dict ["age"]) 6 | test_dict[ "animal"] = "cat" 7 | print(test_dict["animal"]) 8 | animal = test_dict.pop("animal") 9 | print(animal) 10 | 11 | copy_test = test_dict.copy() 12 | test_dict.clear() 13 | print(test_dict, "<-copied->", copy_test) 14 | 15 | for key, value in copy_test.items(): 16 | print(f"Key: {key}, Value: {value}") 17 | 18 | for value in copy_test.values(): 19 | print(value) 20 | 21 | wrong_key = copy_test.pop("currency", "key not found") 22 | print(wrong_key) 23 | 24 | 25 | dict_update = {"new_role": "admin", "salary": 10000} 26 | copy_test.update(dict_update) 27 | print(copy_test) -------------------------------------------------------------------------------- /4_lesson/list.py: -------------------------------------------------------------------------------- 1 | a = [1, 2, 3, 4, 5] 2 | b = ["apple", "banana", "cherry"] 3 | 4 | print(a[0], a[1], a[-1]) 5 | print(b[1]) 6 | 7 | print(a[1:4], a[::2], a[::]) 8 | print(b[::2]) 9 | 10 | print(a[::-1]) 11 | print(b[::-1]) 12 | 13 | a.append(6) 14 | b.append("tomato") 15 | print(a, b) 16 | 17 | a.insert(3, True) 18 | b.insert(3, "bottle") 19 | print(a, b) 20 | 21 | a.remove(True) 22 | b.remove("bottle") 23 | print(a, b) 24 | 25 | last_elem_a = a.pop() 26 | last_elem_b = b.pop() 27 | print(last_elem_a, last_elem_b) 28 | 29 | first_elem_a = a.pop(0) 30 | first_elem_b = b.pop(0) 31 | print(a.index(3), b.index("banana")) 32 | 33 | a.extend([5, 5, 5]) 34 | b.extend(["cherry", "banana", "banana"]) 35 | print(a.count(5), b.count("banana"), b.count("cherry")) 36 | 37 | print(a, b) 38 | a.sort(reverse=True) 39 | b.sort() 40 | print(a, b) 41 | a.reverse() 42 | b.reverse() 43 | print(a, b) 44 | -------------------------------------------------------------------------------- /4_lesson/tuple.py: -------------------------------------------------------------------------------- 1 | a = (1, 2, 3, 4, 5, 5, 4) 2 | print(a[0], a[1], a[2]) 3 | print(a[:2], a[-2:]) 4 | 5 | print(a.count(5), a.count(4)) 6 | print(a.index(4)) -------------------------------------------------------------------------------- /5_lesson/5_les_home_work.txt: -------------------------------------------------------------------------------- 1 | **Завдання 1: Робота з функціями** 2 | 3 | Створіть Python-файл з ім'ям `calculator.py`. У цьому файлі створіть наступні функції: 4 | 5 | 1. `add(a, b)`: Приймає два числа `a` і `b` та повертає їхню суму. 6 | 2. `subtract(a, b)`: Приймає два числа `a` і `b` та повертає їхню різницю. 7 | 3. `multiply(a, b)`: Приймає два числа `a` і `b` та повертає їхній добуток. 8 | 4. `divide(a, b)`: Приймає два числа `a` і `b` і повертає результат ділення `a` на `b`. Пам'ятайте про можливість ділення на нуль і додайте перевірку цього варіанту. 9 | 10 | Після створення цих функцій, напишіть програму, яка імпортує модуль `calculator.py` і використовує його функції для виконання обчислень. Попросіть користувача ввести два числа і операцію (додавання, віднімання, множення або ділення), і виведіть результат обчислення. 11 | 12 | **Завдання 2: Створення та імпорт власних модулів** 13 | 14 | Створіть власний Python-модуль з ім'ям `utilities.py`. У цьому модулі створіть наступні функції: 15 | 16 | 1. `calculate_average(numbers)`: Приймає список чисел `numbers` і повертає середнє арифметичне цих чисел. 17 | 2. `find_max(numbers)`: Приймає список чисел `numbers` і повертає найбільше число у списку. 18 | 19 | Після створення цього модуля, створіть інший Python-файл (наприклад, `main.py`), який імпортує модуль `utilities.py` і використовує його функції для обробки списку чисел. 20 | 21 | В `main.py` створіть список чисел та використовуйте функції з модуля `utilities` для знаходження середнього значення та найбільшого числа у списку. Виведіть результати на екран. -------------------------------------------------------------------------------- /5_lesson/functions.py: -------------------------------------------------------------------------------- 1 | def say_hello(): 2 | print("Hello world") 3 | 4 | 5 | def say_hello_user(username, age): 6 | print(f"Hello {username}, welcome to the club, buddy!") 7 | print(f"Your age is {age}, you are so beautiful!") 8 | print("-----------------------------------------") 9 | 10 | 11 | def print_numbers(): 12 | for num in range(1, 11): 13 | print(f"Current number is: {num}") 14 | 15 | 16 | def print_numbers_with_params(start, stop): 17 | for num in range(start, stop): 18 | print(f"Current number is: {num}") 19 | 20 | print("------------------------------") 21 | 22 | 23 | say_hello() 24 | print_numbers() 25 | print("<<<------->>>") 26 | 27 | user_data = {"Dima": 25, "Sarah": 34, "Tom": 11} 28 | list_of_ranges = [(1, 10), (2, 9), (0, 100)] 29 | 30 | for name, age in user_data.items(): 31 | say_hello_user(name, age) 32 | 33 | print("<<<------->>>") 34 | for start_pos, stop_pos in list_of_ranges: 35 | print_numbers_with_params(start_pos, stop_pos) 36 | 37 | 38 | def check_connection(username, count_tries, priority): 39 | if priority >= 10: 40 | finish = 5 41 | for attemt in range(1, count_tries + 1): 42 | if attemt == finish: 43 | print("Connect was successfully") 44 | break 45 | print(f"Attemp: {attemt} to connect to {username}") 46 | 47 | elif priority >= 5 and priority < 10: 48 | finish = 3 49 | for attemt in range (1, 6): 50 | if attemt == finish: 51 | print("Connect was successfully") 52 | print(f"Attemp: {attemt} to connect to username") 53 | 54 | else: 55 | print("Your username has so how priority") 56 | 57 | 58 | check_connection (count_tries=10, username="Oleg", priority=100) -------------------------------------------------------------------------------- /5_lesson/module/main.py: -------------------------------------------------------------------------------- 1 | import my_module 2 | 3 | 4 | my_module.hello("Tom") -------------------------------------------------------------------------------- /5_lesson/module/my_module.py: -------------------------------------------------------------------------------- 1 | def hello(name): 2 | print(f"Hello {name}") -------------------------------------------------------------------------------- /5_lesson/turtle_lib_practice.py: -------------------------------------------------------------------------------- 1 | import turtle 2 | 3 | 4 | def drawSquare(size, color): 5 | turtle.speed(1) 6 | turtle.color(color) 7 | turtle.begin_fill() 8 | def move(len): 9 | turtle.forward(len) 10 | turtle.left(90) 11 | 12 | for _ in range(4): 13 | move(size) 14 | 15 | turtle.end_fill() 16 | 17 | drawSquare(100, 'red') 18 | turtle.goto(200, 200) 19 | drawSquare(200, 'blue') -------------------------------------------------------------------------------- /6_lesson/6_les_home_work.txt: -------------------------------------------------------------------------------- 1 | **Завдання 1: Створення класу і об'єктів** 2 | 3 | Створіть клас `Animal`, який представляє тварину. Кожний об'єкт класу `Animal` повинен мати наступні атрибути: 4 | 5 | - `name` (ім'я тварини) 6 | - `species` (вид тварини) 7 | - `age` (вік тварини) 8 | 9 | Створіть конструктор класу, який ініціалізує ці атрибути при створенні об'єкта. Напишіть метод `make_sound()`, який буде виводити звук, який виділяє тварина. 10 | 11 | Створіть два об'єкта класу `Animal` з різними характеристиками та викличте їхні методи `make_sound()`. 12 | 13 | **Завдання 2: Робота з об'єктами** 14 | 15 | Створіть клас `Rectangle`, який представляє прямокутник. Кожен об'єкт класу `Rectangle` повинен мати наступні атрибути: 16 | 17 | - `width` (ширина прямокутника) 18 | - `height` (висота прямокутника) 19 | 20 | Створіть конструктор класу, який ініціалізує ці атрибути при створенні об'єкта. Напишіть метод `calculate_area()`, який розраховує площу прямокутника (площа = ширина * висота). 21 | 22 | Створіть два об'єкта класу `Rectangle` з різними розмірами та викличте їхні методи `calculate_area()`, виведіть площу прямокутників на екран. -------------------------------------------------------------------------------- /6_lesson/class.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | """Class for creation person""" 3 | name = "Tom" 4 | age = 18 5 | high = 180 6 | 7 | print(Person.name, Person.age) 8 | Person.age = 50 9 | print(Person.name, Person.age) 10 | print(Person.__dict__) 11 | 12 | person_1 = Person() 13 | print(person_1) 14 | print(person_1.name, person_1.age, person_1.high) 15 | 16 | person_2 = Person() 17 | print(person_2) 18 | print(person_2.name, person_2.age, person_2.high) 19 | 20 | person_1.is_animal = False 21 | print(person_1.__dict__) 22 | print(Person.__dict__) 23 | print(person_2.__dict__) 24 | 25 | print(getattr(person_1, "name")) 26 | print(getattr(person_1, "where_is", False)) 27 | 28 | person_1.age = 59 29 | person_1.color = "black" 30 | print(person_1.__dict__) 31 | 32 | setattr(person_1, "high", 100) 33 | print(person_1.high) 34 | print(person_1.__dict__) 35 | 36 | print(hasattr(person_1, "name")) 37 | print(hasattr(person_1, "where_is")) 38 | 39 | del Person.high 40 | print(Person.__dict__) 41 | print(hasattr(Person, "high")) 42 | 43 | delattr(Person, "age") 44 | print(Person.__dict__) 45 | print(hasattr(Person, "age")) 46 | -------------------------------------------------------------------------------- /6_lesson/class_init.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | """Class for creation person""" 3 | 4 | def __init__(self, name, age): 5 | self.name = name 6 | self.age = age 7 | 8 | def print_attrs(self): 9 | print(f">>>> {str(self)} <<<<") 10 | print(self.name, self.age) 11 | 12 | 13 | person_1 = Person("Tom", 18) 14 | print(person_1) 15 | person_1.print_attrs() 16 | 17 | person_2 = Person("Oleg", 50) 18 | print(person_2) 19 | person_2.print_attrs() 20 | 21 | 22 | class Point: 23 | """Class for create and set coords""" 24 | 25 | def __init__(self, x, y, z): 26 | self.x = x 27 | self.y = y 28 | self.z = z 29 | self.get_attrs() 30 | self.check_coords() 31 | 32 | def check_coords(self): 33 | for attr in self.__dict__: 34 | if getattr(self, attr, False) < 0 and not isinstance(self.__dict__[attr], str): 35 | print("Coord can't be less than 0") 36 | setattr(self, attr, 0) 37 | elif getattr(self, attr, False) > 100 and not isinstance(self.__dict__[attr], str): 38 | print("Coord can't be great than 100") 39 | setattr(self, attr, 100) 40 | print(self.__dict__) 41 | 42 | def get_attrs(self): 43 | print(self.__dict__) 44 | 45 | def set_attrs(self, x, y, z): 46 | self.x = x 47 | self.y = y 48 | self.z = z 49 | self.check_coords() 50 | 51 | coord_1 = Point(-1, 101, 50) 52 | 53 | print("-----------------") 54 | coord_1.set_attrs(1000, 1000, -5) -------------------------------------------------------------------------------- /7_lesson/7_les_home_work.txt: -------------------------------------------------------------------------------- 1 | **Завдання 1: Наслідування** 2 | 3 | Створіть базовий клас `Vehicle` (транспортний засіб), який містить наступні атрибути: 4 | 5 | - `make` (виробник) 6 | - `model` (модель) 7 | - `year` (рік виробництва) 8 | 9 | Додайте конструктор класу `Vehicle`, який ініціалізує ці атрибути. 10 | 11 | Створіть підкласи (похідні класи) від `Vehicle` для різних видів транспорту, наприклад, `Car`, `Motorcycle`, `Bicycle`, тощо. Кожен підклас повинен мати додаткові атрибути та методи, які є специфічними для цього виду транспорту. Наприклад, для класу `Car` можна додати атрибут `fuel_type` та метод `start_engine()`. 12 | 13 | Створіть об'єкти для кожного з підкласів та виведіть їхні атрибути на екран. 14 | 15 | **Завдання 2: Поліморфізм** 16 | 17 | Створіть метод `display_info()` у базовому класі `Vehicle`, який виводить загальну інформацію про транспортний засіб (наприклад, "Це [виробник] [модель] [рік] року виробництва."). 18 | 19 | В кожному з підкласів перевизначте метод `display_info()` для виведення специфічної інформації про цей вид транспорту. 20 | 21 | Створіть список об'єктів з різних видів транспорту, викличте метод `display_info()` для кожного об'єкта, і спостерігайте за тим, як поліморфізм дозволяє викликати правильну версію методу для кожного об'єкта. -------------------------------------------------------------------------------- /7_lesson/inheritance.py: -------------------------------------------------------------------------------- 1 | class A: 2 | """Class A""" 3 | name_a = "class A is a parent" 4 | is_main_class = True 5 | 6 | def print_hello(self): 7 | print("Hello from A") 8 | 9 | 10 | class B(A): 11 | """Class B""" 12 | name_b = "class B is a child" 13 | is_main_class = False 14 | 15 | def print_hello(self): 16 | print("Hello from B") 17 | 18 | 19 | class C(B): 20 | pass 21 | 22 | 23 | test_ex = C() 24 | print(test_ex.name_a) 25 | print(test_ex.name_b) 26 | print(test_ex.is_main_class) 27 | print(test_ex.print_hello()) 28 | 29 | 30 | class Vehicle: 31 | """It's a base class for Vehicles""" 32 | 33 | def __init__(self, type, color, left_of_life=100) -> None: 34 | self.type = type 35 | self.color = color 36 | self.left_of_life = left_of_life 37 | 38 | def move(self): 39 | print("Your vehicle is moving") 40 | 41 | def fix(self): 42 | if self.left_of_life <= 50: 43 | print(f"{self.type} need to fix") 44 | else: 45 | print(f"Your {self.type} is good") 46 | 47 | class Car(Vehicle): 48 | """Class Car""" 49 | 50 | def __init__(self, type, color, left_of_life, cost=0) -> None: 51 | super().__init__(type, color, left_of_life) 52 | self.cost = cost 53 | 54 | def move(self): 55 | print(f"{self.type} {self.color} is driving") 56 | print(f"Cost of this car: {self.cost}") 57 | 58 | 59 | class Bicycle(Vehicle): 60 | """Class Bicycle""" 61 | 62 | def __init__(self, type, color, left_of_life, count_of_wheels) -> None: 63 | super().__init__(type, color, left_of_life) 64 | self.count_of_wheels = count_of_wheels 65 | 66 | def move(self): 67 | print("You are so fast") 68 | 69 | 70 | car_1 = Car("car", "black", 70, 10000) 71 | car_1.move() 72 | car_1.fix() 73 | bicycle_1 = Bicycle("road_bicycle", "blue", 30, 2000) 74 | bicycle_1.move() 75 | bicycle_1.fix() 76 | -------------------------------------------------------------------------------- /7_lesson/interfaces_example.py: -------------------------------------------------------------------------------- 1 | class BaseInterface: 2 | """Base class""" 3 | def __init__(self) -> None: 4 | pass 5 | 6 | def get_attrs(self) -> None: 7 | pass 8 | 9 | def print_model(self) -> None: 10 | pass 11 | 12 | def count_of_price(self) -> None: 13 | pass 14 | 15 | def call_to_support(self) -> None: 16 | pass 17 | 18 | 19 | class SiteInterface(BaseInterface): 20 | """Interface of our site""" 21 | 22 | def __init__(self, number, model, price) -> None: 23 | super().__init__() 24 | self.number = number 25 | self.model = model 26 | self.price = price 27 | 28 | def print_model(self): 29 | print(f"Model of site: {self.model}") 30 | 31 | def count_of_price(self): 32 | print(f"Count of site price: {self.price ** 2}") 33 | 34 | def call_to_support(self) -> None: 35 | print(f"Number of support is {self.number}") 36 | print(f"Your can call from 8am to 19pm") 37 | 38 | 39 | class AppInterface(BaseInterface): 40 | """Interface of our application""" 41 | 42 | def __init__(self, number, model, price) -> None: 43 | super().__init__() 44 | self.number = number 45 | self.model = model 46 | self.price = price 47 | 48 | def print_model(self): 49 | print(f"Model of application: {self.model}") 50 | 51 | def count_of_price(self): 52 | print(f"Count of application price: {self.price ** 2}") 53 | 54 | def call_to_support(self) -> None: 55 | print(f"Number of support is {self.number}") 56 | print(f"Your can call from 8am to 19pm") 57 | 58 | 59 | site_user = SiteInterface(12345, "shop", 1000) 60 | app_user = AppInterface(322324, "android", 5000) 61 | 62 | for user in (site_user, app_user): 63 | user.print_model() 64 | user.count_of_price() 65 | user.call_to_support() 66 | print("---------------") 67 | 68 | 69 | -------------------------------------------------------------------------------- /7_lesson/polymorphism.py: -------------------------------------------------------------------------------- 1 | class Counter: 2 | """Count of something""" 3 | 4 | def __init__(self, count_obj, type_obj, max_elements) -> None: 5 | self.count_obj = count_obj 6 | self.type_obj = type_obj 7 | self.max_elements = max_elements 8 | 9 | def counter(self): 10 | print(f"Type of object: {self.type_obj}") 11 | if isinstance(self.count_obj, (list, dict, str, tuple)): 12 | count = len(self.count_obj) 13 | if count > self.max_elements: 14 | print("Count elements of your object more than need") 15 | print(f"More on {count - self.max_elements}") 16 | else: 17 | print(f"Count of elements: {count}") 18 | else: 19 | print("Your object must be iterable") 20 | 21 | def get_attrs(self): 22 | print(self.__dict__) 23 | 24 | def set_attrs(self, attr, value): 25 | if hasattr(self, attr): 26 | setattr(self, attr, value) 27 | else: 28 | print("Check your attrs") 29 | 30 | 31 | class ListElements(Counter): 32 | """Class for list elements""" 33 | 34 | def __init__(self, count_obj, type_obj, max_elements) -> None: 35 | super().__init__(count_obj, type_obj, max_elements) 36 | pass 37 | 38 | def counter(self): 39 | super().counter() 40 | print("Operation was ended") 41 | 42 | def get_attrs(self): 43 | super().get_attrs() 44 | print("Operation was ended") 45 | 46 | 47 | list_ex = ListElements([1, 2, 3, 4, 5], "list", 10) 48 | list_ex.counter() 49 | list_ex.get_attrs() 50 | list_ex.set_attrs("count_obj", [1, 2, 3, 4, 5, 6]) -------------------------------------------------------------------------------- /8_lesson/8_les_home_work.txt: -------------------------------------------------------------------------------- 1 | Завдання 1: Інкапсуляція 2 | 3 | Створіть клас "Користувач" (User), який має такі приватні поля (інкапсульовані дані): 4 | 5 | Ім'я (name) 6 | Електронна пошта (email) 7 | Пароль (password) 8 | Напишіть публічні методи для установки і отримання значень цих полів (геттери і сеттери). Потім створіть об'єкт класу "Користувач" і встановіть значення полів, а також виведіть їх на екран. 9 | 10 | Завдання 2: Абстракція 11 | 12 | Створіть клас "Фігура" (Shape), який буде абстрактним класом. У цьому класі визначіть абстрактний метод "обчислити_площу" (calculate_area). 13 | 14 | Створіть підкласи цього класу для різних геометричних фігур, наприклад, "Коло" (Circle), "Прямокутник" (Rectangle) і "Трикутник" (Triangle). У кожному з підкласів реалізуйте метод "обчислити_площу" відповідно до формули для обчислення площі кожної фігури. 15 | 16 | Створіть об'єкти кожного з підкласів і використайте метод "обчислити_площу", щоб вивести площу кожної фігури на екран. 17 | 18 | Завдання 3: Користування інкапсуляцією та абстракцією у реальному коді 19 | 20 | Розгляньте фрагмент коду з існуючого проекту або бібліотеки та ідентифікуйте в ньому використання інкапсуляції та абстракції. Поясніть, як вони застосовуються і як це допомагає поліпшити читабельність та підтримку коду. -------------------------------------------------------------------------------- /8_lesson/abstraction.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractclassmethod 2 | 3 | 4 | class Car(ABC): 5 | 6 | def __init__(self, mark, cost) -> None: 7 | self.mark = mark 8 | self.cost = cost 9 | 10 | @abstractclassmethod 11 | def car_preview(self): 12 | pass 13 | 14 | 15 | class Toyota(Car): 16 | 17 | def car_preview(self): 18 | print(f"Car {self.mark} costs {self.cost}") 19 | 20 | 21 | class Mersedes(Car): 22 | 23 | def car_preview(self): 24 | print(f"Car {self.mark} costs {self.cost}") 25 | 26 | 27 | class BMW(Car): 28 | pass 29 | 30 | 31 | class Animal(ABC): 32 | 33 | @abstractclassmethod 34 | def move(self): 35 | pass 36 | 37 | 38 | @abstractclassmethod 39 | def eat(self): 40 | pass 41 | 42 | 43 | class Cat(Animal): 44 | 45 | def __init__(self, color, age) -> None: 46 | self.color = color 47 | self.age = age 48 | 49 | 50 | def move(self): 51 | print(f"Cat with {self.color} color moves to ahead") 52 | 53 | 54 | def eat(self): 55 | print(f"Cat ate this food {self.age} years") 56 | 57 | 58 | 59 | class Dog(Animal): 60 | 61 | def __init__(self, distance, food_type) -> None: 62 | self.distance = distance 63 | self.food_type = food_type 64 | 65 | 66 | def move(self): 67 | print(f"Dog walked {self.distance} km") 68 | 69 | 70 | def eat(self): 71 | print(f"Dog ate this {self.food_type} today") 72 | 73 | 74 | class AnimalType(Animal): 75 | pass 76 | 77 | 78 | class Bird(AnimalType): 79 | 80 | 81 | def __init__(self, name) -> None: 82 | self.name = name 83 | 84 | def move(self): 85 | print(f"Bird {self.name} flyes") 86 | 87 | 88 | def eat(self): 89 | print(f"Bird {self.name} ate two days ago") 90 | 91 | 92 | 93 | cat = Cat("red", 7) 94 | cat.move() 95 | cat.eat() 96 | 97 | dog = Dog(10, "meat") 98 | dog.move() 99 | dog.eat() 100 | 101 | bird = Bird("lusy") 102 | bird.move() 103 | bird.eat() 104 | 105 | animal_type = AnimalType() 106 | animal_type.move() 107 | animal_type.eat() 108 | -------------------------------------------------------------------------------- /8_lesson/encapsulation.py: -------------------------------------------------------------------------------- 1 | class Card: 2 | """Class for users card""" 3 | 4 | def __init__(self, card_number, balance) -> None: 5 | if self.__check_attribute_type(card_number, str): 6 | self._card_number = card_number 7 | if self.__check_attribute_type(balance, float): 8 | self.__balance = balance 9 | 10 | 11 | def get_cart_data(self): 12 | return self.__dict__ 13 | 14 | 15 | def set_card_data(self, attr, value): 16 | if self.__check_attribute_type(attr, str): 17 | self.__dict__[attr] = value 18 | return {attr: self.__dict__[attr]} 19 | else: 20 | return "Attribute must be string type" 21 | 22 | 23 | def __check_attribute_type(self, attr, should_be): 24 | if type(attr) == should_be: 25 | return True 26 | else: 27 | raise TypeError(f"Attribute must be {should_be}") 28 | 29 | 30 | user_card_1 = Card("4145 3454 6787 9043", 1000.0) 31 | print(user_card_1.get_cart_data()) 32 | print(user_card_1.set_card_data("card_number", "4145 0000 6787 9043")) 33 | print(user_card_1.set_card_data("balance", 100)) -------------------------------------------------------------------------------- /9_lesson/9_les_home_work.txt: -------------------------------------------------------------------------------- 1 | Завдання 1: Принцип єдиного обов'язку (Single Responsibility Principle - SRP) 2 | 3 | Спроектуйте і реалізуйте клас "Користувач" (User), який відповідає принципу SRP. В цьому класі повинні бути методи для створення користувача, оновлення даних користувача та видалення користувача. Переконайтеся, що кожен метод відповідає за одну конкретну функцію. 4 | 5 | Завдання 2: Принцип відкритості/закритості (Open/Closed Principle - OCP) 6 | 7 | Створіть інтерфейс "Фігура" (Shape) та два класи, які реалізують цей інтерфейс, наприклад, "Коло" (Circle) та "Прямокутник" (Rectangle). Потім додайте новий клас, який розраховує площу будь-якої фігури, не модифікуючи існуючі класи. Використовуйте принцип OCP для розширення функціональності. 8 | 9 | Завдання 3: Принцип підстановки Лісков (Liskov Substitution Principle - LSP) 10 | 11 | Створіть ієрархію класів для геометричних фігур, де кожен підклас (наприклад, "Квадрат" і "Круг") може замінити базовий клас "Фігура" без порушення функціональності. Переконайтеся, що ці підкласи можуть використовуватися замість базового класу у всіх контекстах без проблем. 12 | 13 | Завдання 4: Принцип інтерфейсу користувача (Interface Segregation Principle - ISP) 14 | 15 | Розробіть інтерфейс "Мережевий принтер" (NetworkPrinter), який включає методи для друку, сканування та копіювання. Потім створіть два класи: "Принтер" (Printer) та "Сканер" (Scanner), які реалізують цей інтерфейс та використовують лише ті методи, які їм потрібні. Переконайтеся, що жоден з класів не має пустого методу. 16 | 17 | Завдання 5: Принцип залежностей (Dependency Inversion Principle - DIP) 18 | 19 | Використовуючи принцип DIP, переробіть код залежностей у вашому проекті так, щоб він використовував абстракції та інтерфейси замість конкретних реалізацій. Переконайтеся, що класи залежностей не знають про конкретну реалізацію інших класів. -------------------------------------------------------------------------------- /9_lesson/dip.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from abc import ABC, abstractclassmethod 3 | 4 | 5 | class Teams(Enum): 6 | BLUE_TEAM = 1 7 | RED_TEAM = 2 8 | GREEN_TEAM = 3 9 | 10 | 11 | class TeamMembershipLookUp: 12 | @abstractclassmethod 13 | def find_all_students(self, team): 14 | pass 15 | 16 | 17 | class Student: 18 | def __init__(self, name) -> None: 19 | self.name = name 20 | 21 | 22 | class TeamMemberShips(TeamMembershipLookUp): 23 | def __init__(self) -> None: 24 | self.team_memberships = [] 25 | 26 | def add_team_memberships(self, student, team): 27 | self.team_memberships.append((student, team)) 28 | 29 | def find_all_students(self, team): 30 | for members in self.team_memberships: 31 | if members[1] == team: 32 | yield members[0].name 33 | 34 | 35 | class Analysis: 36 | def __init__(self, team_member_ship_lookup) -> None: 37 | for student in team_member_ship_lookup.find_all_students(Teams.RED_TEAM): 38 | print(f"{student} is in Red team") 39 | 40 | 41 | 42 | student_1 = Student("Oleg") 43 | student_2 = Student("Dima") 44 | student_3 = Student("Sergey") 45 | 46 | team_memberships = TeamMemberShips() 47 | team_memberships.add_team_memberships(student_1, Teams.RED_TEAM) 48 | team_memberships.add_team_memberships(student_2, Teams.RED_TEAM) 49 | team_memberships.add_team_memberships(student_3, Teams.RED_TEAM) 50 | 51 | Analysis(team_memberships) -------------------------------------------------------------------------------- /9_lesson/isp.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractclassmethod 2 | 3 | 4 | class MakeCall(ABC): 5 | 6 | @abstractclassmethod 7 | def make_call(self): 8 | pass 9 | 10 | class SendSms(ABC): 11 | 12 | @abstractclassmethod 13 | def send_sms(self): 14 | pass 15 | 16 | class GetInternet(ABC): 17 | 18 | @abstractclassmethod 19 | def get_internet(self): 20 | pass 21 | 22 | 23 | class MobilePhone(MakeCall, SendSms, GetInternet): 24 | 25 | def make_call(self): 26 | print("calling to abonent...") 27 | 28 | def send_sms(self): 29 | print("sending sms to abonent...") 30 | 31 | def get_internet(self): 32 | print("get connect to internet...") 33 | 34 | 35 | class StacionarPhone(MakeCall): 36 | 37 | def make_call(self): 38 | print("calling to abonent...") 39 | 40 | 41 | 42 | 43 | 44 | mobile_phone = MobilePhone() 45 | mobile_phone.make_call() 46 | mobile_phone.send_sms() 47 | mobile_phone.get_internet() 48 | 49 | print("------------") 50 | 51 | stacionar_phone = StacionarPhone() 52 | stacionar_phone.make_call() -------------------------------------------------------------------------------- /9_lesson/lsp.py: -------------------------------------------------------------------------------- 1 | class Car: 2 | def __init__(self, type) -> None: 3 | self.type = type 4 | self.properties = {} 5 | 6 | def set_propetries(self, color, cost, capacity): 7 | self.properties = {"Color": color, "Cost": cost, "Capacity": capacity} 8 | 9 | def get_properties(self): 10 | return self.properties 11 | 12 | 13 | class PetrolCar(Car): 14 | def __init__(self, type) -> None: 15 | self.type = type 16 | self.properties = {} 17 | 18 | 19 | car = Car("Toyota") 20 | car.set_propetries("Red", 10000, 6) 21 | 22 | petrol_car = PetrolCar("Volvo") 23 | petrol_car.set_propetries("Blue", 5000, 4) 24 | 25 | cars = [car, petrol_car] 26 | 27 | def get_concret_color_car(color): 28 | count = 0 29 | car_types = [] 30 | for car in cars: 31 | if car.properties["Color"] == color: 32 | count += 1 33 | car_types.append(car.type) 34 | 35 | print(f"Count of {color} cars: {count}\nCat types: {car_types}") 36 | 37 | get_concret_color_car("Blue") -------------------------------------------------------------------------------- /9_lesson/ocp.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractclassmethod 2 | 3 | 4 | class DiscountCalculator(ABC): 5 | 6 | @abstractclassmethod 7 | def get_discounted_product(): 8 | pass 9 | 10 | 11 | class DiscountCalculatorShirt(DiscountCalculator): 12 | 13 | def __init__(self, cost) -> None: 14 | self.cost = cost 15 | 16 | def get_discounted_product(self): 17 | return self.cost - (self.cost * 0.10) 18 | 19 | 20 | class DiscountCalculatorTShirt(DiscountCalculator): 21 | 22 | def __init__(self, cost) -> None: 23 | self.cost = cost 24 | 25 | def get_discounted_product(self): 26 | return self.cost - (self.cost * 0.15) 27 | 28 | 29 | class DiscountCalculatorPant(DiscountCalculator): 30 | 31 | def __init__(self, cost) -> None: 32 | self.cost = cost 33 | 34 | def get_discounted_product(self): 35 | return self.cost - (self.cost * 0.20) 36 | 37 | 38 | ds_shirt = DiscountCalculatorShirt(100) 39 | print(ds_shirt.get_discounted_product()) 40 | ds_tshirt = DiscountCalculatorTShirt(1000) 41 | print(ds_tshirt.get_discounted_product()) 42 | ds_pant = DiscountCalculatorPant(500) 43 | print(ds_pant.get_discounted_product()) -------------------------------------------------------------------------------- /9_lesson/srp.py: -------------------------------------------------------------------------------- 1 | class Journal: 2 | def __init__(self) -> None: 3 | self.entries = [] 4 | self.count = 0 5 | 6 | def add_entry(self, text): 7 | self.count += 1 8 | self.entries.append(f"{self.count}: {text}") 9 | 10 | def remove_entry(self, pos): 11 | del self.entries[pos] 12 | 13 | def __str__(self): 14 | return "\n".join(self.entries) 15 | 16 | 17 | class SaveFiles: 18 | 19 | @staticmethod 20 | def save_to_file(journal, filename): 21 | with open(file=filename, mode="w") as file: 22 | file.write(journal) 23 | 24 | 25 | class LoadFromWeb: 26 | 27 | @staticmethod 28 | def load(journal, filename): 29 | pass 30 | 31 | 32 | j = Journal() 33 | j.add_entry("I ate today") 34 | j.add_entry("I slept yesterday") 35 | print(f"Count of entries:\n{j}") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-core 2 | This project can help you to study Python and FastApi 3 | --------------------------------------------------------------------------------