From 25a55d9d79cf08098e2ee98b0f714ab1336b3d7e Mon Sep 17 00:00:00 2001 From: Kazantsev Daniil Date: Mon, 13 Oct 2025 02:49:23 +0300 Subject: [PATCH 1/5] Implement ASGI application with fibonacci, factorial, and mean endpoints --- hw1/app.py | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 183 insertions(+), 5 deletions(-) diff --git a/hw1/app.py b/hw1/app.py index 6107b870..aae039dc 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -1,4 +1,159 @@ from typing import Any, Awaitable, Callable +from urllib.parse import parse_qs +import json +import math +import re + + +HTTP_OK = 200 +HTTP_BAD_REQUEST = 400 +HTTP_NOT_FOUND = 404 +HTTP_UNPROCESSABLE_ENTITY = 422 + + +def fibonacci(n: int) -> int: + if n == 0: + return 0 + elif n == 1: + return 1 + + a, b = 0, 1 + for _ in range(2, n + 1): + a, b = b, a + b + return b + + +def factorial(n: int) -> int: + return math.factorial(n) + + +def mean(numbers: list[int | float]) -> float: + if not numbers: + raise ValueError('Empty list') + return sum(numbers) / len(numbers) + + +async def read_body(receive: Callable[[], Awaitable[dict[str, Any]]]) -> bytes: + body_parts = [] + while True: + message = await receive() + if message['type'] != 'http.request': + continue + + body_parts.append(message.get('body', b'')) + if not message.get('more_body', False): + break + + return b''.join(body_parts) + + +async def send_response( + send: Callable[[dict[str, Any]], Awaitable[None]], + status: int, + body: dict[str, Any] | None = None, +): + await send({ + 'type': 'http.response.start', + 'status': status, + 'headers': [[b"content-type", b"application/json"]], + }) + + response_body = json.dumps(body if body else {}).encode('utf-8') + await send({ + 'type': 'http.response.body', + 'body': response_body, + }) + + +async def handle_lifespan( + receive: Callable[[], Awaitable[dict[str, Any]]], + send: Callable[[dict[str, Any]], Awaitable[None]], +): + while True: + message = await receive() + if message['type'] == 'lifespan.startup': + await send({'type': 'lifespan.startup.complete'}) + elif message['type'] == 'lifespan.shutdown': + await send({'type': 'lifespan.shutdown.complete'}) + return + + +async def handle_fibonacci( + path: str, + send: Callable[[dict[str, Any]], Awaitable[None]], +): + match = re.match(r'^/fibonacci/(.+)$', path) + if not match: + await send_response(send, HTTP_NOT_FOUND) + return + + try: + n = int(match.group(1)) + if n < 0: + await send_response(send, HTTP_BAD_REQUEST) + return + + result = fibonacci(n) + await send_response(send, HTTP_OK, {'result': result}) + except ValueError: + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + + +async def handle_factorial( + query_string: bytes, + send: Callable[[dict[str, Any]], Awaitable[None]], +): + if not query_string: + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + return + + params = parse_qs(query_string.decode('utf-8')) + + if 'n' not in params or len(params['n']) != 1: + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + return + + try: + n = int(params['n'][0]) + if n < 0: + await send_response(send, HTTP_BAD_REQUEST) + return + + result = factorial(n) + await send_response(send, HTTP_OK, {'result': result}) + except ValueError: + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + + +async def handle_mean( + receive: Callable[[], Awaitable[dict[str, Any]]], + send: Callable[[dict[str, Any]], Awaitable[None]], +): + body_bytes = await read_body(receive) + + if not body_bytes: + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + return + + try: + data = json.loads(body_bytes.decode('utf-8')) + + if not isinstance(data, list): + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + return + + if len(data) == 0: + await send_response(send, HTTP_BAD_REQUEST) + return + + if not all(isinstance(x, (int, float)) for x in data): + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) + return + + result = mean(data) + await send_response(send, HTTP_OK, {'result': result}) + except (json.JSONDecodeError, ValueError): + await send_response(send, HTTP_UNPROCESSABLE_ENTITY) async def application( @@ -6,14 +161,37 @@ async def application( receive: Callable[[], Awaitable[dict[str, Any]]], send: Callable[[dict[str, Any]], Awaitable[None]], ): - """ + ''' Args: scope: Словарь с информацией о запросе receive: Корутина для получения сообщений от клиента send: Корутина для отправки сообщений клиенту - """ - # TODO: Ваша реализация здесь + ''' + if scope['type'] == 'lifespan': + await handle_lifespan(receive, send) + return + + if scope['type'] != 'http': + return + + method = scope['method'] + path = scope['path'] + + if method != 'GET': + await send_response(send, HTTP_NOT_FOUND) + return + + if path.startswith('/fibonacci/'): + await handle_fibonacci(path, send) + elif path == '/factorial': + query_string = scope.get('query_string', b'') + await handle_factorial(query_string, send) + elif path == '/mean': + await handle_mean(receive, send) + else: + await send_response(send, HTTP_NOT_FOUND) + -if __name__ == "__main__": +if __name__ == '__main__': import uvicorn - uvicorn.run("app:application", host="0.0.0.0", port=8000, reload=True) + uvicorn.run('app:application', host='0.0.0.0', port=8000, reload=True) From 3ada1e6f0e2ad0282fcb32c78cd5d63cf483d186 Mon Sep 17 00:00:00 2001 From: Kazantsev Daniil Date: Sat, 18 Oct 2025 01:51:20 +0300 Subject: [PATCH 2/5] hw2 --- hw2/hw/shop_api/main.py | 166 +++++++++++++++++++++++++++++++++- hw2/hw/shop_api/models.py | 21 +++++ hw2/hw/shop_api/repository.py | 82 +++++++++++++++++ 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 hw2/hw/shop_api/models.py create mode 100644 hw2/hw/shop_api/repository.py diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index f60a8c60..2f46b248 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,3 +1,167 @@ -from fastapi import FastAPI +from typing import List, Optional + +from fastapi import FastAPI, HTTPException, status +from fastapi.encoders import jsonable_encoder +from fastapi.responses import Response, JSONResponse +from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat, BaseModel + +from shop_api.models import CartItem, Cart, Item +from shop_api.repository import CartNotFoundException, CartsRepository, ItemNotFoundException, ItemsRepository app = FastAPI(title="Shop API") + + +@app.post("/cart") +async def create_cart(): + new_cart: Cart = CartsRepository.create_cart() + return JSONResponse( + content={"id": new_cart.id}, + headers={"location": f"/cart/{new_cart.id}"}, + status_code=status.HTTP_201_CREATED, + ) + + +@app.get("/cart/{id}") +async def get_cart(id: NonNegativeInt): + try: + cart = CartsRepository.get_cart(id) + except CartNotFoundException: + raise HTTPException(status_code=404, detail="Cart not found") + return cart + + +@app.get("/cart") +async def get_carts(offset: NonNegativeInt = 0, + limit: PositiveInt = 10, + min_price: Optional[NonNegativeFloat] = None, + max_price: Optional[NonNegativeFloat] = None, + min_quantity: Optional[NonNegativeInt] = None, + max_quantity: Optional[NonNegativeInt] = None): + carts: List[Cart] = CartsRepository.get_carts(offset, limit) + result = [] + for cart in carts: + if min_price is not None and cart.price < min_price: + continue + if max_price is not None and cart.price > max_price: + continue + + cart_quantity = sum([item.quantity for item in cart.items]) + if min_quantity is not None and cart_quantity < min_quantity: + continue + if max_quantity is not None and cart_quantity > max_quantity: + continue + + result.append(cart) + + return result + + +@app.post("/cart/{cart_id}/add/{item_id}") +async def add_item_to_cart(cart_id: NonNegativeInt, item_id: NonNegativeInt): + try: + cart: Cart = CartsRepository.get_cart(cart_id) + except CartNotFoundException: + raise HTTPException(status_code=404, detail="Cart not found") + + try: + item: Item = ItemsRepository.get_item(item_id) + except ItemNotFoundException: + raise HTTPException(status_code=404, detail="Item not found") + + for cart_item in cart.items: + if cart_item.id == item.id: + cart_item.quantity += 1 + break + else: + cart.items.append(CartItem(id=item.id, name=item.name, quantity=1, + available=not item.deleted)) + cart.price += item.price + + try: + CartsRepository.update_cart(cart) + except CartNotFoundException: + raise HTTPException(status_code=500, detail="Internal server error") + + +class CreateItemRequestBody(BaseModel): + name: str + price: float + + +@app.post("/item", status_code=201) +async def create_item(body: CreateItemRequestBody): + new_item: Item = ItemsRepository.create_item(name=body.name, price=body.price) + return JSONResponse( + content=jsonable_encoder(new_item), + headers={"location": f"/item/{new_item.id}"}, + status_code=status.HTTP_201_CREATED, + ) + + +@app.get("/item/{id}") +async def get_item(id: NonNegativeInt): + try: + item: Item = ItemsRepository.get_item(id) + except ItemNotFoundException: + raise HTTPException(status_code=404, detail="Item not found") + return item + + +@app.get("/item") +async def get_items(offset: NonNegativeInt = 0, + limit: PositiveInt = 10, + min_price: Optional[NonNegativeFloat] = None, + max_price: Optional[NonNegativeFloat] = None, + show_deleted: bool = False): + items: List[Item] = ItemsRepository.get_items(offset, limit) + result = [] + for item in items: + if min_price is not None and item.price < min_price: + continue + if max_price is not None and item.price > max_price: + continue + if not show_deleted and item.deleted: + continue + + result.append(item) + + return result + + +class ReplaceItemRequestBody(BaseModel): + name: str + price: float + + +@app.put("/item/{id}") +async def replace_item(id: NonNegativeInt, body: ReplaceItemRequestBody): + try: + item: Item = ItemsRepository.replace_item(item_id=id, name=body.name, price=body.price) + except ItemNotFoundException: + raise HTTPException(status_code=404, detail="Item not found") + return item + + +class UpdateItemRequestBody(BaseModel): + model_config = {"extra": "forbid"} + + name: Optional[str] = None + price: Optional[float] = None + + +@app.patch("/item/{id}") +async def update_item(id: NonNegativeInt, body: UpdateItemRequestBody): + try: + updated_item: Optional[Item] = ItemsRepository.update_item( + item_id=id, name=body.name, price=body.price) + except ItemNotFoundException: + raise HTTPException(status_code=404, detail="Item not found") + + if not updated_item: + return Response(status_code=status.HTTP_304_NOT_MODIFIED) + return updated_item + + +@app.delete("/item/{id}") +async def delete_item(id: NonNegativeInt): + ItemsRepository.delete_item(item_id=id) diff --git a/hw2/hw/shop_api/models.py b/hw2/hw/shop_api/models.py new file mode 100644 index 00000000..2185fe31 --- /dev/null +++ b/hw2/hw/shop_api/models.py @@ -0,0 +1,21 @@ +from typing import List + +from pydantic import BaseModel + + +class CartItem(BaseModel): + id: int + name: str + quantity: int + available: bool + +class Cart(BaseModel): + id: int + items: List[CartItem] + price: float + +class Item(BaseModel): + id: int + name: str + price: float + deleted: bool diff --git a/hw2/hw/shop_api/repository.py b/hw2/hw/shop_api/repository.py new file mode 100644 index 00000000..eb8622f5 --- /dev/null +++ b/hw2/hw/shop_api/repository.py @@ -0,0 +1,82 @@ +from typing import List, Optional + +from shop_api.models import CartItem, Cart, Item + + +carts: List[Cart] = [] + +class CartNotFoundException(Exception): + pass + +class CartsRepository: + def create_cart() -> Cart: + cart_id = len(carts) + cart = Cart(id=cart_id, items=[], price=0.0) + carts.append(cart) + return cart.model_copy(deep=True) + + def get_cart(cart_id: int) -> Cart: + if cart_id >= len(carts): + raise CartNotFoundException() + return carts[cart_id].model_copy(deep=True) + + def get_carts(offset: int, limit: int) -> List[Cart]: + return [cart.model_copy(deep=True) for cart in carts[offset:offset+limit]] + + def update_cart(new_cart: Cart): + if new_cart.id > len(carts): + raise CartNotFoundException() + carts[new_cart.id] = new_cart + + +items: List[Item] = [] + +class ItemNotFoundException(Exception): + pass + +class ItemsRepository: + def create_item(name: str, price: float) -> Item: + item_id = len(items) + item = Item(id=item_id, name=name, price=price, deleted=False) + items.append(item) + return item.model_copy(deep=True) + + def _get_item(item_id: int) -> Item: + if item_id >= len(items): + raise ItemNotFoundException() + return items[item_id] + + def get_item(item_id: int) -> Item: + item: Item = ItemsRepository._get_item(item_id) + if item.deleted: + raise ItemNotFoundException() + + return item.model_copy(deep=True) + + def get_items(offset: int, limit: int) -> List[Item]: + return [item.model_copy(deep=True) for item in items[offset:offset+limit]] + + def replace_item(item_id: int, name: str, price: float) -> Item: + if item_id >= len(items): + raise ItemNotFoundException() + + item = Item(id=item_id, name=name, price=price, deleted=False) + items[item_id] = item + return item.model_copy(deep=True) + + def update_item(item_id: int, name: Optional[str], price: Optional[float]) -> Optional[Item]: + item: Item = ItemsRepository._get_item(item_id) + + if item.deleted: + return None + + if name is not None: + item.name = name + if price is not None: + item.price = price + + return item.model_copy(deep=True) + + def delete_item(item_id: int): + item: Item = ItemsRepository._get_item(item_id) + item.deleted = True From 7a0795636eb7726aa83b69265892f374238319a1 Mon Sep 17 00:00:00 2001 From: Kazantsev Daniil Date: Sun, 19 Oct 2025 17:06:23 +0300 Subject: [PATCH 3/5] hw3 --- hw2/hw/Dockerfile | 23 ++++++++++++++ hw2/hw/dash_example.png | Bin 0 -> 125353 bytes hw2/hw/docker-compose.yml | 36 ++++++++++++++++++++++ hw2/hw/requirements.txt | 2 ++ hw2/hw/settings/prometheus/prometheus.yml | 10 ++++++ hw2/hw/shop_api/main.py | 7 +++++ 6 files changed, 78 insertions(+) create mode 100644 hw2/hw/Dockerfile create mode 100644 hw2/hw/dash_example.png create mode 100644 hw2/hw/docker-compose.yml create mode 100644 hw2/hw/settings/prometheus/prometheus.yml diff --git a/hw2/hw/Dockerfile b/hw2/hw/Dockerfile new file mode 100644 index 00000000..27b0b636 --- /dev/null +++ b/hw2/hw/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12 AS base + +ARG PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=on \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=500 + +RUN apt-get update && apt-get install -y gcc +RUN python -m pip install --upgrade pip + +WORKDIR $APP_ROOT/src +COPY . ./ + +ENV VIRTUAL_ENV=$APP_ROOT/src/.venv \ + PATH=$APP_ROOT/src/.venv/bin:$PATH + +RUN pip install -r requirements.txt + +FROM base as local + +CMD ["uvicorn", "shop_api.main:app", "--port", "8080", "--host", "0.0.0.0"] diff --git a/hw2/hw/dash_example.png b/hw2/hw/dash_example.png new file mode 100644 index 0000000000000000000000000000000000000000..1c65a4f267ab74b7f92b9b50dbca6df8716e9843 GIT binary patch literal 125353 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 Veq;R;>=0.117.1 uvicorn>=0.24.0 +prometheus-client +prometheus-fastapi-instrumentator # Зависимости для тестирования pytest>=7.4.0 diff --git a/hw2/hw/settings/prometheus/prometheus.yml b/hw2/hw/settings/prometheus/prometheus.yml new file mode 100644 index 00000000..7fa1951b --- /dev/null +++ b/hw2/hw/settings/prometheus/prometheus.yml @@ -0,0 +1,10 @@ +global: + scrape_interval: 10s + evaluation_interval: 10s + +scrape_configs: + - job_name: shop-api-local + metrics_path: /metrics + static_configs: + - targets: + - local:8080 diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index 2f46b248..5095db76 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -3,12 +3,15 @@ from fastapi import FastAPI, HTTPException, status from fastapi.encoders import jsonable_encoder from fastapi.responses import Response, JSONResponse +from prometheus_client import Counter +from prometheus_fastapi_instrumentator import Instrumentator from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat, BaseModel from shop_api.models import CartItem, Cart, Item from shop_api.repository import CartNotFoundException, CartsRepository, ItemNotFoundException, ItemsRepository app = FastAPI(title="Shop API") +instrumentator = Instrumentator().instrument(app).expose(app) @app.post("/cart") @@ -21,8 +24,12 @@ async def create_cart(): ) +test_get_cart_counter = Counter('get_cart_counter', 'Test custum counter') + + @app.get("/cart/{id}") async def get_cart(id: NonNegativeInt): + test_get_cart_counter.inc() try: cart = CartsRepository.get_cart(id) except CartNotFoundException: From 3ec2bbb4b561c0ae859920e9a3fc02881769e2fd Mon Sep 17 00:00:00 2001 From: Kazantsev Daniil Date: Sun, 26 Oct 2025 19:40:00 +0300 Subject: [PATCH 4/5] hw4 --- hw2/hw/DockerfilePytest | 23 +++ hw2/hw/docker-compose-pytest.yml | 59 ++++++ hw2/hw/docker-compose.yml | 26 +++ hw2/hw/requirements.txt | 2 + hw2/hw/shop_api/in_memory_repository.py | 86 ++++++++ hw2/hw/shop_api/main.py | 112 +++++----- hw2/hw/shop_api/models.py | 6 + hw2/hw/shop_api/postgres_repository.py | 198 ++++++++++++++++++ hw2/hw/shop_api/repository.py | 82 -------- hw2/hw/test_homework2.py | 7 +- lecture4/hw/Dockerfile | 23 +++ lecture4/hw/demo.py | 264 ++++++++++++++++++++++++ lecture4/hw/docker-compose.yml | 37 ++++ lecture4/hw/requirements.txt | 2 + 14 files changed, 793 insertions(+), 134 deletions(-) create mode 100644 hw2/hw/DockerfilePytest create mode 100644 hw2/hw/docker-compose-pytest.yml create mode 100644 hw2/hw/shop_api/in_memory_repository.py create mode 100644 hw2/hw/shop_api/postgres_repository.py delete mode 100644 hw2/hw/shop_api/repository.py create mode 100644 lecture4/hw/Dockerfile create mode 100644 lecture4/hw/demo.py create mode 100644 lecture4/hw/docker-compose.yml create mode 100644 lecture4/hw/requirements.txt diff --git a/hw2/hw/DockerfilePytest b/hw2/hw/DockerfilePytest new file mode 100644 index 00000000..3c9f591a --- /dev/null +++ b/hw2/hw/DockerfilePytest @@ -0,0 +1,23 @@ +FROM python:3.12 AS base + +ARG PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=on \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=500 + +RUN apt-get update && apt-get install -y gcc +RUN python -m pip install --upgrade pip + +WORKDIR $APP_ROOT/src +COPY . ./ + +ENV VIRTUAL_ENV=$APP_ROOT/src/.venv \ + PATH=$APP_ROOT/src/.venv/bin:$PATH + +RUN pip install -r requirements.txt + +FROM base as local + +CMD ["pytest", "-vv", "--showlocals", "--strict", "./test_homework2.py"] diff --git a/hw2/hw/docker-compose-pytest.yml b/hw2/hw/docker-compose-pytest.yml new file mode 100644 index 00000000..762f15af --- /dev/null +++ b/hw2/hw/docker-compose-pytest.yml @@ -0,0 +1,59 @@ +version: "3" + +services: + + local: + build: + context: . + dockerfile: ./DockerfilePytest + target: local + restart: no + ports: + - 8080:8080 + environment: + PYTHONPATH: . + SHOP_API_DB_TYPE: postgres + POSTGRES_ADDRESS: postgres + POSTGRES_PORT: ${POSTGRES_PORT} + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:latest + ports: + - "${POSTGRES_PORT}:5432" + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + healthcheck: + test: [ "CMD-SHELL", "pg_isready" ] + interval: 1s + timeout: 5s + retries: 10 + + grafana: + image: grafana/grafana:latest + ports: + - 3000:3000 + restart: always + volumes: + - grafana-storage:/var/lib/grafana + + prometheus: + image: prom/prometheus + volumes: + - ./settings/prometheus/:/etc/prometheus/ + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.console.libraries=/usr/share/prometheus/console_libraries" + - "--web.console.templates=/usr/share/prometheus/consoles" + ports: + - 9090:9090 + restart: always + +volumes: + grafana-storage: {} diff --git a/hw2/hw/docker-compose.yml b/hw2/hw/docker-compose.yml index 1564ac24..453bff47 100644 --- a/hw2/hw/docker-compose.yml +++ b/hw2/hw/docker-compose.yml @@ -10,6 +10,31 @@ services: restart: always ports: - 8080:8080 + environment: + PYTHONPATH: . + SHOP_API_DB_TYPE: postgres + POSTGRES_ADDRESS: postgres + POSTGRES_PORT: ${POSTGRES_PORT} + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:latest + ports: + - "${POSTGRES_PORT}:5432" + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + healthcheck: + test: [ "CMD-SHELL", "pg_isready" ] + interval: 1s + timeout: 5s + retries: 10 + volumes: + - pgdata:/var/lib/postgresql grafana: image: grafana/grafana:latest @@ -34,3 +59,4 @@ services: volumes: grafana-storage: {} + pgdata: {} diff --git a/hw2/hw/requirements.txt b/hw2/hw/requirements.txt index 545007ac..cc1cb98d 100644 --- a/hw2/hw/requirements.txt +++ b/hw2/hw/requirements.txt @@ -3,6 +3,8 @@ fastapi>=0.117.1 uvicorn>=0.24.0 prometheus-client prometheus-fastapi-instrumentator +SQLAlchemy +psycopg2 # Зависимости для тестирования pytest>=7.4.0 diff --git a/hw2/hw/shop_api/in_memory_repository.py b/hw2/hw/shop_api/in_memory_repository.py new file mode 100644 index 00000000..4bf694fd --- /dev/null +++ b/hw2/hw/shop_api/in_memory_repository.py @@ -0,0 +1,86 @@ +from typing import List, Optional + +from shop_api.models import CartItem, Cart, Item +from shop_api.models import CartNotFoundException, Item, ItemNotFoundException + + +class Repository: + carts: List[Cart] = [] + items: List[Item] = [] + + def create_cart(self) -> Cart: + cart_id = len(self.carts) + cart = Cart(id=cart_id, items=[], price=0.0) + self.carts.append(cart) + return cart.model_copy(deep=True) + + def _get_cart(self, cart_id: int) -> Cart: + if cart_id >= len(self.carts): + raise CartNotFoundException() + return self.carts[cart_id] + + def get_cart(self, cart_id: int) -> Cart: + return self._get_cart(cart_id).model_copy(deep=True) + + def get_carts(self, offset: int, limit: int) -> List[Cart]: + return [cart.model_copy(deep=True) for cart in self.carts[offset:offset+limit]] + + def add_item_to_cart(self, cart_id: int, item_id: int): + cart: Cart = self._get_cart(cart_id) + item: Item = self.get_item(item_id) + + for cart_item in cart.items: + if cart_item.id == item.id: + cart_item.quantity += 1 + break + else: + cart.items.append(CartItem(id=item.id, name=item.name, quantity=1, + available=not item.deleted)) + + cart.price += item.price + + def create_item(self, name: str, price: float) -> Item: + item_id = len(self.items) + item = Item(id=item_id, name=name, price=price, deleted=False) + self.items.append(item) + return item.model_copy(deep=True) + + def _get_item(self, item_id: int) -> Item: + if item_id >= len(self.items): + raise ItemNotFoundException() + return self.items[item_id] + + def get_item(self, item_id: int) -> Item: + item: Item = self._get_item(item_id) + if item.deleted: + raise ItemNotFoundException() + + return item.model_copy(deep=True) + + def get_items(self, offset: int, limit: int) -> List[Item]: + return [item.model_copy(deep=True) for item in self.items[offset:offset+limit]] + + def replace_item(self, item_id: int, name: str, price: float) -> Item: + if item_id >= len(self.items): + raise ItemNotFoundException() + + item = Item(id=item_id, name=name, price=price, deleted=False) + self.items[item_id] = item + return item.model_copy(deep=True) + + def update_item(self, item_id: int, name: Optional[str], price: Optional[float]) -> Optional[Item]: + item: Item = self._get_item(item_id) + + if item.deleted: + return None + + if name is not None: + item.name = name + if price is not None: + item.price = price + + return item.model_copy(deep=True) + + def delete_item(self, item_id: int): + item: Item = self._get_item(item_id) + item.deleted = True diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index 5095db76..9766ec2f 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,3 +1,4 @@ +import os from typing import List, Optional from fastapi import FastAPI, HTTPException, status @@ -7,19 +8,32 @@ from prometheus_fastapi_instrumentator import Instrumentator from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat, BaseModel -from shop_api.models import CartItem, Cart, Item -from shop_api.repository import CartNotFoundException, CartsRepository, ItemNotFoundException, ItemsRepository +from shop_api.models import Cart, Item +from shop_api.models import CartNotFoundException, ItemNotFoundException +from shop_api.in_memory_repository import Repository as InMemoryRepository +from shop_api.postgres_repository import Repository as PostgresRepository -app = FastAPI(title="Shop API") + +def get_repository(base: str): + if base == 'in-memory': + return InMemoryRepository() + elif base == 'postgres': + return PostgresRepository() + raise Exception('Unknown base') + + +repository = get_repository(os.environ.get('SHOP_API_DB_TYPE', 'in-memory')) + +app = FastAPI(title='Shop API') instrumentator = Instrumentator().instrument(app).expose(app) -@app.post("/cart") +@app.post('/cart') async def create_cart(): - new_cart: Cart = CartsRepository.create_cart() + new_cart: Cart = repository.create_cart() return JSONResponse( - content={"id": new_cart.id}, - headers={"location": f"/cart/{new_cart.id}"}, + content={'id': new_cart.id}, + headers={'location': f'/cart/{new_cart.id}'}, status_code=status.HTTP_201_CREATED, ) @@ -27,24 +41,27 @@ async def create_cart(): test_get_cart_counter = Counter('get_cart_counter', 'Test custum counter') -@app.get("/cart/{id}") +@app.get('/cart/{id}') async def get_cart(id: NonNegativeInt): test_get_cart_counter.inc() try: - cart = CartsRepository.get_cart(id) + cart = repository.get_cart(id) except CartNotFoundException: - raise HTTPException(status_code=404, detail="Cart not found") + raise HTTPException(status_code=404, detail='Cart not found') + except Exception as e: + print("Unexpected internal error: ", e) + raise HTTPException(status_code=500, detail='Internal server error') return cart -@app.get("/cart") +@app.get('/cart') async def get_carts(offset: NonNegativeInt = 0, limit: PositiveInt = 10, min_price: Optional[NonNegativeFloat] = None, max_price: Optional[NonNegativeFloat] = None, min_quantity: Optional[NonNegativeInt] = None, max_quantity: Optional[NonNegativeInt] = None): - carts: List[Cart] = CartsRepository.get_carts(offset, limit) + carts: List[Cart] = repository.get_carts(offset, limit) result = [] for cart in carts: if min_price is not None and cart.price < min_price: @@ -63,31 +80,17 @@ async def get_carts(offset: NonNegativeInt = 0, return result -@app.post("/cart/{cart_id}/add/{item_id}") +@app.post('/cart/{cart_id}/add/{item_id}') async def add_item_to_cart(cart_id: NonNegativeInt, item_id: NonNegativeInt): try: - cart: Cart = CartsRepository.get_cart(cart_id) + cart: Cart = repository.add_item_to_cart(cart_id, item_id) except CartNotFoundException: - raise HTTPException(status_code=404, detail="Cart not found") - - try: - item: Item = ItemsRepository.get_item(item_id) + raise HTTPException(status_code=404, detail='Cart not found') except ItemNotFoundException: - raise HTTPException(status_code=404, detail="Item not found") - - for cart_item in cart.items: - if cart_item.id == item.id: - cart_item.quantity += 1 - break - else: - cart.items.append(CartItem(id=item.id, name=item.name, quantity=1, - available=not item.deleted)) - cart.price += item.price - - try: - CartsRepository.update_cart(cart) - except CartNotFoundException: - raise HTTPException(status_code=500, detail="Internal server error") + raise HTTPException(status_code=404, detail='Item not found') + except Exception as e: + print("Unexpected internal error: ", e) + raise HTTPException(status_code=500, detail='Internal server errorss') class CreateItemRequestBody(BaseModel): @@ -95,32 +98,35 @@ class CreateItemRequestBody(BaseModel): price: float -@app.post("/item", status_code=201) +@app.post('/item', status_code=201) async def create_item(body: CreateItemRequestBody): - new_item: Item = ItemsRepository.create_item(name=body.name, price=body.price) + new_item: Item = repository.create_item(name=body.name, price=body.price) return JSONResponse( content=jsonable_encoder(new_item), - headers={"location": f"/item/{new_item.id}"}, + headers={'location': f'/item/{new_item.id}'}, status_code=status.HTTP_201_CREATED, ) -@app.get("/item/{id}") +@app.get('/item/{id}') async def get_item(id: NonNegativeInt): try: - item: Item = ItemsRepository.get_item(id) + item: Item = repository.get_item(id) except ItemNotFoundException: - raise HTTPException(status_code=404, detail="Item not found") + raise HTTPException(status_code=404, detail='Item not found') + except Exception as e: + print("Unexpected internal error: ", e) + raise HTTPException(status_code=500, detail='Internal server error') return item -@app.get("/item") +@app.get('/item') async def get_items(offset: NonNegativeInt = 0, limit: PositiveInt = 10, min_price: Optional[NonNegativeFloat] = None, max_price: Optional[NonNegativeFloat] = None, show_deleted: bool = False): - items: List[Item] = ItemsRepository.get_items(offset, limit) + items: List[Item] = repository.get_items(offset, limit) result = [] for item in items: if min_price is not None and item.price < min_price: @@ -140,35 +146,41 @@ class ReplaceItemRequestBody(BaseModel): price: float -@app.put("/item/{id}") +@app.put('/item/{id}') async def replace_item(id: NonNegativeInt, body: ReplaceItemRequestBody): try: - item: Item = ItemsRepository.replace_item(item_id=id, name=body.name, price=body.price) + item: Item = repository.replace_item(item_id=id, name=body.name, price=body.price) except ItemNotFoundException: - raise HTTPException(status_code=404, detail="Item not found") + raise HTTPException(status_code=404, detail='Item not found') + except Exception as e: + print("Unexpected internal error: ", e) + raise HTTPException(status_code=500, detail='Internal server error') return item class UpdateItemRequestBody(BaseModel): - model_config = {"extra": "forbid"} + model_config = {'extra': 'forbid'} name: Optional[str] = None price: Optional[float] = None -@app.patch("/item/{id}") +@app.patch('/item/{id}') async def update_item(id: NonNegativeInt, body: UpdateItemRequestBody): try: - updated_item: Optional[Item] = ItemsRepository.update_item( + updated_item: Optional[Item] = repository.update_item( item_id=id, name=body.name, price=body.price) except ItemNotFoundException: - raise HTTPException(status_code=404, detail="Item not found") + raise HTTPException(status_code=404, detail='Item not found') + except Exception as e: + print("Unexpected internal error: ", e) + raise HTTPException(status_code=500, detail='Internal server error') if not updated_item: return Response(status_code=status.HTTP_304_NOT_MODIFIED) return updated_item -@app.delete("/item/{id}") +@app.delete('/item/{id}') async def delete_item(id: NonNegativeInt): - ItemsRepository.delete_item(item_id=id) + repository.delete_item(item_id=id) diff --git a/hw2/hw/shop_api/models.py b/hw2/hw/shop_api/models.py index 2185fe31..eef0ecc4 100644 --- a/hw2/hw/shop_api/models.py +++ b/hw2/hw/shop_api/models.py @@ -19,3 +19,9 @@ class Item(BaseModel): name: str price: float deleted: bool + +class CartNotFoundException(Exception): + pass + +class ItemNotFoundException(Exception): + pass diff --git a/hw2/hw/shop_api/postgres_repository.py b/hw2/hw/shop_api/postgres_repository.py new file mode 100644 index 00000000..aa094db9 --- /dev/null +++ b/hw2/hw/shop_api/postgres_repository.py @@ -0,0 +1,198 @@ +import os +from typing import List, Optional + +from sqlalchemy import create_engine, Engine, ForeignKey, select, String +from sqlalchemy.orm import DeclarativeBase, joinedload, Mapped, mapped_column, Session, relationship + +from shop_api import models + +class Base(DeclarativeBase): + pass + + +class Item(Base): + __tablename__ = "items" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(50)) + price: Mapped[float] = mapped_column(default=0) + deleted: Mapped[bool] = mapped_column(default=False) + + def __repr__(self) -> str: + return f"Item(id={self.id!r}, name={self.name!r}, price={self.price!r}, deleted={self.deleted!r})" + + +class CartItem(Base): + __tablename__ = "cart_items" + + id: Mapped[int] = mapped_column(primary_key=True) + item_id: Mapped[int] = mapped_column(ForeignKey("items.id")) + item: Mapped["Item"] = relationship() + quantity: Mapped[int] + cart_id: Mapped[int] = mapped_column(ForeignKey("carts.id")) + cart: Mapped["Cart"] = relationship(back_populates="items") + + def __repr__(self) -> str: + return f"CartItem(id={self.id!r}, item_id={self.item_id!r}, quantity={self.quantity!r})" + + +class Cart(Base): + __tablename__ = "carts" + + id: Mapped[int] = mapped_column(primary_key=True) + items: Mapped[List["CartItem"]] = relationship(back_populates="cart") + price: Mapped[float] + + def __repr__(self) -> str: + return f"Cart(id={self.id!r}, items=[{', '.join([f'{item.id!r}' for item in self.items])}], quanpricetity={self.price!r})" + + +def ItemToModel(item: Item) -> models.Item: + return models.Item( + id=item.id, + name=item.name, + price=item.price, + deleted=item.deleted + ) + + +def CartItemToModel(cart_item: CartItem) -> models.CartItem: + return models.CartItem( + id=cart_item.item.id, + name=cart_item.item.name, + quantity=cart_item.quantity, + available=not cart_item.item.deleted + ) + + +def CartToModel(cart: Cart) -> models.Cart: + return models.Cart( + id=cart.id, + items=[CartItemToModel(cart_item) for cart_item in cart.items], + price=cart.price + ) + + +class Repository: + engine: Engine + + def __init__(self): + postgres_address = os.environ['POSTGRES_ADDRESS'] + postgres_port = os.environ['POSTGRES_PORT'] + self.engine = create_engine(f'postgresql+psycopg2://user:password@{postgres_address}:{postgres_port}/shop_api') + Base.metadata.create_all(self.engine) + + def create_cart(self) -> models.Cart: + with Session(self.engine) as session: + cart = Cart(items=[], price=0.0) + session.add(cart) + session.commit() + + return CartToModel(cart) + + def get_cart(self, cart_id: int) -> models.Cart: + with Session(self.engine) as session: + stmt = select(Cart).options(joinedload(Cart.items)).where(Cart.id == cart_id) + cart = session.scalar(stmt) + + if cart is None: + raise models.CartNotFoundException() + return CartToModel(cart) + + def get_carts(self, offset: int, limit: int) -> List[models.Cart]: + with Session(self.engine) as session: + stmt = select(Cart).options(joinedload(Cart.items)).where(Cart.id > offset).limit(limit) + carts = session.scalars(stmt).unique() + + return [CartToModel(cart) for cart in carts] + + def add_item_to_cart(self, cart_id: int, item_id: int): + with Session(self.engine) as session: + stmt = ( + select(CartItem).options(joinedload(CartItem.cart), joinedload(CartItem.item)) + .where(CartItem.cart_id == cart_id) + .where(CartItem.item_id == item_id) + ) + cart_item = session.scalar(stmt) + if cart_item is not None: + cart_item.quantity += 1 + cart_item.cart.price += cart_item.item.price + session.commit() + return + + stmt = select(Cart).where(Cart.id == cart_id) + cart = session.scalar(stmt) + if cart is None: + raise models.CartNotFoundException() + + stmt = select(Item).where(Item.id == item_id) + item = session.scalar(stmt) + if item is None: + raise models.ItemNotFoundException() + + cart.items.append(CartItem(item_id=item_id, quantity=1, cart_id=cart_id)) + cart.price += item.price + session.commit() + + def create_item(self, name: str, price: float) -> models.Item: + with Session(self.engine) as session: + item = Item(name=name, price=price) + session.add(item) + session.commit() + + return ItemToModel(item) + + def _get_item(self, item_id: int, session: Session) -> Item: + stmt = select(Item).where(Item.id == item_id) + item = session.scalar(stmt) + + if item is None: + raise models.ItemNotFoundException() + return item + + def get_item(self, item_id: int) -> models.Item: + with Session(self.engine) as session: + item: Item = self._get_item(item_id, session) + if item.deleted: + raise models.ItemNotFoundException() + return ItemToModel(item) + + def get_items(self, offset: int, limit: int) -> List[models.Item]: + with Session(self.engine) as session: + stmt = select(Item).where(Item.id > offset).where(not Item.deleted).limit(limit) + items = session.scalars(stmt) + + return [ItemToModel(item) for item in items] + + def replace_item(self, item_id: int, name: str, price: float) -> models.Item: + with Session(self.engine) as session: + item: Item = self._get_item(item_id, session) + + item.name = name + item.price = price + item.deleted = False + + session.commit() + return ItemToModel(item) + + + def update_item(self, item_id: int, name: Optional[str], price: Optional[float]) -> Optional[Item]: + with Session(self.engine) as session: + item: Item = self._get_item(item_id, session) + if item.deleted: + return + + if name is not None: + item.name = name + if price is not None: + item.price = price + + session.commit() + return ItemToModel(item) + + def delete_item(self, item_id: int): + with Session(self.engine) as session: + item: Item = self._get_item(item_id, session) + if not item.deleted: + item.deleted = True + session.commit() diff --git a/hw2/hw/shop_api/repository.py b/hw2/hw/shop_api/repository.py deleted file mode 100644 index eb8622f5..00000000 --- a/hw2/hw/shop_api/repository.py +++ /dev/null @@ -1,82 +0,0 @@ -from typing import List, Optional - -from shop_api.models import CartItem, Cart, Item - - -carts: List[Cart] = [] - -class CartNotFoundException(Exception): - pass - -class CartsRepository: - def create_cart() -> Cart: - cart_id = len(carts) - cart = Cart(id=cart_id, items=[], price=0.0) - carts.append(cart) - return cart.model_copy(deep=True) - - def get_cart(cart_id: int) -> Cart: - if cart_id >= len(carts): - raise CartNotFoundException() - return carts[cart_id].model_copy(deep=True) - - def get_carts(offset: int, limit: int) -> List[Cart]: - return [cart.model_copy(deep=True) for cart in carts[offset:offset+limit]] - - def update_cart(new_cart: Cart): - if new_cart.id > len(carts): - raise CartNotFoundException() - carts[new_cart.id] = new_cart - - -items: List[Item] = [] - -class ItemNotFoundException(Exception): - pass - -class ItemsRepository: - def create_item(name: str, price: float) -> Item: - item_id = len(items) - item = Item(id=item_id, name=name, price=price, deleted=False) - items.append(item) - return item.model_copy(deep=True) - - def _get_item(item_id: int) -> Item: - if item_id >= len(items): - raise ItemNotFoundException() - return items[item_id] - - def get_item(item_id: int) -> Item: - item: Item = ItemsRepository._get_item(item_id) - if item.deleted: - raise ItemNotFoundException() - - return item.model_copy(deep=True) - - def get_items(offset: int, limit: int) -> List[Item]: - return [item.model_copy(deep=True) for item in items[offset:offset+limit]] - - def replace_item(item_id: int, name: str, price: float) -> Item: - if item_id >= len(items): - raise ItemNotFoundException() - - item = Item(id=item_id, name=name, price=price, deleted=False) - items[item_id] = item - return item.model_copy(deep=True) - - def update_item(item_id: int, name: Optional[str], price: Optional[float]) -> Optional[Item]: - item: Item = ItemsRepository._get_item(item_id) - - if item.deleted: - return None - - if name is not None: - item.name = name - if price is not None: - item.price = price - - return item.model_copy(deep=True) - - def delete_item(item_id: int): - item: Item = ItemsRepository._get_item(item_id) - item.deleted = True diff --git a/hw2/hw/test_homework2.py b/hw2/hw/test_homework2.py index 60a1f36a..e3a88a5b 100644 --- a/hw2/hw/test_homework2.py +++ b/hw2/hw/test_homework2.py @@ -37,7 +37,8 @@ def existing_not_empty_carts(existing_items: list[int]) -> list[int]: for i in range(20): cart_id: int = client.post("/cart").json()["id"] for item_id in faker.random_elements(existing_items, unique=False, length=i): - client.post(f"/cart/{cart_id}/add/{item_id}") + response = client.post(f"/cart/{cart_id}/add/{item_id}") + assert response.status_code == HTTPStatus.OK carts.append(cart_id) @@ -106,7 +107,9 @@ def test_get_cart(request, cart: int, not_empty: bool) -> None: for item in response_json["items"]: item_id = item["id"] - price += client.get(f"/item/{item_id}").json()["price"] * item["quantity"] + response = client.get(f"/item/{item_id}") + assert response.status_code == HTTPStatus.OK + price += response.json()["price"] * item["quantity"] assert response_json["price"] == pytest.approx(price, 1e-8) else: diff --git a/lecture4/hw/Dockerfile b/lecture4/hw/Dockerfile new file mode 100644 index 00000000..7c8ff183 --- /dev/null +++ b/lecture4/hw/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12 AS base + +ARG PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=on \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=500 + +RUN apt-get update && apt-get install -y gcc +RUN python -m pip install --upgrade pip + +WORKDIR $APP_ROOT/src +COPY . ./ + +ENV VIRTUAL_ENV=$APP_ROOT/src/.venv \ + PATH=$APP_ROOT/src/.venv/bin:$PATH + +RUN pip install -r requirements.txt + +FROM base as local + +CMD ["python", "demo.py"] diff --git a/lecture4/hw/demo.py b/lecture4/hw/demo.py new file mode 100644 index 00000000..8a63fd17 --- /dev/null +++ b/lecture4/hw/demo.py @@ -0,0 +1,264 @@ +import os +import time +import threading +from sqlalchemy import create_engine, select, String, text +from sqlalchemy.orm import Mapped, mapped_column, Session, DeclarativeBase +from sqlalchemy.pool import NullPool + +POSTGRES_USER = os.getenv("POSTGRES_USER", "user") +POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD", "password") +POSTGRES_DB = os.getenv("POSTGRES_DB", "shop_api") +POSTGRES_ADDRESS = os.getenv("POSTGRES_ADDRESS", "localhost") +POSTGRES_PORT = os.getenv("POSTGRES_PORT", "5432") + +DATABASE_URL = ( + f"postgresql+psycopg2://{POSTGRES_USER}:{POSTGRES_PASSWORD}" + f"@{POSTGRES_ADDRESS}:{POSTGRES_PORT}/{POSTGRES_DB}" +) + +class Base(DeclarativeBase): + pass + + +class Item(Base): + __tablename__ = "items" + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(50)) + price: Mapped[float] = mapped_column(default=0) + deleted: Mapped[bool] = mapped_column(default=False) + + def __repr__(self) -> str: + return f"Item(id={self.id!r}, name={self.name!r}, price={self.price!r})" + + +def create_engine_with_isolation(isolation_level: str): + return create_engine( + DATABASE_URL, + isolation_level=isolation_level, + ) + + +def init_database(): + engine = create_engine(DATABASE_URL) + Base.metadata.drop_all(engine) + Base.metadata.create_all(engine) + + with Session(engine) as session: + session.add(Item(id=1, name="Item1", price=1000)) + session.add(Item(id=2, name="Item2", price=2000)) + session.commit() + session.close() + + engine.dispose() + + print("База данных инициализирована\n") + + +def print_section(title: str): + print("=" * 80) + print(f" {title}") + print("=" * 80) + + +def print_subsection(title: str): + print(f"\n--- {title} ---") + + +# =========== +# Dirty Read +# =========== + +def demo_dirty_read(isolation_level): + print_subsection(f"Dirty Read при {isolation_level}") + init_database() + + engine = create_engine_with_isolation(isolation_level) + with Session(engine) as session1: + stmt = select(Item).where(Item.id == 1) + item = session1.scalar(stmt) + price1 = item.price + print(f"[T1] Исходная цена Item1: {item.price}") + + item.price = 5000 + session1.flush() + print(f"[T1] Изменили цену Item1 на {item.price} без коммита") + + session2 = Session(engine) + sesion2_item = session2.scalar(stmt) + print(f"[T2] Прочитали цену Item1: {sesion2_item.price}") + price2 = sesion2_item.price + + session1.rollback() + print("[T1] Откатили изменения") + + session2.expire_all() + sesion2_item = session2.scalar(stmt) + print(f"[T2] Прочитали цену Item1 после отката: {sesion2_item.price}") + + session2.close() + + if price1 != price2: + print(f"Dirty Read. Значение без изменений T1: {price1}, T2 прочитал: {price2}") + else: + print(f"Dirty Read не случился") + if isolation_level == "READ UNCOMMITTED": + print(f"PostgreSQL трактует READ UNCOMMITTED как READ COMMITTED") + + engine.dispose() + +def full_demo_dirty_read(): + print_section(f"Dirty Read") + demo_dirty_read("READ UNCOMMITTED") + demo_dirty_read("READ COMMITTED") + + +# ======================= +# 2. Non-Repeatable Read +# ======================= + +def demo_non_repeatable_read(isolation_level): + print_subsection(f"Non-Repeatable Read при {isolation_level}") + init_database() + + engine = create_engine_with_isolation(isolation_level) + with Session(engine) as session1: + stmt = select(Item).where(Item.id == 1) + item = session1.scalar(stmt) + price1 = item.price + print(f"[T1] Исходная цена Item1: {item.price}") + + with Session(engine) as session2: + sesion2_item = session2.scalar(stmt) + sesion2_item.price = 5000 + print(f"[T2] Изменили цену Item1 на {sesion2_item.price}") + session2.commit() + + session1.expire_all() + + item2 = session1.scalar(stmt) + price2 = item2.price + print(f"[T1] Повторно в той же транзации читаем цену Item1: {item2.price}") + + + if price1 != price2: + print(f"Non-Repeatable Read. Первое чтение T1: {price1}, второе чтение T1: {price2}") + else: + print(f"Non-Repeatable Read не случился") + + engine.dispose() + +def full_demo_non_repeatable_read(): + print_section(f"Non-Repeatable Read") + demo_non_repeatable_read("READ COMMITTED") + demo_non_repeatable_read("REPEATABLE READ") + +# ============= +# Phantom Read +# ============= + +def demo_phantom_reads(isolation_level): + print_subsection(f"Phantom Read при {isolation_level}") + init_database() + + engine = create_engine_with_isolation(isolation_level) + with Session(engine) as session1: + stmt = select(Item).where(Item.price > 1000) + items = session1.scalars(stmt).all() + count1 = len(items) + print(f"[T1] Первое чтение: найдено {count1} товаров") + for item in items: + print(f" {item}") + + with Session(engine) as session2: + new_item = Item(id=3, name="Item3", price=3000) + session2.add(new_item) + session2.commit() + print("[T2] Добавили новый товар Item3 и закоммитили") + + session1.expire_all() + + items = session1.scalars(stmt).all() + count2 = len(items) + print(f"[T1] Второе чтение в той же транзакции: найдено {count2} товаров") + for item in items: + print(f" {item}") + + if count1 != count2: + print(f"Phantom Read. Первое чтение: {count1}, второе: {count2}") + else: + print(f"Phantom Read не случился") + if isolation_level == 'REPEATABLE READ': + print(f"PostgreSQL не допускает PHANTOM READ при REPEATABLE READ") + + engine.dispose() + + +def full_demo_phantom_reads(): + print_section("Phantom Read") + demo_phantom_reads("READ COMMITTED") + demo_phantom_reads("REPEATABLE READ") + demo_phantom_reads("SERIALIZABLE") + +# ============= +# Serialization Anomaly +# ============= + +def demo_serialization_anomaly(isolation_level): + print_subsection(f"Serialization Anomaly при {isolation_level}") + init_database() + + engine = create_engine_with_isolation(isolation_level) + with Session(engine) as session1: + stmt = select(Item).where(Item.price > 1000) + items = session1.scalars(stmt).all() + count1 = len(items) + print(f"[T1] Первое чтение: найдено {count1} товаров") + for item in items: + print(f" {item}") + + new_item = Item(id=3, name="Item3", price=3000) + session1.add(new_item) + print("[T1] Добавили новый товар Item3, но не закоммитили") + + with Session(engine) as session2: + session2_items = session2.scalars(stmt).all() + print(f"[T2] Первое чтение: найдено {len(session2_items)} товаров") + for item in session2_items: + print(f" {item}") + + new_item = Item(id=4, name="Item4", price=4000) + session2.add(new_item) + session2.commit() + print("[T2] Добавили новый товар Item4 и закоммитили") + + try: + print("[T1] Пытаемся закоммитить.") + session1.commit() + print(f"Serialization Anomaly. Получилось закоммитить.") + except Exception as e: + print(f"Serialization Anomaly не случилось, транзакция упала с ошибкой: {e}") + + engine.dispose() + +def full_demo_serialization_anomaly(): + print_section("Serialization Anomaly") + demo_serialization_anomaly("REPEATABLE READ") + demo_serialization_anomaly("SERIALIZABLE") + + +def main(): + try: + full_demo_dirty_read() + full_demo_non_repeatable_read() + full_demo_phantom_reads() + full_demo_serialization_anomaly() + + except Exception as e: + print(f"\nПроизошла ошибка: {e}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/lecture4/hw/docker-compose.yml b/lecture4/hw/docker-compose.yml new file mode 100644 index 00000000..29de7cc7 --- /dev/null +++ b/lecture4/hw/docker-compose.yml @@ -0,0 +1,37 @@ +version: "3" + +services: + + local: + build: + context: . + dockerfile: ./Dockerfile + target: local + restart: no + ports: + - 8080:8080 + environment: + PYTHONPATH: . + POSTGRES_ADDRESS: postgres + POSTGRES_PORT: 5432 + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:latest + ports: + - "${POSTGRES_PORT}:5432" + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] + interval: 1s + timeout: 5s + retries: 10 diff --git a/lecture4/hw/requirements.txt b/lecture4/hw/requirements.txt new file mode 100644 index 00000000..783fdb00 --- /dev/null +++ b/lecture4/hw/requirements.txt @@ -0,0 +1,2 @@ +SQLAlchemy +psycopg2 From 2c2d4aae28244725b5312cc2aecdb2f03b8cd1a7 Mon Sep 17 00:00:00 2001 From: Kazantsev Daniil Date: Sun, 26 Oct 2025 21:55:05 +0300 Subject: [PATCH 5/5] hw5 --- .github/workflows/hw2-tests-custom.yml | 69 ++++++++++++++++++++++++++ hw2/hw/DockerfilePytest | 4 +- hw2/hw/docker-compose-pytest.yml | 3 +- hw2/hw/docker-compose.yml | 2 + hw2/hw/requirements.txt | 1 + hw2/hw/shop_api/postgres_repository.py | 4 +- hw2/hw/test_homework2.py | 48 +++++++++++++++--- 7 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/hw2-tests-custom.yml diff --git a/.github/workflows/hw2-tests-custom.yml b/.github/workflows/hw2-tests-custom.yml new file mode 100644 index 00000000..2cee0559 --- /dev/null +++ b/.github/workflows/hw2-tests-custom.yml @@ -0,0 +1,69 @@ +name: "HW2 Tests Custom (with Postgres)" + +# Запускаем тесты при изменении файлов в hw2/hw/ +on: + pull_request: + branches: [ main ] + paths: [ 'hw2/hw/**' ] + push: + branches: [ main ] + paths: [ 'hw2/hw/**' ] + +jobs: + test-hw2-custom: + runs-on: ubuntu-latest + defaults: + run: + working-directory: hw2/hw + + strategy: + matrix: + python-version: ["3.12", "3.13"] + + services: + postgres: + image: postgres:latest + env: + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: shop_api + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests with in-memory database + env: + PYTHONPATH: ${{ github.workspace }}/hw2/hw + SHOP_API_DB_TYPE: in-memory + run: | + pytest test_homework2.py -v + + - name: Run tests with Postgres database + env: + PYTHONPATH: ${{ github.workspace }}/hw2/hw + SHOP_API_DB_TYPE: postgres + POSTGRES_ADDRESS: localhost + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: shop_api + POSTGRES_PORT: 5432 + run: | + pytest test_homework2.py -v diff --git a/hw2/hw/DockerfilePytest b/hw2/hw/DockerfilePytest index 3c9f591a..f2416f90 100644 --- a/hw2/hw/DockerfilePytest +++ b/hw2/hw/DockerfilePytest @@ -20,4 +20,6 @@ RUN pip install -r requirements.txt FROM base as local -CMD ["pytest", "-vv", "--showlocals", "--strict", "./test_homework2.py"] +CMD ["bash", "-c", "\ + SHOP_API_DB_TYPE=in-memory pytest -vv --cov=. --cov-report= --showlocals --strict ./test_homework2.py && \ + SHOP_API_DB_TYPE=postgres pytest -vv --cov=. --cov-append --cov-report=term-missing --showlocals --strict ./test_homework2.py"] diff --git a/hw2/hw/docker-compose-pytest.yml b/hw2/hw/docker-compose-pytest.yml index 762f15af..c9fc61b5 100644 --- a/hw2/hw/docker-compose-pytest.yml +++ b/hw2/hw/docker-compose-pytest.yml @@ -12,9 +12,10 @@ services: - 8080:8080 environment: PYTHONPATH: . - SHOP_API_DB_TYPE: postgres POSTGRES_ADDRESS: postgres POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} depends_on: postgres: condition: service_healthy diff --git a/hw2/hw/docker-compose.yml b/hw2/hw/docker-compose.yml index 453bff47..b033addb 100644 --- a/hw2/hw/docker-compose.yml +++ b/hw2/hw/docker-compose.yml @@ -15,6 +15,8 @@ services: SHOP_API_DB_TYPE: postgres POSTGRES_ADDRESS: postgres POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} depends_on: postgres: condition: service_healthy diff --git a/hw2/hw/requirements.txt b/hw2/hw/requirements.txt index cc1cb98d..50f4de57 100644 --- a/hw2/hw/requirements.txt +++ b/hw2/hw/requirements.txt @@ -9,5 +9,6 @@ psycopg2 # Зависимости для тестирования pytest>=7.4.0 pytest-asyncio>=0.21.0 +pytest-cov httpx>=0.27.2 Faker>=37.8.0 diff --git a/hw2/hw/shop_api/postgres_repository.py b/hw2/hw/shop_api/postgres_repository.py index aa094db9..dc7da7d4 100644 --- a/hw2/hw/shop_api/postgres_repository.py +++ b/hw2/hw/shop_api/postgres_repository.py @@ -77,9 +77,11 @@ class Repository: engine: Engine def __init__(self): + postgres_user = os.environ['POSTGRES_USER'] + postgres_password = os.environ['POSTGRES_PASSWORD'] postgres_address = os.environ['POSTGRES_ADDRESS'] postgres_port = os.environ['POSTGRES_PORT'] - self.engine = create_engine(f'postgresql+psycopg2://user:password@{postgres_address}:{postgres_port}/shop_api') + self.engine = create_engine(f'postgresql+psycopg2://{postgres_user}:{postgres_password}@{postgres_address}:{postgres_port}/shop_api') Base.metadata.create_all(self.engine) def create_cart(self) -> models.Cart: diff --git a/hw2/hw/test_homework2.py b/hw2/hw/test_homework2.py index e3a88a5b..52dd7fb0 100644 --- a/hw2/hw/test_homework2.py +++ b/hw2/hw/test_homework2.py @@ -76,6 +76,11 @@ def deleted_item(existing_item: dict[str, Any]) -> dict[str, Any]: return existing_item +@pytest.fixture() +def non_existing_item() -> dict[str, Any]: + return {"id": 100500, "name": "non existing", "price": 0, "deleted": False} + + def test_post_cart() -> None: response = client.post("/cart") @@ -116,6 +121,11 @@ def test_get_cart(request, cart: int, not_empty: bool) -> None: assert response_json["price"] == 0.0 +def test_non_existing_get_cart() -> None: + response = client.get(f"/cart/100500") + assert response.status_code == HTTPStatus.NOT_FOUND + + @pytest.mark.parametrize( ("query", "status_code"), [ @@ -159,6 +169,28 @@ def test_get_cart_list(query: dict[str, Any], status_code: int): assert quantity <= query["max_quantity"] +@pytest.mark.parametrize( + ("use_existing_cart", "use_existing_item", "status_code"), + [ + (True, True, HTTPStatus.OK), + (False, True, HTTPStatus.NOT_FOUND), + (True, False, HTTPStatus.NOT_FOUND), + ], +) +def test_add_item_to_cart( + existing_empty_cart_id: int, + existing_item: dict[str, Any], + use_existing_cart: bool, + use_existing_item: bool, + status_code: int +): + cart_id = existing_empty_cart_id if use_existing_cart else 100500 + item_id = existing_item["id"] if use_existing_item else 100500 + response = client.post(f"/cart/{cart_id}/add/{item_id}") + + assert response.status_code == status_code + + def test_post_item() -> None: item = {"name": "test item", "price": 9.99} response = client.post("/item", json=item) @@ -214,19 +246,22 @@ def test_get_item_list(query: dict[str, Any], status_code: int) -> None: @pytest.mark.parametrize( - ("body", "status_code"), + ("item", "body", "status_code"), [ - ({}, HTTPStatus.UNPROCESSABLE_ENTITY), - ({"price": 9.99}, HTTPStatus.UNPROCESSABLE_ENTITY), - ({"name": "new name", "price": 9.99}, HTTPStatus.OK), + ("existing_item", {}, HTTPStatus.UNPROCESSABLE_ENTITY), + ("existing_item", {"price": 9.99}, HTTPStatus.UNPROCESSABLE_ENTITY), + ("existing_item", {"name": "new name", "price": 9.99}, HTTPStatus.OK), + ("non_existing_item", {"name": "new name", "price": 9.99}, HTTPStatus.NOT_FOUND), ], ) def test_put_item( - existing_item: dict[str, Any], + request, + existing_item, + item, body: dict[str, Any], status_code: int, ) -> None: - item_id = existing_item["id"] + item_id = request.getfixturevalue(item)["id"] response = client.put(f"/item/{item_id}", json=body) assert response.status_code == status_code @@ -256,6 +291,7 @@ def test_put_item( {"name": "new name", "price": 9.99, "deleted": True}, HTTPStatus.UNPROCESSABLE_ENTITY, ), + ("non_existing_item", {"name": "new name", "price": 9.99}, HTTPStatus.NOT_FOUND), ], ) def test_patch_item(request, item: str, body: dict[str, Any], status_code: int) -> None: