From 06ff55cb0c764e88ebc2196a711fa2fceb7c25c0 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 13 Oct 2025 02:26:04 +0300 Subject: [PATCH 01/13] Integrate prometheus and grafana to application --- hw3/Dockerfile | 23 ++++ hw3/README.md | 15 +++ hw3/docker-compose.yml | 42 +++++++ hw3/hw2/rest_example/api/__init__.py | 0 hw3/hw2/rest_example/api/pokemon/__init__.py | 9 ++ hw3/hw2/rest_example/api/pokemon/contracts.py | 41 ++++++ hw3/hw2/rest_example/api/pokemon/routes.py | 119 ++++++++++++++++++ hw3/hw2/rest_example/main.py | 13 ++ hw3/hw2/rest_example/store/__init__.py | 15 +++ hw3/hw2/rest_example/store/models.py | 19 +++ hw3/hw2/rest_example/store/queries.py | 75 +++++++++++ hw3/pics/grafana.png | Bin 0 -> 139866 bytes hw3/prometheus.yml | 9 ++ hw3/requirements.txt | 3 + 14 files changed, 383 insertions(+) create mode 100644 hw3/Dockerfile create mode 100644 hw3/README.md create mode 100644 hw3/docker-compose.yml create mode 100644 hw3/hw2/rest_example/api/__init__.py create mode 100644 hw3/hw2/rest_example/api/pokemon/__init__.py create mode 100644 hw3/hw2/rest_example/api/pokemon/contracts.py create mode 100644 hw3/hw2/rest_example/api/pokemon/routes.py create mode 100644 hw3/hw2/rest_example/main.py create mode 100644 hw3/hw2/rest_example/store/__init__.py create mode 100644 hw3/hw2/rest_example/store/models.py create mode 100644 hw3/hw2/rest_example/store/queries.py create mode 100644 hw3/pics/grafana.png create mode 100644 hw3/prometheus.yml create mode 100644 hw3/requirements.txt diff --git a/hw3/Dockerfile b/hw3/Dockerfile new file mode 100644 index 00000000..62129162 --- /dev/null +++ b/hw3/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12 + +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 + +COPY requirements.txt . + +RUN pip install -r requirements.txt + +COPY ./hw2 /app/hw2 + +EXPOSE 8000 + +CMD ["uvicorn", "hw2.rest_example.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/hw3/README.md b/hw3/README.md new file mode 100644 index 00000000..237b9aca --- /dev/null +++ b/hw3/README.md @@ -0,0 +1,15 @@ +# ДЗ 3 + +Я пока не выполнил дз 2, поэтому взял указанный пример. + +Добавленные файлы: + +- *Dockerfile* +- *docker-compose.yml* +- *prometheus.yml* + +Обновил код в файле *hw2/rest_example/main.py* + +Скриншот из Grafana (*pics/grafana.png*) + +![Dashboard](pics/grafana.png) \ No newline at end of file diff --git a/hw3/docker-compose.yml b/hw3/docker-compose.yml new file mode 100644 index 00000000..f9e18d9e --- /dev/null +++ b/hw3/docker-compose.yml @@ -0,0 +1,42 @@ +version: "3" + +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: pokemon_api + ports: + - "8000:8000" + networks: + - monitoring-net + + prometheus: + image: prom/prometheus:latest + container_name: prometheus_server + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + 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" + restart: always + networks: + - monitoring-net + + grafana: + image: grafana/grafana-oss:latest + container_name: grafana_dashboard + ports: + - "3000:3000" + depends_on: + - prometheus + networks: + - monitoring-net + +networks: + monitoring-net: + driver: bridge \ No newline at end of file diff --git a/hw3/hw2/rest_example/api/__init__.py b/hw3/hw2/rest_example/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw3/hw2/rest_example/api/pokemon/__init__.py b/hw3/hw2/rest_example/api/pokemon/__init__.py new file mode 100644 index 00000000..ccd625f1 --- /dev/null +++ b/hw3/hw2/rest_example/api/pokemon/__init__.py @@ -0,0 +1,9 @@ +from .contracts import PatchPokemonRequest, PokemonRequest, PokemonResponse +from .routes import router + +__all__ = [ + "PokemonResponse", + "PokemonRequest", + "PatchPokemonRequest", + "router", +] diff --git a/hw3/hw2/rest_example/api/pokemon/contracts.py b/hw3/hw2/rest_example/api/pokemon/contracts.py new file mode 100644 index 00000000..a985b15b --- /dev/null +++ b/hw3/hw2/rest_example/api/pokemon/contracts.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from hw2.rest_example.store.models import ( + PatchPokemonInfo, + PokemonEntity, + PokemonInfo, +) + + +class PokemonResponse(BaseModel): + id: int + name: str + published: bool + + @staticmethod + def from_entity(entity: PokemonEntity) -> PokemonResponse: + return PokemonResponse( + id=entity.id, + name=entity.info.name, + published=entity.info.published, + ) + + +class PokemonRequest(BaseModel): + name: str + published: bool + + def as_pokemon_info(self) -> PokemonInfo: + return PokemonInfo(name=self.name, published=self.published) + + +class PatchPokemonRequest(BaseModel): + name: str | None = None + published: bool | None = None + + model_config = ConfigDict(extra="forbid") + + def as_patch_pokemon_info(self) -> PatchPokemonInfo: + return PatchPokemonInfo(name=self.name, published=self.published) diff --git a/hw3/hw2/rest_example/api/pokemon/routes.py b/hw3/hw2/rest_example/api/pokemon/routes.py new file mode 100644 index 00000000..ab935c9a --- /dev/null +++ b/hw3/hw2/rest_example/api/pokemon/routes.py @@ -0,0 +1,119 @@ +from http import HTTPStatus +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query, Response +from pydantic import NonNegativeInt, PositiveInt + +from hw2.rest_example import store + +from .contracts import ( + PatchPokemonRequest, + PokemonRequest, + PokemonResponse, +) + +router = APIRouter(prefix="/pokemon") + + +@router.get("/") +async def get_pokemon_list( + offset: Annotated[NonNegativeInt, Query()] = 0, + limit: Annotated[PositiveInt, Query()] = 10, +) -> list[PokemonResponse]: + return [PokemonResponse.from_entity(e) for e in store.get_many(offset, limit)] + + +@router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested pokemon", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested pokemon as one was not found", + }, + }, +) +async def get_pokemon_by_id(id: int) -> PokemonResponse: + entity = store.get_one(id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /pokemon/{id} was not found", + ) + + return PokemonResponse.from_entity(entity) + + +@router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_pokemon(info: PokemonRequest, response: Response) -> PokemonResponse: + entity = store.add(info.as_pokemon_info()) + + # as REST states one should provide uri to newly created resource in location header + response.headers["location"] = f"/pokemon/{entity.id}" + + return PokemonResponse.from_entity(entity) + + +@router.patch( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully patched pokemon", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify pokemon as one was not found", + }, + }, +) +async def patch_pokemon(id: int, info: PatchPokemonRequest) -> PokemonResponse: + entity = store.patch(id, info.as_patch_pokemon_info()) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /pokemon/{id} was not found", + ) + + return PokemonResponse.from_entity(entity) + + +@router.put( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully updated or upserted pokemon", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify pokemon as one was not found", + }, + } +) +async def put_pokemon( + id: int, + info: PokemonRequest, + upsert: Annotated[bool, Query()] = False, +) -> PokemonResponse: + entity = ( + store.upsert(id, info.as_pokemon_info()) + if upsert + else store.update(id, info.as_pokemon_info()) + ) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /pokemon/{id} was not found", + ) + + return PokemonResponse.from_entity(entity) + + +@router.delete("/{id}") +async def delete_pokemon(id: int) -> Response: + store.delete(id) + return Response("") diff --git a/hw3/hw2/rest_example/main.py b/hw3/hw2/rest_example/main.py new file mode 100644 index 00000000..90ab1832 --- /dev/null +++ b/hw3/hw2/rest_example/main.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI +from prometheus_fastapi_instrumentator import Instrumentator + +from hw2.rest_example.api.pokemon import router + +app = FastAPI(title="Pokemon REST API Example") + +app.include_router(router) + +instrumentator = Instrumentator(excluded_handlers=["/metrics"]) + +instrumentator.instrument(app) +instrumentator.expose(app, include_in_schema=True, should_gzip=True) \ No newline at end of file diff --git a/hw3/hw2/rest_example/store/__init__.py b/hw3/hw2/rest_example/store/__init__.py new file mode 100644 index 00000000..cb99d02a --- /dev/null +++ b/hw3/hw2/rest_example/store/__init__.py @@ -0,0 +1,15 @@ +from .models import PatchPokemonInfo, PokemonEntity, PokemonInfo +from .queries import add, delete, get_many, get_one, patch, update, upsert + +__all__ = [ + "PokemonEntity", + "PokemonInfo", + "PatchPokemonInfo", + "add", + "delete", + "get_many", + "get_one", + "update", + "upsert", + "patch", +] diff --git a/hw3/hw2/rest_example/store/models.py b/hw3/hw2/rest_example/store/models.py new file mode 100644 index 00000000..95cd40b9 --- /dev/null +++ b/hw3/hw2/rest_example/store/models.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass + + +@dataclass(slots=True) +class PokemonInfo: + name: str + published: bool + + +@dataclass(slots=True) +class PokemonEntity: + id: int + info: PokemonInfo + + +@dataclass(slots=True) +class PatchPokemonInfo: + name: str | None = None + published: bool | None = None diff --git a/hw3/hw2/rest_example/store/queries.py b/hw3/hw2/rest_example/store/queries.py new file mode 100644 index 00000000..959492d7 --- /dev/null +++ b/hw3/hw2/rest_example/store/queries.py @@ -0,0 +1,75 @@ +from typing import Iterable + +from hw2.rest_example.store.models import ( + PatchPokemonInfo, + PokemonEntity, + PokemonInfo, +) + +_data = dict[int, PokemonInfo]() + + +def int_id_generator() -> Iterable[int]: + i = 0 + while True: + yield i + i += 1 + + +_id_generator = int_id_generator() + + +def add(info: PokemonInfo) -> PokemonEntity: + _id = next(_id_generator) + _data[_id] = info + + return PokemonEntity(_id, info) + + +def delete(id: int) -> None: + if id in _data: + del _data[id] + + +def get_one(id: int) -> PokemonEntity | None: + if id not in _data: + return None + + return PokemonEntity(id=id, info=_data[id]) + + +def get_many(offset: int = 0, limit: int = 10) -> Iterable[PokemonEntity]: + curr = 0 + for id, info in _data.items(): + if offset <= curr < offset + limit: + yield PokemonEntity(id, info) + + curr += 1 + + +def update(id: int, info: PokemonInfo) -> PokemonEntity | None: + if id not in _data: + return None + + _data[id] = info + + return PokemonEntity(id=id, info=info) + + +def upsert(id: int, info: PokemonInfo) -> PokemonEntity: + _data[id] = info + + return PokemonEntity(id=id, info=info) + + +def patch(id: int, patch_info: PatchPokemonInfo) -> PokemonEntity | None: + if id not in _data: + return None + + if patch_info.name is not None: + _data[id].name = patch_info.name + + if patch_info.published is not None: + _data[id].published = patch_info.published + + return PokemonEntity(id=id, info=_data[id]) diff --git a/hw3/pics/grafana.png b/hw3/pics/grafana.png new file mode 100644 index 0000000000000000000000000000000000000000..4f0439b6792b49d904e15b2e31ebd92fe6ded10c GIT binary patch literal 139866 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�**rahPG7=oU5qT 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 f@V{KcE4v4NE!r=0.117.1 +uvicorn +prometheus-fastapi-instrumentator \ No newline at end of file From 9faa9a2be16a49cc4739b567647cafd12f89100c Mon Sep 17 00:00:00 2001 From: Deuqz Date: Fri, 24 Oct 2025 10:20:46 +0300 Subject: [PATCH 02/13] Did hw1 --- hw1/app.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/hw1/app.py b/hw1/app.py index 6107b870..3332fe46 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -1,4 +1,27 @@ from typing import Any, Awaitable, Callable +import json +import math + + +def fibonacchi(n): + if n < 0: + raise Exception() + prev, ans = 0, 1 + for _ in range(n): + prev, ans = ans, ans + prev + return ans + + +def factorial(n): + if n < 0: + raise Exception() + return math.factorial(n) + + +def mean(arr): + if len(arr) == 0: + raise Exception() + return sum(arr) / len(arr) async def application( @@ -12,7 +35,53 @@ async def application( receive: Корутина для получения сообщений от клиента send: Корутина для отправки сообщений клиенту """ - # TODO: Ваша реализация здесь + if 'path' in scope and scope['method'] == 'GET': + splitted_path = scope['path'].split('/') + else: + splitted_path = [None, None] + body = {"result": None} + status = 200 + function = None + try: + if splitted_path[1] == 'fibonacci': + val = splitted_path[2] + assert '.' not in val + param = int(val) + function = fibonacchi + elif splitted_path[1] == 'factorial': + key, val = scope['query_string'].decode('utf-8').split('=') + assert key == 'n' + assert '.' not in val + param = int(val) + function = factorial + elif splitted_path[1] == 'mean': + if scope['query_string'] != b'': + key, val = scope['query_string'].decode('utf-8').split('=') + assert key == 'numbers' + else: + val = await receive() + val = val['body'].decode('utf-8') + assert val[0] == '[' and val[-1] == ']' + val = val[1:-1] + param = [] + if val != '': + for num in val.split(','): + param.append(float(num)) + function = mean + else: + status = 404 + except: + status = 422 + if function is not None: + try: + body = {"result": function(param)} + except: + status = 400 + await send({'type': 'http.response.start', + 'status': status, + 'headers': [[b'content-type', b'application/json']]}) + await send({'type': 'http.response.body', 'body': json.dumps(body).encode('utf-8')}) + if __name__ == "__main__": import uvicorn From 3dec26423becb5981c622c82777c5a1d2a59f5b6 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Fri, 24 Oct 2025 10:22:06 +0300 Subject: [PATCH 03/13] Did hw2 --- hw2/__init__.py | 0 hw2/hw/shop_api/api/__init__.py | 0 hw2/hw/shop_api/api/cart/__init__.py | 1 + hw2/hw/shop_api/api/cart/contracts.py | 33 +++++++ hw2/hw/shop_api/api/cart/routes.py | 72 ++++++++++++++ hw2/hw/shop_api/api/item/__init__.py | 1 + hw2/hw/shop_api/api/item/contracts.py | 44 +++++++++ hw2/hw/shop_api/api/item/routes.py | 114 ++++++++++++++++++++++ hw2/hw/shop_api/main.py | 5 + hw2/hw/shop_api/store/__init__.py | 22 +++++ hw2/hw/shop_api/store/models.py | 45 +++++++++ hw2/hw/shop_api/store/queries.py | 133 ++++++++++++++++++++++++++ 12 files changed, 470 insertions(+) create mode 100644 hw2/__init__.py create mode 100644 hw2/hw/shop_api/api/__init__.py create mode 100644 hw2/hw/shop_api/api/cart/__init__.py create mode 100644 hw2/hw/shop_api/api/cart/contracts.py create mode 100644 hw2/hw/shop_api/api/cart/routes.py create mode 100644 hw2/hw/shop_api/api/item/__init__.py create mode 100644 hw2/hw/shop_api/api/item/contracts.py create mode 100644 hw2/hw/shop_api/api/item/routes.py create mode 100644 hw2/hw/shop_api/store/__init__.py create mode 100644 hw2/hw/shop_api/store/models.py create mode 100644 hw2/hw/shop_api/store/queries.py diff --git a/hw2/__init__.py b/hw2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/shop_api/api/__init__.py b/hw2/hw/shop_api/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/shop_api/api/cart/__init__.py b/hw2/hw/shop_api/api/cart/__init__.py new file mode 100644 index 00000000..a81cce79 --- /dev/null +++ b/hw2/hw/shop_api/api/cart/__init__.py @@ -0,0 +1 @@ +from .routes import router \ No newline at end of file diff --git a/hw2/hw/shop_api/api/cart/contracts.py b/hw2/hw/shop_api/api/cart/contracts.py new file mode 100644 index 00000000..cb38b17b --- /dev/null +++ b/hw2/hw/shop_api/api/cart/contracts.py @@ -0,0 +1,33 @@ +from typing import List + +from pydantic import BaseModel + +from hw2.hw.shop_api.store.models import ( + CartEntity, + AddItemInfo +) + + +class ItemCartResponse(BaseModel): + id: int + name: str + quantity: int + available: bool + + +class CartResponse(BaseModel): + id: int + items: List[ItemCartResponse] + price: float + + @staticmethod + def from_entity(entity: CartEntity): + return CartResponse( + id=entity.id, + items=[ItemCartResponse(id=item_id, + name=info.name, + quantity=info.quantity, + available=info.available) + for item_id, info in entity.info.items.items()], + price=entity.info.price + ) diff --git a/hw2/hw/shop_api/api/cart/routes.py b/hw2/hw/shop_api/api/cart/routes.py new file mode 100644 index 00000000..c4fedc6f --- /dev/null +++ b/hw2/hw/shop_api/api/cart/routes.py @@ -0,0 +1,72 @@ +from http import HTTPStatus +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query, Response, Request +from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat + +from hw2.hw.shop_api import store + +from .contracts import CartResponse + +router = APIRouter(prefix="/cart") + + +@router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_cart(_: Request, response: Response) -> CartResponse: + entity = store.add_cart() + + # as REST states one should provide uri to newly created resource in location header + response.headers["location"] = f"/cart/{entity.id}" + + return CartResponse.from_entity(entity) + + +@router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested cart", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested cart as one was not found", + }, + }, +) +async def get_cart_by_id(id: int) -> CartResponse: + entity = store.get_one_cart(id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /cart/{id} was not found", + ) + + return CartResponse.from_entity(entity) + + +@router.get("/") +async def get_cart_list( + offset: Annotated[NonNegativeInt, Query()] = 0, + limit: Annotated[PositiveInt, Query()] = 10, + min_price: Annotated[NonNegativeFloat, Query()] = 0, + max_price: Annotated[NonNegativeFloat, Query()] = 1e10, + min_quantity: Annotated[NonNegativeInt, Query()] = 0, + max_quantity: Annotated[NonNegativeInt, Query()] = 1e10, +) -> list[CartResponse]: + return [CartResponse.from_entity(e) for e in store.get_many_carts(offset, limit, min_price, max_price, min_quantity, max_quantity)] + + +@router.post( + "/{cart_id}/add/{item_id}", + status_code=HTTPStatus.CREATED, +) +async def post_item_to_cart(cart_id: int, item_id: int, response: Response) -> CartResponse: + entity = store.add_item_to_cart(cart_id, item_id) + + # as REST states one should provide uri to newly created resource in location header + response.headers["location"] = f"/cart/{entity.id}" + + return CartResponse.from_entity(entity) diff --git a/hw2/hw/shop_api/api/item/__init__.py b/hw2/hw/shop_api/api/item/__init__.py new file mode 100644 index 00000000..a81cce79 --- /dev/null +++ b/hw2/hw/shop_api/api/item/__init__.py @@ -0,0 +1 @@ +from .routes import router \ No newline at end of file diff --git a/hw2/hw/shop_api/api/item/contracts.py b/hw2/hw/shop_api/api/item/contracts.py new file mode 100644 index 00000000..4faee679 --- /dev/null +++ b/hw2/hw/shop_api/api/item/contracts.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + +from hw2.hw.shop_api.store.models import ( + ItemInfo, + ItemEntity, + PatchItemInfo +) + + +class ItemResponse(BaseModel): + id: int + name: str + price: float + deleted: bool + + @staticmethod + def from_entity(entity: ItemEntity) -> ItemResponse: + return ItemResponse( + id=entity.id, + name=entity.info.name, + price=entity.info.price, + deleted=entity.info.deleted + ) + + +class ItemRequest(BaseModel): + name: str + price: float + deleted: bool = False + + def as_item_info(self) -> ItemInfo: + return ItemInfo(name=self.name, price=self.price, deleted=self.deleted) + + +class PatchItemRequest(BaseModel): + name: str | None = None + price: float | None = None + + model_config = ConfigDict(extra="forbid") + + def as_patch_item_info(self) -> PatchItemInfo: + return PatchItemInfo(name=self.name, price=self.price) diff --git a/hw2/hw/shop_api/api/item/routes.py b/hw2/hw/shop_api/api/item/routes.py new file mode 100644 index 00000000..c255df58 --- /dev/null +++ b/hw2/hw/shop_api/api/item/routes.py @@ -0,0 +1,114 @@ +from http import HTTPStatus +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query, Response +from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat, StrictBool + +from hw2.hw.shop_api import store + +from .contracts import ItemRequest, ItemResponse, PatchItemRequest + + +router = APIRouter(prefix="/item") + + +@router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_item(info: ItemRequest, response: Response) -> ItemResponse: + entity = store.add_item(info.as_item_info()) + + # as REST states one should provide uri to newly created resource in location header + response.headers["location"] = f"/item/{entity.id}" + + return ItemResponse.from_entity(entity) + + +@router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested item", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested item as one was not found", + }, + }, +) +async def get_item_by_id(id: int) -> ItemResponse: + entity = store.get_one_item(id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /item/{id} was not found", + ) + + return ItemResponse.from_entity(entity) + + +@router.get("/") +async def get_item_list( + offset: Annotated[NonNegativeInt, Query()] = 0, + limit: Annotated[PositiveInt, Query()] = 10, + min_price: Annotated[NonNegativeFloat, Query()] = 0, + max_price: Annotated[NonNegativeFloat, Query()] = 1e10, + show_deleted: Annotated[bool, Query()] = False +) -> list[ItemResponse]: + return [ItemResponse.from_entity(e) for e in store.get_many_items(offset, limit, min_price, max_price, show_deleted)] + + +@router.put( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully updated item", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify item as one was not found", + }, + } +) +async def put_item( + id: int, + info: ItemRequest +) -> ItemResponse: + entity = store.update_item(id, info.as_item_info()) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + return ItemResponse.from_entity(entity) + + +@router.patch( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully patched item", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify item as one was not found", + }, + }, +) +async def patch_item(id: int, info: PatchItemRequest) -> ItemResponse: + entity = store.patch_item(id, info.as_patch_item_info()) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + return ItemResponse.from_entity(entity) + + +@router.delete("/{id}") +async def delete_item(id: int) -> Response: + store.delete_item(id) + return Response("") diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index f60a8c60..0a71edf8 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,3 +1,8 @@ from fastapi import FastAPI +from hw2.hw.shop_api.api import cart, item + app = FastAPI(title="Shop API") + +app.include_router(cart.router) +app.include_router(item.router) diff --git a/hw2/hw/shop_api/store/__init__.py b/hw2/hw/shop_api/store/__init__.py new file mode 100644 index 00000000..448a069f --- /dev/null +++ b/hw2/hw/shop_api/store/__init__.py @@ -0,0 +1,22 @@ +from .models import ItemInfo, ItemEntity, PatchItemInfo, CartInfo, CartEntity, AddItemInfo +from .queries import (add_item, delete_item, get_one_item, get_many_items, update_item, patch_item, + add_cart, get_one_cart, get_many_carts, add_item_to_cart) + +__all__ = [ + "ItemInfo", + "ItemEntity", + "PatchItemInfo", + "CartInfo", + "CartEntity", + "AddItemInfo", + "add_item", + "delete_item", + "get_one_item", + "get_many_items", + "update_item", + "patch_item", + "add_cart", + "get_one_cart", + "get_many_carts", + "add_item_to_cart" +] \ No newline at end of file diff --git a/hw2/hw/shop_api/store/models.py b/hw2/hw/shop_api/store/models.py new file mode 100644 index 00000000..c43cc7a6 --- /dev/null +++ b/hw2/hw/shop_api/store/models.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from typing import Dict + + +@dataclass(slots=True) +class ItemInfo: + name: str + price: float + deleted: bool + + +@dataclass(slots=True) +class ItemEntity: + id: int + info: ItemInfo + + +@dataclass(slots=True) +class PatchItemInfo: + name: str | None = None + price: float | None = None + + +@dataclass(slots=True) +class ItemCartInfo: + name: str + quantity: int + available: bool + + +@dataclass(slots=True) +class CartInfo: + items: Dict[int, ItemCartInfo] + price: float + + +@dataclass(slots=True) +class CartEntity: + id: int + info: CartInfo + + +@dataclass(slots=True) +class AddItemInfo: + item_id: int diff --git a/hw2/hw/shop_api/store/queries.py b/hw2/hw/shop_api/store/queries.py new file mode 100644 index 00000000..94e7bd4a --- /dev/null +++ b/hw2/hw/shop_api/store/queries.py @@ -0,0 +1,133 @@ +from typing import Iterable + +from hw2.hw.shop_api.store.models import * + + +_data_items = dict[int, ItemInfo]() +_data_carts = dict[int, CartInfo]() + + +def int_id_generator() -> Iterable[int]: + i = 0 + while True: + yield i + i += 1 + + +_id_generator = int_id_generator() + + +def add_item(info: ItemInfo) -> ItemEntity: + _id = next(_id_generator) + _data_items[_id] = info + + return ItemEntity(_id, info) + + +def delete_item(id: int) -> None: + if id in _data_items: + if _data_items[id].deleted: + return + _data_items[id].deleted = True + else: + return + for cart_id, cart_info in _data_carts.items(): + if id in cart_info.items: + cart_info.items[id].available = False + cart_info.price -= _data_items[id].price * cart_info.items[id].quantity + + +def get_one_item(id: int) -> ItemEntity | None: + if id not in _data_items or _data_items[id].deleted: + return None + + return ItemEntity(id, _data_items[id]) + + +def get_many_items(offset: int = 0, + limit: int = 10, + min_price: float = 0, + max_price: float = 1e10, + show_deleted: bool = False) -> Iterable[ItemEntity]: + curr = 0 + for id, info in _data_items.items(): + if offset <= curr < curr + limit \ + and min_price <= info.price <= max_price \ + and (not info.deleted or show_deleted): + yield ItemEntity(id, info) + + curr += 1 + + +def update_item(id: int, info: ItemInfo) -> ItemEntity | None: + if id not in _data_items: + return None + + old_info = _data_items[id] + _data_items[id] = info + + for cart_id, cart_info in _data_carts.items(): + if id in cart_info.items: + cart_info.items[id].name = _data_items[id].name + cart_info.price -= old_info.price * cart_info.items[id].quantity + cart_info.price += _data_items[id].price * cart_info.items[id].quantity + + return ItemEntity(id, info) + + +def patch_item(id: int, patch_info: PatchItemInfo) -> ItemEntity | None: + if id not in _data_items or _data_items[id].deleted: + return None + old_price = _data_items[id].price + if patch_info.name is not None: + _data_items[id].name = patch_info.name + if patch_info.price is not None: + _data_items[id].price = patch_info.price + for cart_id, cart_info in _data_carts.items(): + if id in cart_info.items: + cart_info.items[id].name = _data_items[id].name + cart_info.price -= old_price * cart_info.items[id].quantity + cart_info.price += _data_items[id].price * cart_info.items[id].quantity + return ItemEntity(id, _data_items[id]) + + +def add_cart() -> CartEntity: + _id = next(_id_generator) + _data_carts[_id] = CartInfo({}, 0) + + return CartEntity(_id, _data_carts[_id]) + + +def get_one_cart(id: int) -> CartEntity | None: + if id not in _data_carts: + return None + + return CartEntity(id, _data_carts[id]) + + +def get_many_carts(offset: int = 0, + limit: int = 10, + min_price: float = 0, + max_price: float = 1e10, + min_quantity: int = 0, + max_quantity: int = 1e10) -> Iterable[CartEntity]: + curr = 0 + for id, info in _data_carts.items(): + if offset <= curr < curr + limit \ + and min_price <= info.price <= max_price \ + and min_quantity <= sum([item.quantity for _, item in info.items.items() if item.available]) <= max_quantity: + yield CartEntity(id, info) + + curr += 1 + + +def add_item_to_cart(cart_id: int, item_id: int) -> CartEntity | None: + if cart_id not in _data_carts: + return None + + if item_id in _data_carts[cart_id].items: + _data_carts[cart_id].items[item_id].quantity += 1 + else: + _data_carts[cart_id].items[item_id] = ItemCartInfo(_data_items[item_id].name, 1, True) + _data_carts[cart_id].price += _data_items[item_id].price + return CartEntity(cart_id, _data_carts[cart_id]) From 9f1a4fc90538d096dd2b75df7df7aa035786a632 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Fri, 24 Oct 2025 10:22:22 +0300 Subject: [PATCH 04/13] Did chat for hw2 --- hw2/hw/chat/__init__.py | 0 hw2/hw/chat/client.py | 35 ++++++++++++++++++++++++++++++ hw2/hw/chat/server.py | 47 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 hw2/hw/chat/__init__.py create mode 100644 hw2/hw/chat/client.py create mode 100644 hw2/hw/chat/server.py diff --git a/hw2/hw/chat/__init__.py b/hw2/hw/chat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/chat/client.py b/hw2/hw/chat/client.py new file mode 100644 index 00000000..e929638c --- /dev/null +++ b/hw2/hw/chat/client.py @@ -0,0 +1,35 @@ +import asyncio +import websockets + + +async def receive_messages(websocket): + try: + async for message in websocket: + print(message) + except: + pass + + +async def send_messages(websocket): + loop = asyncio.get_running_loop() + while True: + try: + message = await loop.run_in_executor(None, lambda: input()) + await websocket.send(message) + except: + break + + +async def main(): + chat_name = input("Chat name: ").strip() + + uri = f"ws://127.0.0.1:8000/chat/{chat_name}" + async with websockets.connect(uri) as websocket: + await asyncio.gather( + receive_messages(websocket), + send_messages(websocket) + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/hw2/hw/chat/server.py b/hw2/hw/chat/server.py new file mode 100644 index 00000000..2b8de5b6 --- /dev/null +++ b/hw2/hw/chat/server.py @@ -0,0 +1,47 @@ +from collections import defaultdict +from typing import Dict, List + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +import uuid + + +app = FastAPI() + + +class ConnectionManager: + def __init__(self): + self.active_connections: Dict[str, List[WebSocket]] = defaultdict(list) + + async def connect(self, websocket: WebSocket, chat_name: str) -> str: + await websocket.accept() + username = str(uuid.uuid4()) + self.active_connections[chat_name].append(websocket) + return username + + def disconnect(self, websocket: WebSocket, chat_name: str): + self.active_connections[chat_name].remove(websocket) + if chat_name in self.active_connections and len(self.active_connections[chat_name]) == 0: + del self.active_connections[chat_name] + + async def broadcast(self, message: str, chat_name: str, websocket = None): + for connection in self.active_connections[chat_name]: + if connection != websocket: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@app.websocket("/chat/{chat_name}") +async def websocket_endpoint(websocket: WebSocket, chat_name: str): + username = await manager.connect(websocket, chat_name) + + await manager.broadcast(f"--- {username} joined the chat ---", chat_name) + + try: + while True: + data = await websocket.receive_text() + await manager.broadcast(f"{username} :: {data}", chat_name, websocket) + except WebSocketDisconnect: + manager.disconnect(websocket, chat_name) + await manager.broadcast(f"--- {username} left the chat ---", chat_name) \ No newline at end of file From e3a9f62f0735848c852b8975c343fa95cbc11cce Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:03:02 +0300 Subject: [PATCH 05/13] Did hw4 --- hw4/Dockerfile | 8 ++ hw4/docker-compose.yml | 33 +++++ hw4/requirements.txt | 5 + hw4/shop_api/__init__.py | 0 hw4/shop_api/api/__init__.py | 0 hw4/shop_api/api/cart/__init__.py | 1 + hw4/shop_api/api/cart/contracts.py | 30 +++++ hw4/shop_api/api/cart/routes.py | 74 +++++++++++ hw4/shop_api/api/item/__init__.py | 1 + hw4/shop_api/api/item/contracts.py | 24 ++++ hw4/shop_api/api/item/routes.py | 117 +++++++++++++++++ hw4/shop_api/database.py | 23 ++++ hw4/shop_api/main.py | 12 ++ hw4/shop_api/store/__init__.py | 19 +++ hw4/shop_api/store/models.py | 42 +++++++ hw4/shop_api/store/queries.py | 118 ++++++++++++++++++ hw4/transaction_scripts/__init__.py | 0 hw4/transaction_scripts/db_config.py | 20 +++ hw4/transaction_scripts/run_dirty_read.py | 64 ++++++++++ .../run_non_repeatable_read.py | 78 ++++++++++++ hw4/transaction_scripts/setup_db.py | 23 ++++ 21 files changed, 692 insertions(+) create mode 100644 hw4/Dockerfile create mode 100644 hw4/docker-compose.yml create mode 100644 hw4/requirements.txt create mode 100644 hw4/shop_api/__init__.py create mode 100644 hw4/shop_api/api/__init__.py create mode 100644 hw4/shop_api/api/cart/__init__.py create mode 100644 hw4/shop_api/api/cart/contracts.py create mode 100644 hw4/shop_api/api/cart/routes.py create mode 100644 hw4/shop_api/api/item/__init__.py create mode 100644 hw4/shop_api/api/item/contracts.py create mode 100644 hw4/shop_api/api/item/routes.py create mode 100644 hw4/shop_api/database.py create mode 100644 hw4/shop_api/main.py create mode 100644 hw4/shop_api/store/__init__.py create mode 100644 hw4/shop_api/store/models.py create mode 100644 hw4/shop_api/store/queries.py create mode 100644 hw4/transaction_scripts/__init__.py create mode 100644 hw4/transaction_scripts/db_config.py create mode 100644 hw4/transaction_scripts/run_dirty_read.py create mode 100644 hw4/transaction_scripts/run_non_repeatable_read.py create mode 100644 hw4/transaction_scripts/setup_db.py diff --git a/hw4/Dockerfile b/hw4/Dockerfile new file mode 100644 index 00000000..7f685a54 --- /dev/null +++ b/hw4/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . \ No newline at end of file diff --git a/hw4/docker-compose.yml b/hw4/docker-compose.yml new file mode 100644 index 00000000..ed0e6c55 --- /dev/null +++ b/hw4/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3.8' + +services: + db: + image: postgres:15-alpine + container_name: shop_db + environment: + - POSTGRES_USER=shop_user + - POSTGRES_PASSWORD=shop_password + - POSTGRES_DB=shop_db + volumes: + - postgres_data:/var/lib/postgresql/data/ + ports: + - "5432:5432" + restart: unless-stopped + + api: + build: . + container_name: shop_api + command: uvicorn shop_api.main:app --host 0.0.0.0 --port 8000 --reload + volumes: + - .:/app + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgresql+psycopg2://shop_user:shop_password@db/shop_db + - PYTHONPATH=/app + depends_on: + - db + restart: unless-stopped + +volumes: + postgres_data: \ No newline at end of file diff --git a/hw4/requirements.txt b/hw4/requirements.txt new file mode 100644 index 00000000..f0be3185 --- /dev/null +++ b/hw4/requirements.txt @@ -0,0 +1,5 @@ +fastapi +uvicorn[standard] +sqlalchemy +psycopg2-binary +pydantic-settings \ No newline at end of file diff --git a/hw4/shop_api/__init__.py b/hw4/shop_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw4/shop_api/api/__init__.py b/hw4/shop_api/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw4/shop_api/api/cart/__init__.py b/hw4/shop_api/api/cart/__init__.py new file mode 100644 index 00000000..6773176f --- /dev/null +++ b/hw4/shop_api/api/cart/__init__.py @@ -0,0 +1 @@ +from shop_api.api.cart.routes import router diff --git a/hw4/shop_api/api/cart/contracts.py b/hw4/shop_api/api/cart/contracts.py new file mode 100644 index 00000000..b0bbba83 --- /dev/null +++ b/hw4/shop_api/api/cart/contracts.py @@ -0,0 +1,30 @@ +from typing import List + +from pydantic import BaseModel + +from shop_api.store.models import Cart + + +class ItemCartResponse(BaseModel): + id: int + name: str + quantity: int + available: bool + + +class CartResponse(BaseModel): + id: int + items: List[ItemCartResponse] + price: float + + @classmethod + def from_orm(cls, cart: Cart): + items = [ + ItemCartResponse( + id=ci.item.id, + name=ci.item.name, + quantity=ci.quantity, + available=not ci.item.deleted + ) for ci in cart.items + ] + return cls(id=cart.id, items=items, price=cart.total_price) diff --git a/hw4/shop_api/api/cart/routes.py b/hw4/shop_api/api/cart/routes.py new file mode 100644 index 00000000..c0b3fa33 --- /dev/null +++ b/hw4/shop_api/api/cart/routes.py @@ -0,0 +1,74 @@ +from http import HTTPStatus +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query, Response, Request, Depends +from sqlalchemy.orm import Session +from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat + +from shop_api import store +from shop_api.database import get_db + +from shop_api.api.cart.contracts import CartResponse + +router = APIRouter(prefix="/cart") + + +@router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_cart(_: Request, response: Response, db: Session = Depends(get_db)) -> CartResponse: + entity = store.add_cart(db) + + # as REST states one should provide uri to newly created resource in location header + response.headers["location"] = f"/cart/{entity.id}" + + return CartResponse.from_orm(entity) + + +@router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested cart", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested cart as one was not found", + }, + }, +) +async def get_cart_by_id(id: int, db: Session = Depends(get_db)) -> CartResponse: + entity = store.get_one_cart(db, id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /cart/{id} was not found", + ) + + return CartResponse.from_orm(entity) + + +@router.get("/") +async def get_cart_list( + offset: Annotated[NonNegativeInt, Query()] = 0, + limit: Annotated[PositiveInt, Query()] = 10, + min_price: Annotated[NonNegativeFloat, Query()] = 0, + max_price: Annotated[NonNegativeFloat, Query()] = 1e10, + min_quantity: Annotated[NonNegativeInt, Query()] = 0, + max_quantity: Annotated[NonNegativeInt, Query()] = 1e10, + db: Session = Depends(get_db) +) -> list[CartResponse]: + return [CartResponse.from_orm(e) for e in store.get_many_carts(db, offset, limit, min_price, max_price, min_quantity, max_quantity)] + + +@router.post( + "/{cart_id}/add/{item_id}", + status_code=HTTPStatus.CREATED, +) +async def post_item_to_cart(cart_id: int, item_id: int, response: Response, db: Session = Depends(get_db)) -> CartResponse: + entity = store.add_item_to_cart(db, cart_id, item_id) + + response.headers["location"] = f"/cart/{entity.id}" + + return CartResponse.from_orm(entity) diff --git a/hw4/shop_api/api/item/__init__.py b/hw4/shop_api/api/item/__init__.py new file mode 100644 index 00000000..5402b999 --- /dev/null +++ b/hw4/shop_api/api/item/__init__.py @@ -0,0 +1 @@ +from shop_api.api.item.routes import router diff --git a/hw4/shop_api/api/item/contracts.py b/hw4/shop_api/api/item/contracts.py new file mode 100644 index 00000000..dfdc1f51 --- /dev/null +++ b/hw4/shop_api/api/item/contracts.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel, ConfigDict + + +class ItemResponse(BaseModel): + id: int + name: str + price: float + deleted: bool + + class Config: + from_attributes = True + + +class ItemRequest(BaseModel): + name: str + price: float + deleted: bool = False + + +class PatchItemRequest(BaseModel): + name: str | None = None + price: float | None = None + + model_config = ConfigDict(extra="forbid") diff --git a/hw4/shop_api/api/item/routes.py b/hw4/shop_api/api/item/routes.py new file mode 100644 index 00000000..1bfe2507 --- /dev/null +++ b/hw4/shop_api/api/item/routes.py @@ -0,0 +1,117 @@ +from http import HTTPStatus +from typing import Annotated + +from fastapi import APIRouter, HTTPException, Query, Response, Depends +from sqlalchemy.orm import Session +from pydantic import NonNegativeInt, PositiveInt, NonNegativeFloat + +from shop_api import store +from shop_api.database import get_db + +from shop_api.api.item.contracts import ItemRequest, ItemResponse, PatchItemRequest + + +router = APIRouter(prefix="/item") + + +@router.post( + "/", + status_code=HTTPStatus.CREATED, +) +async def post_item(info: ItemRequest, response: Response, db: Session = Depends(get_db)) -> ItemResponse: + entity = store.add_item(db, info) + + response.headers["location"] = f"/item/{entity.id}" + + return ItemResponse.from_orm(entity) + + +@router.get( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully returned requested item", + }, + HTTPStatus.NOT_FOUND: { + "description": "Failed to return requested item as one was not found", + }, + }, +) +async def get_item_by_id(id: int, db: Session = Depends(get_db)) -> ItemResponse: + entity = store.get_one_item(db, id) + + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /item/{id} was not found", + ) + + return ItemResponse.from_orm(entity) + + +@router.get("/") +async def get_item_list( + offset: Annotated[NonNegativeInt, Query()] = 0, + limit: Annotated[PositiveInt, Query()] = 10, + min_price: Annotated[NonNegativeFloat, Query()] = 0, + max_price: Annotated[NonNegativeFloat, Query()] = 1e10, + show_deleted: Annotated[bool, Query()] = False, + db: Session = Depends(get_db) +) -> list[ItemResponse]: + return [ItemResponse.from_orm(e) for e in store.get_many_items(db, offset, limit, min_price, max_price, show_deleted)] + + +@router.put( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully updated item", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify item as one was not found", + }, + } +) +async def put_item( + id: int, + info: ItemRequest, + db: Session = Depends(get_db) +) -> ItemResponse: + entity = store.update_item(db, id, info) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + return ItemResponse.from_orm(entity) + + +@router.patch( + "/{id}", + responses={ + HTTPStatus.OK: { + "description": "Successfully patched item", + }, + HTTPStatus.NOT_MODIFIED: { + "description": "Failed to modify item as one was not found", + }, + }, +) +async def patch_item(id: int, info: PatchItemRequest, db: Session = Depends(get_db)) -> ItemResponse: + entity = store.patch_item(db, id, info) + + if entity is None: + raise HTTPException( + HTTPStatus.NOT_MODIFIED, + f"Requested resource /item/{id} was not found", + ) + + return ItemResponse.from_orm(entity) + + +@router.delete("/{id}") +async def delete_item(id: int, db: Session = Depends(get_db)) -> Response: + store.delete_item(db, id) + return Response("") diff --git a/hw4/shop_api/database.py b/hw4/shop_api/database.py new file mode 100644 index 00000000..94d0e0e3 --- /dev/null +++ b/hw4/shop_api/database.py @@ -0,0 +1,23 @@ +import os +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+psycopg2://shop_user:shop_password@localhost/shop_db") + + +settings = Settings() + +engine = create_engine(settings.DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/hw4/shop_api/main.py b/hw4/shop_api/main.py new file mode 100644 index 00000000..42cdfc6b --- /dev/null +++ b/hw4/shop_api/main.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from shop_api.api import cart, item +from shop_api.database import engine +from shop_api.store import models + + +models.Base.metadata.create_all(bind=engine) + +app = FastAPI(title="Shop API") + +app.include_router(cart.router) +app.include_router(item.router) diff --git a/hw4/shop_api/store/__init__.py b/hw4/shop_api/store/__init__.py new file mode 100644 index 00000000..364a90af --- /dev/null +++ b/hw4/shop_api/store/__init__.py @@ -0,0 +1,19 @@ +from shop_api.store.models import Item, CartItem, Cart +from shop_api.store.queries import (add_item, delete_item, get_one_item, get_many_items, update_item, patch_item, + add_cart, get_one_cart, get_many_carts, add_item_to_cart) + +__all__ = [ + "Item", + "CartItem", + "Cart", + "add_item", + "delete_item", + "get_one_item", + "get_many_items", + "update_item", + "patch_item", + "add_cart", + "get_one_cart", + "get_many_carts", + "add_item_to_cart" +] \ No newline at end of file diff --git a/hw4/shop_api/store/models.py b/hw4/shop_api/store/models.py new file mode 100644 index 00000000..12ace7c1 --- /dev/null +++ b/hw4/shop_api/store/models.py @@ -0,0 +1,42 @@ +from sqlalchemy import Column, Integer, String, Float, Boolean, ForeignKey +from sqlalchemy.orm import relationship + +from shop_api.database import Base + + +class Item(Base): + __tablename__ = "items" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, index=True) + price = Column(Float) + deleted = Column(Boolean, default=False, index=True) + + cart_items = relationship("CartItem", back_populates="item") + + +class Cart(Base): + __tablename__ = "carts" + + id = Column(Integer, primary_key=True, index=True) + + items = relationship("CartItem", back_populates="cart", cascade="all, delete-orphan") + + @property + def total_price(self): + if not self.items: + return 0.0 + return sum( + ci.item.price * ci.quantity for ci in self.items if not ci.item.deleted + ) + + +class CartItem(Base): + __tablename__ = "cart_items" + + cart_id = Column(Integer, ForeignKey("carts.id"), primary_key=True) + item_id = Column(Integer, ForeignKey("items.id"), primary_key=True) + quantity = Column(Integer, default=1) + + cart = relationship("Cart", back_populates="items") + item = relationship("Item", back_populates="cart_items") diff --git a/hw4/shop_api/store/queries.py b/hw4/shop_api/store/queries.py new file mode 100644 index 00000000..91426394 --- /dev/null +++ b/hw4/shop_api/store/queries.py @@ -0,0 +1,118 @@ +from typing import List +from sqlalchemy.orm import Session, joinedload + +import shop_api.store.models as db_models +from shop_api.api.item.contracts import ItemRequest, PatchItemRequest + + +def add_item(db: Session, info: ItemRequest) -> db_models.Item: + db_item = db_models.Item(name=info.name, price=info.price, deleted=info.deleted) + db.add(db_item) + db.commit() + db.refresh(db_item) + return db_item + + +def delete_item(db: Session, item_id: int) -> None: + db_item = db.query(db_models.Item).filter(db_models.Item.id == item_id).first() + if db_item: + db_item.deleted = True + db.commit() + + +def get_one_item(db: Session, item_id: int) -> db_models.Item | None: + return db.query(db_models.Item).filter( + db_models.Item.id == item_id, + db_models.Item.deleted == False + ).first() + + +def get_many_items( + db: Session, offset: int, limit: int, min_price: float, max_price: float, show_deleted: bool +) -> List[db_models.Item]: + query = db.query(db_models.Item) + if not show_deleted: + query = query.filter(db_models.Item.deleted == False) + + query = query.filter(db_models.Item.price >= min_price, db_models.Item.price <= max_price) + + return query.offset(offset).limit(limit).all() + + +def update_item(db: Session, item_id: int, info: ItemRequest) -> db_models.Item | None: + db_item = db.query(db_models.Item).filter(db_models.Item.id == item_id).first() + if db_item: + db_item.name = info.name + db_item.price = info.price + db_item.deleted = info.deleted + db.commit() + db.refresh(db_item) + return db_item + + +def patch_item(db: Session, item_id: int, patch_info: PatchItemRequest) -> db_models.Item | None: + db_item = get_one_item(db, item_id) + if db_item: + if patch_info.name is not None: + db_item.name = patch_info.name + if patch_info.price is not None: + db_item.price = patch_info.price + db.commit() + db.refresh(db_item) + return db_item + + +def add_cart(db: Session) -> db_models.Cart: + db_cart = db_models.Cart() + db.add(db_cart) + db.commit() + db.refresh(db_cart) + return db_cart + + +def get_one_cart(db: Session, cart_id: int) -> db_models.Cart | None: + return db.query(db_models.Cart).options( + joinedload(db_models.Cart.items).joinedload(db_models.CartItem.item) + ).filter(db_models.Cart.id == cart_id).first() + + +def get_many_carts( + db: Session, offset: int, limit: int, min_price: float, max_price: float, min_quantity: int, max_quantity: int +) -> List[db_models.Cart]: + all_carts = db.query(db_models.Cart).options( + joinedload(db_models.Cart.items).joinedload(db_models.CartItem.item) + ).offset(offset).limit(limit).all() + + filtered_carts = [] + for cart in all_carts: + total_quantity = sum(ci.quantity for ci in cart.items if not ci.item.deleted) + total_price = cart.total_price + + if (min_price <= total_price <= max_price and + min_quantity <= total_quantity <= max_quantity): + filtered_carts.append(cart) + + return filtered_carts + + +def add_item_to_cart(db: Session, cart_id: int, item_id: int) -> db_models.Cart | None: + cart = get_one_cart(db, cart_id) + item = get_one_item(db, item_id) + + if not cart or not item: + return None + + cart_item = db.query(db_models.CartItem).filter( + db_models.CartItem.cart_id == cart_id, + db_models.CartItem.item_id == item_id + ).first() + + if cart_item: + cart_item.quantity += 1 + else: + new_cart_item = db_models.CartItem(cart_id=cart_id, item_id=item_id, quantity=1) + db.add(new_cart_item) + + db.commit() + db.refresh(cart) + return cart \ No newline at end of file diff --git a/hw4/transaction_scripts/__init__.py b/hw4/transaction_scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw4/transaction_scripts/db_config.py b/hw4/transaction_scripts/db_config.py new file mode 100644 index 00000000..b56cab1b --- /dev/null +++ b/hw4/transaction_scripts/db_config.py @@ -0,0 +1,20 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy import Column, Integer, String, Float, Boolean +from sqlalchemy.orm import declarative_base + +DATABASE_URL = "postgresql+psycopg2://shop_user:shop_password@localhost:5432/shop_db" +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + + +class Item(Base): + __tablename__ = 'items' + id = Column(Integer, primary_key=True) + name = Column(String) + price = Column(Float) + deleted = Column(Boolean, default=False) + + def __repr__(self): + return f"" \ No newline at end of file diff --git a/hw4/transaction_scripts/run_dirty_read.py b/hw4/transaction_scripts/run_dirty_read.py new file mode 100644 index 00000000..c1486a75 --- /dev/null +++ b/hw4/transaction_scripts/run_dirty_read.py @@ -0,0 +1,64 @@ +import threading +from db_config import Item, engine, SessionLocal +from setup_db import setup +from sqlalchemy import select, update + + +def transaction_a(item_id, barrier): + connection = engine.connect().execution_options(isolation_level="READ COMMITTED") + try: + with connection.begin() as transaction: + print("Transaction A started. Try set price 99.99") + stmt = ( + update(Item) + .where(Item.id == item_id) + .values(price=55.55) + ) + connection.execute(stmt) + print("Transaction A price updated and flushed") + barrier.wait() + barrier.wait() + stmt = select(Item.price).where(Item.id == item_id) + price = connection.execute(stmt).scalar_one() + print(f"Transaction A has price {price}") + assert price == 55.55 + print("Transaction A do rollback") + transaction.rollback() + finally: + connection.close() + + +def transaction_b(item_id, barrier): + connection = engine.connect().execution_options(isolation_level="READ COMMITTED") + try: + barrier.wait() + with connection.begin(): + print("Transaction B read price") + stmt = select(Item.price).where(Item.id == item_id) + price = connection.execute(stmt).scalar_one() + assert price == 10.0 + print(f"Transaction B price={price:.2f}. No dirty read! Sleep") + barrier.wait() + finally: + connection.close() + + +if __name__ == "__main__": + print("--- Postgresql: no Dirty read. All READ COMMITTED ---") + item_id = setup() + + barrier = threading.Barrier(2, timeout=10) + thread_a = threading.Thread(target=transaction_a, args=(item_id, barrier)) + thread_b = threading.Thread(target=transaction_b, args=(item_id, barrier)) + + thread_a.start() + thread_b.start() + + thread_a.join() + thread_b.join() + + final_session = SessionLocal() + final_item = final_session.query(Item).get(item_id) + print(f"Final price: {final_item.price:.2f}") + assert final_item.price == 10.0 + final_session.close() diff --git a/hw4/transaction_scripts/run_non_repeatable_read.py b/hw4/transaction_scripts/run_non_repeatable_read.py new file mode 100644 index 00000000..3b46937f --- /dev/null +++ b/hw4/transaction_scripts/run_non_repeatable_read.py @@ -0,0 +1,78 @@ +import threading +from db_config import Item, engine +from setup_db import setup +from sqlalchemy import select, update + + +def reader_transaction(item_id, isolation_level, barrier): + connection = engine.connect().execution_options(isolation_level=isolation_level) + try: + with connection.begin(): + print(f"READER ({isolation_level}): Started") + stmt1 = select(Item.price).where(Item.id == item_id) + price1 = connection.execute(stmt1).scalar_one() + print(f"READER ({isolation_level}): First read, price={price1:.2f}") + + barrier.wait() + barrier.wait() + + stmt2 = select(Item.price).where(Item.id == item_id) + price2 = connection.execute(stmt2).scalar_one() + print(f"READER ({isolation_level}): Second read, price={price2:.2f}") + + if isolation_level == 'READ COMMITTED': + assert price1 != price2 + print(f"READER ({isolation_level}): Non-Repeatable Read!") + else: + assert price1 == price2 + print(f"READER ({isolation_level}): ОК") + finally: + connection.close() + + +def writer_transaction(item_id, barrier): + barrier.wait() + connection = engine.connect() + try: + with connection.begin(): + print(f"WRITER: Update price {item_id}") + stmt = ( + update(Item) + .where(Item.id == item_id) + .values(price=55.55) + ) + connection.execute(stmt) + with connection.begin(): + check_stmt = select(Item.price).where(Item.id == item_id) + current_price = connection.execute(check_stmt).scalar_one() + print(f"WRITER: Check price={current_price}") + print("WRITER: Price updated") + finally: + connection.close() + barrier.wait() + + +if __name__ == "__main__": + print("--- Non-Repeatable Read on level READ COMMITTED ---") + item_id = setup() + barrier = threading.Barrier(2, timeout=10) + + reader_thread = threading.Thread(target=reader_transaction, args=(item_id, "READ COMMITTED", barrier)) + writer_thread = threading.Thread(target=writer_transaction, args=(item_id, barrier)) + + reader_thread.start() + writer_thread.start() + reader_thread.join() + writer_thread.join() + + print("--- No Non-Repeatable Read on level REPEATABLE READ ---") + item_id = setup() + barrier = threading.Barrier(2, timeout=10) + + reader_thread = threading.Thread(target=reader_transaction, args=(item_id, "REPEATABLE READ", barrier)) + writer_thread = threading.Thread(target=writer_transaction, args=(item_id, barrier)) + + reader_thread.start() + writer_thread.start() + reader_thread.join() + writer_thread.join() diff --git a/hw4/transaction_scripts/setup_db.py b/hw4/transaction_scripts/setup_db.py new file mode 100644 index 00000000..7d601a07 --- /dev/null +++ b/hw4/transaction_scripts/setup_db.py @@ -0,0 +1,23 @@ +from db_config import SessionLocal, Item +import warnings +warnings.filterwarnings("ignore") +from sqlalchemy import text + + +def setup(): + session = SessionLocal() + try: + session.query(Item).delete(synchronize_session=False) + session.execute(text("ALTER SEQUENCE items_id_seq RESTART WITH 1;")) + + test_item = Item(name="Notebook", price=10.00) + session.add(test_item) + session.commit() + print(f"SETUP: created {test_item.id} with price {test_item.price}") + return test_item.id + finally: + session.close() + + +if __name__ == "__main__": + setup() From e0b1a0acd69b9f8a290e2fc178797f31a129b0cb Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:31:12 +0300 Subject: [PATCH 06/13] Did tests for hw5 --- .github/workflows/hw5-tests.yml | 38 ++++++ hw2/hw/tests/__init__.py | 0 hw2/hw/tests/conftest.py | 18 +++ hw2/hw/tests/test_api.py | 207 ++++++++++++++++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100644 .github/workflows/hw5-tests.yml create mode 100644 hw2/hw/tests/__init__.py create mode 100644 hw2/hw/tests/conftest.py create mode 100644 hw2/hw/tests/test_api.py diff --git a/.github/workflows/hw5-tests.yml b/.github/workflows/hw5-tests.yml new file mode 100644 index 00000000..737c8f50 --- /dev/null +++ b/.github/workflows/hw5-tests.yml @@ -0,0 +1,38 @@ +name: Python application tests + +on: + pull_request: + branches: [ main ] + paths: [ 'hw2/tests/**' ] + push: + branches: [ main ] + paths: [ 'hw2/tests/**' ] + +jobs: + test-hw5: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12", "3.13"] + + steps: + - name: Check out repository 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 + working-directory: hw2/hw + run: | + python -m pip install --upgrade pip + pip install -r hw/requirements.txt + + - name: Run tests + working-directory: hw2/hw + env: + PYTHONPATH: ${{ github.workspace }}/hw2/hw + run: | + pytest tests/test_api.py --cov=shop_api --cov-report=term-missing --cov-fail-under=95 \ No newline at end of file diff --git a/hw2/hw/tests/__init__.py b/hw2/hw/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/tests/conftest.py b/hw2/hw/tests/conftest.py new file mode 100644 index 00000000..b01d57b2 --- /dev/null +++ b/hw2/hw/tests/conftest.py @@ -0,0 +1,18 @@ +import pytest +from fastapi.testclient import TestClient + +from shop_api.main import app +from shop_api.store import queries as queries_module + + +@pytest.fixture(autouse=True) +def clear_storage(): + queries_module._data_items.clear() + queries_module._data_carts.clear() + queries_module._id_generator = queries_module.int_id_generator() + yield + + +@pytest.fixture(scope="module") +def client() -> TestClient: + return TestClient(app) \ No newline at end of file diff --git a/hw2/hw/tests/test_api.py b/hw2/hw/tests/test_api.py new file mode 100644 index 00000000..ba5c3c78 --- /dev/null +++ b/hw2/hw/tests/test_api.py @@ -0,0 +1,207 @@ +from http import HTTPStatus + + +def test_create_item_success(client): + response = client.post("/item/", json={"name": "Test Item", "price": 10.5}) + assert response.status_code == HTTPStatus.CREATED + data = response.json() + assert data["name"] == "Test Item" + assert data["price"] == 10.5 + assert "id" in data + assert response.headers["location"] == f"/item/{data['id']}" + + +def test_get_item_not_found(client): + response = client.get("/item/999") + assert response.status_code == HTTPStatus.NOT_FOUND + + +def test_get_item_by_id_success(client): + create_response = client.post("/item/", json={"name": "Another Item", "price": 99.9}) + item_id = create_response.json()["id"] + + response = client.get(f"/item/{item_id}") + assert response.status_code == HTTPStatus.OK + data = response.json() + assert data["id"] == item_id + assert data["name"] == "Another Item" + + +def test_get_items_list(client): + client.post("/item/", json={"name": "Item 1", "price": 10}) + client.post("/item/", json={"name": "Item 2", "price": 20}) + client.post("/item/", json={"name": "Item 3", "price": 30}) + + response = client.get("/item/") + assert response.status_code == HTTPStatus.OK + assert len(response.json()) == 3 + + response = client.get("/item/?min_price=15&max_price=25") + assert len(response.json()) == 1 + assert response.json()[0]["name"] == "Item 2" + + response = client.get("/item/?offset=1&limit=1") + assert len(response.json()) == 1 + assert response.json()[0]["price"] == 20 + + +def test_delete_item(client): + create_response = client.post("/item/", json={"name": "Deletable Item", "price": 5}) + item_id = create_response.json()["id"] + + delete_response = client.delete(f"/item/{item_id}") + assert delete_response.status_code == HTTPStatus.OK + + get_response = client.get(f"/item/{item_id}") + assert get_response.status_code == HTTPStatus.NOT_FOUND + + list_response = client.get("/item/?show_deleted=true") + assert len(list_response.json()) == 1 + deleted_item = list_response.json()[0] + assert deleted_item["id"] == item_id + assert deleted_item["deleted"] is True + + delete_response_2 = client.delete(f"/item/{item_id}") + assert delete_response_2.status_code == HTTPStatus.OK + + +def test_update_item_put(client): + create_response = client.post("/item/", json={"name": "Old Name", "price": 100}) + item_id = create_response.json()["id"] + + response = client.put(f"/item/{item_id}", json={"name": "New Name", "price": 200, "deleted": False}) + assert response.status_code == HTTPStatus.OK + data = response.json() + assert data["name"] == "New Name" + assert data["price"] == 200 + + response_not_found = client.put("/item/999", json={"name": "New Name", "price": 200, "deleted": False}) + assert response_not_found.status_code == HTTPStatus.NOT_MODIFIED + + +def test_update_item_patch(client): + create_response = client.post("/item/", json={"name": "Original", "price": 50}) + item_id = create_response.json()["id"] + + response_name = client.patch(f"/item/{item_id}", json={"name": "Patched Name"}) + assert response_name.status_code == HTTPStatus.OK + assert response_name.json()["name"] == "Patched Name" + assert response_name.json()["price"] == 50 + + + response_price = client.patch(f"/item/{item_id}", json={"price": 75.5}) + assert response_price.status_code == HTTPStatus.OK + assert response_price.json()["name"] == "Patched Name" + assert response_price.json()["price"] == 75.5 + + response_not_found = client.patch("/item/999", json={"name": "New Name"}) + assert response_not_found.status_code == HTTPStatus.NOT_MODIFIED + + +def test_create_cart_success(client): + response = client.post("/cart/") + assert response.status_code == HTTPStatus.CREATED + data = response.json() + assert data["items"] == [] + assert data["price"] == 0.0 + assert "id" in data + assert response.headers["location"] == f"/cart/{data['id']}" + + +def test_get_cart_not_found(client): + response = client.get("/cart/999") + assert response.status_code == HTTPStatus.NOT_FOUND + + +def test_add_item_to_cart(client): + item_resp = client.post("/item/", json={"name": "Burger", "price": 10}) + item_id = item_resp.json()["id"] + cart_resp = client.post("/cart/") + cart_id = cart_resp.json()["id"] + + add_resp = client.post(f"/cart/{cart_id}/add/{item_id}") + assert add_resp.status_code == HTTPStatus.CREATED + cart_data = add_resp.json() + assert len(cart_data["items"]) == 1 + assert cart_data["items"][0]["id"] == item_id + assert cart_data["items"][0]["quantity"] == 1 + assert cart_data["price"] == 10.0 + + add_resp_2 = client.post(f"/cart/{cart_id}/add/{item_id}") + cart_data_2 = add_resp_2.json() + assert len(cart_data_2["items"]) == 1 + assert cart_data_2["items"][0]["quantity"] == 2 + assert cart_data_2["price"] == 20.0 + + add_resp_no_cart = client.post(f"/cart/999/add/{item_id}") + assert add_resp_no_cart.status_code != HTTPStatus.OK + + +def test_get_cart_by_id(client): + cart_resp = client.post("/cart/") + cart_id = cart_resp.json()["id"] + + get_resp = client.get(f"/cart/{cart_id}") + assert get_resp.status_code == HTTPStatus.OK + assert get_resp.json()["id"] == cart_id + + +def test_interaction_delete_item_from_cart(client): + item_resp = client.post("/item/", json={"name": "Fries", "price": 5}) + item_id = item_resp.json()["id"] + cart_resp = client.post("/cart/") + cart_id = cart_resp.json()["id"] + client.post(f"/cart/{cart_id}/add/{item_id}") + + client.delete(f"/item/{item_id}") + + cart_get_resp = client.get(f"/cart/{cart_id}") + cart_data = cart_get_resp.json() + assert cart_data["price"] == 0.0 + assert cart_data["items"][0]["available"] is False + + +def test_interaction_update_item_in_cart(client): + item_resp = client.post("/item/", json={"name": "Item 1", "price": 2}) + item_id = item_resp.json()["id"] + cart_resp = client.post("/cart/") + cart_id = cart_resp.json()["id"] + client.post(f"/cart/{cart_id}/add/{item_id}") + client.post(f"/cart/{cart_id}/add/{item_id}") + + client.put(f"/item/{item_id}", json={"name": "Item 2", "price": 3, "deleted": False}) + cart_get_resp = client.get(f"/cart/{cart_id}") + cart_data = cart_get_resp.json() + assert cart_data["price"] == 6.0 + assert cart_data["items"][0]["name"] == "New Soda" + + client.patch(f"/item/{item_id}", json={"price": 2.5}) + cart_get_resp_2 = client.get(f"/cart/{cart_id}") + cart_data_2 = cart_get_resp_2.json() + assert cart_data_2["price"] == 5.0 + + +def test_get_carts_list(client): + item1_resp = client.post("/item/", json={"name": "A", "price": 10}) + item1_id = item1_resp.json()["id"] + item2_resp = client.post("/item/", json={"name": "B", "price": 50}) + item2_id = item2_resp.json()["id"] + + cart1_resp = client.post("/cart/") + cart1_id = cart1_resp.json()["id"] + client.post(f"/cart/{cart1_id}/add/{item1_id}") + client.post(f"/cart/{cart1_id}/add/{item1_id}") + client.post(f"/cart/{cart1_id}/add/{item1_id}") + + cart2_resp = client.post("/cart/") + cart2_id = cart2_resp.json()["id"] + client.post(f"/cart/{cart2_id}/add/{item1_id}") + client.post(f"/cart/{cart2_id}/add/{item2_id}") + + resp_price = client.get("/cart/?min_price=50") + assert len(resp_price.json()) == 1 + assert resp_price.json()[0]["id"] == cart2_id + + resp_quant = client.get("/cart/?min_quantity=3") + assert len(resp_quant.json()) == 1 + assert resp_quant.json()[0]["id"] == cart1_id From ce3263eafb26e8bcad258e72ebe3da3938db3235 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:35:30 +0300 Subject: [PATCH 07/13] Rename workflow --- .github/workflows/{hw5-tests.yml => ci.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{hw5-tests.yml => ci.yml} (95%) diff --git a/.github/workflows/hw5-tests.yml b/.github/workflows/ci.yml similarity index 95% rename from .github/workflows/hw5-tests.yml rename to .github/workflows/ci.yml index 737c8f50..36ebaaaf 100644 --- a/.github/workflows/hw5-tests.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.12", "3.13"] + python-version: ["3.12"] steps: - name: Check out repository code From 64a4dff6a16f43525539f31bcc672e23f501c24b Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:40:54 +0300 Subject: [PATCH 08/13] Update workflow --- .github/workflows/{ci.yml => hw5-tests.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{ci.yml => hw5-tests.yml} (96%) diff --git a/.github/workflows/ci.yml b/.github/workflows/hw5-tests.yml similarity index 96% rename from .github/workflows/ci.yml rename to .github/workflows/hw5-tests.yml index 36ebaaaf..d7f492e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/hw5-tests.yml @@ -1,4 +1,4 @@ -name: Python application tests +name: "HW5 Tests" on: pull_request: From 203ec17a8ae490ac8ea04c6501aec31c3968b293 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:46:04 +0300 Subject: [PATCH 09/13] Fix workflow --- .github/workflows/hw5-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hw5-tests.yml b/.github/workflows/hw5-tests.yml index d7f492e7..5f99629c 100644 --- a/.github/workflows/hw5-tests.yml +++ b/.github/workflows/hw5-tests.yml @@ -3,10 +3,10 @@ name: "HW5 Tests" on: pull_request: branches: [ main ] - paths: [ 'hw2/tests/**' ] + paths: [ 'hw2/hw/**' ] push: branches: [ main ] - paths: [ 'hw2/tests/**' ] + paths: [ 'hw2/hw/**' ] jobs: test-hw5: From ebb4e8b9f56633f9756352df3da0f75635af7937 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:47:04 +0300 Subject: [PATCH 10/13] Phantom commit --- hw2/hw/tests/test_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hw2/hw/tests/test_api.py b/hw2/hw/tests/test_api.py index ba5c3c78..ca317641 100644 --- a/hw2/hw/tests/test_api.py +++ b/hw2/hw/tests/test_api.py @@ -5,6 +5,7 @@ def test_create_item_success(client): response = client.post("/item/", json={"name": "Test Item", "price": 10.5}) assert response.status_code == HTTPStatus.CREATED data = response.json() + assert data["name"] == "Test Item" assert data["price"] == 10.5 assert "id" in data From 7aef5c10dce38df9d827346fba7c8755c812bab6 Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:48:20 +0300 Subject: [PATCH 11/13] Fix workflow --- .github/workflows/hw5-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hw5-tests.yml b/.github/workflows/hw5-tests.yml index 5f99629c..26369312 100644 --- a/.github/workflows/hw5-tests.yml +++ b/.github/workflows/hw5-tests.yml @@ -28,7 +28,7 @@ jobs: working-directory: hw2/hw run: | python -m pip install --upgrade pip - pip install -r hw/requirements.txt + pip install -r requirements.txt - name: Run tests working-directory: hw2/hw From a175d99f605f1c6c19c6257248bcfb2425d542bc Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 02:48:56 +0300 Subject: [PATCH 12/13] Phantom commit --- hw2/hw/tests/test_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hw2/hw/tests/test_api.py b/hw2/hw/tests/test_api.py index ca317641..ba5c3c78 100644 --- a/hw2/hw/tests/test_api.py +++ b/hw2/hw/tests/test_api.py @@ -5,7 +5,6 @@ def test_create_item_success(client): response = client.post("/item/", json={"name": "Test Item", "price": 10.5}) assert response.status_code == HTTPStatus.CREATED data = response.json() - assert data["name"] == "Test Item" assert data["price"] == 10.5 assert "id" in data From e0fa7378bea63d500700bbddcf6af8aa9396e51f Mon Sep 17 00:00:00 2001 From: Deuqz Date: Mon, 27 Oct 2025 03:25:15 +0300 Subject: [PATCH 13/13] Fix api by tests hw5 --- hw2/hw/requirements.txt | 1 + hw2/hw/shop_api/api/cart/routes.py | 6 ++++++ hw2/hw/shop_api/store/queries.py | 3 ++- hw2/hw/tests/conftest.py | 22 +++++++++++++--------- hw2/hw/tests/test_api.py | 2 +- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/hw2/hw/requirements.txt b/hw2/hw/requirements.txt index 207dcf5c..45ce7ae4 100644 --- a/hw2/hw/requirements.txt +++ b/hw2/hw/requirements.txt @@ -5,5 +5,6 @@ uvicorn>=0.24.0 # Зависимости для тестирования 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/api/cart/routes.py b/hw2/hw/shop_api/api/cart/routes.py index c4fedc6f..d53646cb 100644 --- a/hw2/hw/shop_api/api/cart/routes.py +++ b/hw2/hw/shop_api/api/cart/routes.py @@ -66,6 +66,12 @@ async def get_cart_list( async def post_item_to_cart(cart_id: int, item_id: int, response: Response) -> CartResponse: entity = store.add_item_to_cart(cart_id, item_id) + if not entity: + raise HTTPException( + HTTPStatus.NOT_FOUND, + f"Request resource /{cart_id}/cart/{item_id} was not found", + ) + # as REST states one should provide uri to newly created resource in location header response.headers["location"] = f"/cart/{entity.id}" diff --git a/hw2/hw/shop_api/store/queries.py b/hw2/hw/shop_api/store/queries.py index 94e7bd4a..8bb1ad5f 100644 --- a/hw2/hw/shop_api/store/queries.py +++ b/hw2/hw/shop_api/store/queries.py @@ -7,6 +7,7 @@ _data_carts = dict[int, CartInfo]() + def int_id_generator() -> Iterable[int]: i = 0 while True: @@ -51,7 +52,7 @@ def get_many_items(offset: int = 0, show_deleted: bool = False) -> Iterable[ItemEntity]: curr = 0 for id, info in _data_items.items(): - if offset <= curr < curr + limit \ + if offset <= curr < offset + limit \ and min_price <= info.price <= max_price \ and (not info.deleted or show_deleted): yield ItemEntity(id, info) diff --git a/hw2/hw/tests/conftest.py b/hw2/hw/tests/conftest.py index b01d57b2..018a4f22 100644 --- a/hw2/hw/tests/conftest.py +++ b/hw2/hw/tests/conftest.py @@ -1,18 +1,22 @@ import pytest from fastapi.testclient import TestClient -from shop_api.main import app -from shop_api.store import queries as queries_module +from hw2.hw.shop_api.main import app + +from hw2.hw.shop_api.store import queries as queries_module @pytest.fixture(autouse=True) -def clear_storage(): - queries_module._data_items.clear() - queries_module._data_carts.clear() - queries_module._id_generator = queries_module.int_id_generator() - yield +def clear_storage_before_each_test(monkeypatch): + fresh_items_db = {} + fresh_carts_db = {} + + monkeypatch.setattr(queries_module, "_data_items", fresh_items_db) + monkeypatch.setattr(queries_module, "_data_carts", fresh_carts_db) + + monkeypatch.setattr(queries_module, "_id_generator", queries_module.int_id_generator()) -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def client() -> TestClient: - return TestClient(app) \ No newline at end of file + return TestClient(app) diff --git a/hw2/hw/tests/test_api.py b/hw2/hw/tests/test_api.py index ba5c3c78..68cdf83b 100644 --- a/hw2/hw/tests/test_api.py +++ b/hw2/hw/tests/test_api.py @@ -173,7 +173,7 @@ def test_interaction_update_item_in_cart(client): cart_get_resp = client.get(f"/cart/{cart_id}") cart_data = cart_get_resp.json() assert cart_data["price"] == 6.0 - assert cart_data["items"][0]["name"] == "New Soda" + assert cart_data["items"][0]["name"] == "Item 2" client.patch(f"/item/{item_id}", json={"price": 2.5}) cart_get_resp_2 = client.get(f"/cart/{cart_id}")