From dbed05a50954f5541483cfbc664096e75d20c6f1 Mon Sep 17 00:00:00 2001 From: Polina Kremneva Date: Wed, 24 Sep 2025 19:29:13 +0300 Subject: [PATCH 1/5] Implement ASGI application with fibonacci, factorial, and mean endpoints --- hw1/app.py | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 221 insertions(+), 6 deletions(-) diff --git a/hw1/app.py b/hw1/app.py index 6107b870..f2e0d46e 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -1,10 +1,11 @@ -from typing import Any, Awaitable, Callable +from typing import Any, Awaitable, Callable, Dict, List +import json async def application( - scope: dict[str, Any], - receive: Callable[[], Awaitable[dict[str, Any]]], - send: Callable[[dict[str, Any]], Awaitable[None]], + scope: Dict[str, Any], + receive: Callable[[], Awaitable[Dict[str, Any]]], + send: Callable[[Dict[str, Any]], Awaitable[None]], ): """ Args: @@ -12,8 +13,222 @@ async def application( receive: Корутина для получения сообщений от клиента send: Корутина для отправки сообщений клиенту """ - # TODO: Ваша реализация здесь + # Обрабатываем lifespan события (startup/shutdown) + if scope["type"] == "lifespan": + await handle_lifespan(scope, receive, send) + return + + # Проверяем, что это HTTP запрос + if scope["type"] != "http": + return + + # Получаем метод и путь запроса + method = scope["method"] + path = scope["path"] + + # Обрабатываем только GET запросы + if method != "GET": + await send_response(send, 404, {"error": "Not found"}) + return + + # Обрабатываем разные эндпоинты + if path == "/factorial": + await handle_factorial(scope, receive, send) + elif path.startswith("/fibonacci/"): + await handle_fibonacci(scope, receive, send) + elif path == "/mean": + await handle_mean(scope, receive, send) + else: + await send_response(send, 404, {"error": "Not found"}) + + +async def handle_lifespan(scope: Dict[str, Any], receive: Callable, send: Callable): + """Обработчик lifespan событий (startup/shutdown)""" + 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 send_response(send: Callable, status: int, data: Dict[str, Any]): + """Вспомогательная функция для отправки ответа""" + response_body = json.dumps(data).encode("utf-8") + + # Отправляем заголовки + await send({ + "type": "http.response.start", + "status": status, + "headers": [ + [b"content-type", b"application/json"], + ], + }) + + # Отправляем тело + await send({ + "type": "http.response.body", + "body": response_body, + }) + + +async def handle_factorial(scope: Dict[str, Any], receive: Callable, send: Callable): + """Обработчик для факториала""" + query_string = scope.get("query_string", b"").decode("utf-8") + params = parse_query_string(query_string) + + n_str = params.get("n", [""])[0] + + # Валидация параметра n + if not n_str: + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + # Проверяем, что это целое число + if not is_integer_string(n_str): + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + n = int(n_str) + if n < 0: + await send_response(send, 400, {"error": "Bad request"}) + return + + # Вычисляем факториал + result = factorial(n) + await send_response(send, 200, {"result": result}) + + +async def handle_fibonacci(scope: Dict[str, Any], receive: Callable, send: Callable): + """Обработчик для чисел Фибоначчи""" + path = scope["path"] + + # Извлекаем n из пути /fibonacci/{n} + n_str = path.split("/")[-1] + + if not n_str: + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + # Проверяем, что это целое число + if not is_integer_string(n_str): + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + n = int(n_str) + if n < 0: + await send_response(send, 400, {"error": "Bad request"}) + return + + # Вычисляем n-ное число Фибоначчи + result = fibonacci(n) + await send_response(send, 200, {"result": result}) + + +async def handle_mean(scope: Dict[str, Any], receive: Callable, send: Callable): + """Обработчик для среднего арифметического""" + # Получаем тело запроса + body = await receive_body(receive) + + if not body: + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + try: + numbers = json.loads(body) + except json.JSONDecodeError: + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + if not isinstance(numbers, list): + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + if not numbers: + await send_response(send, 400, {"error": "Bad request"}) + return + + # Проверяем, что все элементы списка - числа + if not all(isinstance(x, (int, float)) for x in numbers): + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + # Вычисляем среднее арифметическое + try: + mean = sum(numbers) / len(numbers) + except TypeError: + await send_response(send, 422, {"error": "Unprocessable entity"}) + return + + await send_response(send, 200, {"result": mean}) + + +async def receive_body(receive: Callable) -> str: + """Получает тело запроса""" + body = b"" + more_body = True + + while more_body: + message = await receive() + body += message.get("body", b"") + more_body = message.get("more_body", False) + + return body.decode("utf-8") + + +def parse_query_string(query_string: str) -> Dict[str, List[str]]: + """Парсит query string в словарь параметров""" + params: Dict[str, List[str]] = {} + if query_string: + for param in query_string.split("&"): + if "=" in param: + key, value = param.split("=", 1) + if key in params: + params[key].append(value) + else: + params[key] = [value] + else: + # Обрабатываем параметры без значения + if param in params: + params[param].append("") + else: + params[param] = [""] + return params + + +def is_integer_string(s: str) -> bool: + """Проверяет, является ли строка целым числом""" + if not s: + return False + if s[0] in ('-', '+'): + return s[1:].isdigit() + return s.isdigit() + + +def factorial(n: int) -> int: + """Вычисляет факториал числа """ + if n == 0: + return 1 + result = 1 + for i in range(1, n + 1): + result *= i + return result + + +def fibonacci(n: int) -> int: + """Вычисляет n-ное число Фибоначчи""" + 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 + 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) \ No newline at end of file From 9105c504f4f53a11dea02271954261dd5a238d24 Mon Sep 17 00:00:00 2001 From: Polina Kremneva Date: Sat, 4 Oct 2025 16:37:09 +0300 Subject: [PATCH 2/5] add shop api --- hw2/hw/shop_api/main.py | 281 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 280 insertions(+), 1 deletion(-) diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index f60a8c60..30b29e99 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,3 +1,282 @@ -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException, status, Response +from pydantic import BaseModel, Field +from typing import List, Optional, Dict, Any +import uuid app = FastAPI(title="Shop API") + +# Хранение данных в памяти +items_db: Dict[int, Dict] = {} +carts_db: Dict[int, Dict] = {} + +# Счетчики для генерации ID +item_counter = 0 +cart_counter = 0 + +# Модели данных +class ItemCreate(BaseModel): + name: str + price: float = Field(gt=0) + +class ItemResponse(BaseModel): + id: int + name: str + price: float + deleted: bool + +class CartItem(BaseModel): + id: int + name: str + quantity: int + available: bool + +class CartResponse(BaseModel): + id: int + items: List[CartItem] + price: float + +# Вспомогательные функции +def get_next_item_id(): + global item_counter + item_counter += 1 + return item_counter + +def get_next_cart_id(): + global cart_counter + cart_counter += 1 + return cart_counter + +def calculate_cart_price(cart): + """ИСПРАВЛЕННАЯ ФУНКЦИЯ: не использует поле 'available'""" + total = 0.0 + for item in cart["items"]: + item_data = items_db.get(item["id"]) + # Проверяем доступность товара через items_db + if item_data and not item_data["deleted"]: + total += item_data["price"] * item["quantity"] + return total + +# API для корзин +@app.post("/cart", status_code=status.HTTP_201_CREATED) +async def create_cart(response: Response): + cart_id = get_next_cart_id() + carts_db[cart_id] = { + "id": cart_id, + "items": [] + } + response.headers["Location"] = f"/cart/{cart_id}" + return {"id": cart_id} + +@app.get("/cart/{cart_id}", response_model=CartResponse) +async def get_cart(cart_id: int): + if cart_id not in carts_db: + raise HTTPException(status_code=404, detail="Cart not found") + + cart = carts_db[cart_id] + response_items = [] + + for item in cart["items"]: + item_data = items_db.get(item["id"]) + available = item_data is not None and not item_data["deleted"] + response_items.append({ + "id": item["id"], + "name": item["name"], + "quantity": item["quantity"], + "available": available + }) + + price = calculate_cart_price(cart) + + return { + "id": cart_id, + "items": response_items, + "price": price + } + +@app.get("/cart") +async def get_carts( + offset: int = 0, + limit: int = 10, + min_price: Optional[float] = None, + max_price: Optional[float] = None, + min_quantity: Optional[int] = None, + max_quantity: Optional[int] = None +): + if offset < 0: + raise HTTPException(status_code=422, detail="Offset must be non-negative") + if limit <= 0: + raise HTTPException(status_code=422, detail="Limit must be positive") + if min_price is not None and min_price < 0: + raise HTTPException(status_code=422, detail="Min price must be non-negative") + if max_price is not None and max_price < 0: + raise HTTPException(status_code=422, detail="Max price must be non-negative") + if min_quantity is not None and min_quantity < 0: + raise HTTPException(status_code=422, detail="Min quantity must be non-negative") + if max_quantity is not None and max_quantity < 0: + raise HTTPException(status_code=422, detail="Max quantity must be non-negative") + + carts_list = list(carts_db.values()) + result = [] + + for cart in carts_list: + response_items = [] + for item in cart["items"]: + item_data = items_db.get(item["id"]) + available = item_data is not None and not item_data["deleted"] + response_items.append({ + "id": item["id"], + "name": item["name"], + "quantity": item["quantity"], + "available": available + }) + + price = calculate_cart_price(cart) + total_quantity = sum(item["quantity"] for item in response_items) + + # Фильтрация по цене + if min_price is not None and price < min_price: + continue + if max_price is not None and price > max_price: + continue + + # Фильтрация по количеству товаров + if min_quantity is not None and total_quantity < min_quantity: + continue + if max_quantity is not None and total_quantity > max_quantity: + continue + + result.append({ + "id": cart["id"], + "items": response_items, + "price": price + }) + + return result[offset:offset + limit] + +@app.post("/cart/{cart_id}/add/{item_id}") +async def add_to_cart(cart_id: int, item_id: int): + if cart_id not in carts_db: + raise HTTPException(status_code=404, detail="Cart not found") + if item_id not in items_db: + raise HTTPException(status_code=404, detail="Item not found") + + item_data = items_db[item_id] + cart = carts_db[cart_id] + + # Проверяем, есть ли товар уже в корзине + for cart_item in cart["items"]: + if cart_item["id"] == item_id: + cart_item["quantity"] += 1 + return {"message": "Item quantity increased"} + + # Добавляем новый товар + cart["items"].append({ + "id": item_id, + "name": item_data["name"], + "quantity": 1 + }) + + return {"message": "Item added to cart"} + +# API для товаров +@app.post("/item", status_code=status.HTTP_201_CREATED) +async def create_item(item: ItemCreate): + item_id = get_next_item_id() + items_db[item_id] = { + "id": item_id, + "name": item.name, + "price": item.price, + "deleted": False + } + return items_db[item_id] + +@app.get("/item/{item_id}") +async def get_item(item_id: int): + if item_id not in items_db: + raise HTTPException(status_code=404, detail="Item not found") + + item = items_db[item_id] + if item["deleted"]: + raise HTTPException(status_code=404, detail="Item not found") + + return item + +@app.get("/item") +async def get_items( + offset: int = 0, + limit: int = 10, + min_price: Optional[float] = None, + max_price: Optional[float] = None, + show_deleted: bool = False +): + if offset < 0: + raise HTTPException(status_code=422, detail="Offset must be non-negative") + if limit <= 0: + raise HTTPException(status_code=422, detail="Limit must be positive") + if min_price is not None and min_price < 0: + raise HTTPException(status_code=422, detail="Min price must be non-negative") + if max_price is not None and max_price < 0: + raise HTTPException(status_code=422, detail="Max price must be non-negative") + + items_list = list(items_db.values()) + + # Фильтрация по удаленным товарам + if not show_deleted: + items_list = [item for item in items_list if not item["deleted"]] + + # Фильтрация по цене + if min_price is not None: + items_list = [item for item in items_list if item["price"] >= min_price] + if max_price is not None: + items_list = [item for item in items_list if item["price"] <= max_price] + + return items_list[offset:offset + limit] + +@app.put("/item/{item_id}") +async def update_item(item_id: int, item: ItemCreate): + if item_id not in items_db: + raise HTTPException(status_code=404, detail="Item not found") + + existing_item = items_db[item_id] + if existing_item["deleted"]: + raise HTTPException(status_code=304) + + items_db[item_id].update({ + "name": item.name, + "price": item.price + }) + + return items_db[item_id] + +@app.patch("/item/{item_id}") +async def partial_update_item(item_id: int, item_update: Dict[str, Any]): + if item_id not in items_db: + raise HTTPException(status_code=404, detail="Item not found") + + existing_item = items_db[item_id] + if existing_item["deleted"]: + raise HTTPException(status_code=304) + + # Проверяем, что не пытаемся изменить поле deleted + if "deleted" in item_update: + raise HTTPException(status_code=422, detail="Cannot modify deleted field") + + # Проверяем, что все поля валидны + allowed_fields = {"name", "price"} + for field in item_update: + if field not in allowed_fields: + raise HTTPException(status_code=422, detail=f"Field {field} is not allowed") + + # Обновляем только переданные поля + for field, value in item_update.items(): + items_db[item_id][field] = value + + return items_db[item_id] + +@app.delete("/item/{item_id}") +async def delete_item(item_id: int): + if item_id not in items_db: + raise HTTPException(status_code=404, detail="Item not found") + + items_db[item_id]["deleted"] = True + return {"message": "Item deleted"} \ No newline at end of file From a4795b1b9a6db1f39b147445b265e3e2573877da Mon Sep 17 00:00:00 2001 From: Polina Kremneva <138605479+interpparietes@users.noreply.github.com> Date: Sat, 4 Oct 2025 16:38:53 +0300 Subject: [PATCH 3/5] Update test_homework2.py change path to test # from shop_api.main import app from hw.shop_api.main import app --- hw2/hw/test_homework2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw2/hw/test_homework2.py b/hw2/hw/test_homework2.py index 60a1f36a..b32126ed 100644 --- a/hw2/hw/test_homework2.py +++ b/hw2/hw/test_homework2.py @@ -6,7 +6,8 @@ from faker import Faker from fastapi.testclient import TestClient -from shop_api.main import app +# from shop_api.main import app +from hw.shop_api.main import app client = TestClient(app) faker = Faker() From 52d9f31e8db4e102ebd731fa646e09c160e3178d Mon Sep 17 00:00:00 2001 From: Polina Kremneva Date: Sat, 4 Oct 2025 17:10:35 +0300 Subject: [PATCH 4/5] Revert "add shop api" This reverts commit 9105c504f4f53a11dea02271954261dd5a238d24. revert changes for hw2 --- hw2/hw/shop_api/main.py | 281 +--------------------------------------- 1 file changed, 1 insertion(+), 280 deletions(-) diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index 30b29e99..f60a8c60 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,282 +1,3 @@ -from fastapi import FastAPI, HTTPException, status, Response -from pydantic import BaseModel, Field -from typing import List, Optional, Dict, Any -import uuid +from fastapi import FastAPI app = FastAPI(title="Shop API") - -# Хранение данных в памяти -items_db: Dict[int, Dict] = {} -carts_db: Dict[int, Dict] = {} - -# Счетчики для генерации ID -item_counter = 0 -cart_counter = 0 - -# Модели данных -class ItemCreate(BaseModel): - name: str - price: float = Field(gt=0) - -class ItemResponse(BaseModel): - id: int - name: str - price: float - deleted: bool - -class CartItem(BaseModel): - id: int - name: str - quantity: int - available: bool - -class CartResponse(BaseModel): - id: int - items: List[CartItem] - price: float - -# Вспомогательные функции -def get_next_item_id(): - global item_counter - item_counter += 1 - return item_counter - -def get_next_cart_id(): - global cart_counter - cart_counter += 1 - return cart_counter - -def calculate_cart_price(cart): - """ИСПРАВЛЕННАЯ ФУНКЦИЯ: не использует поле 'available'""" - total = 0.0 - for item in cart["items"]: - item_data = items_db.get(item["id"]) - # Проверяем доступность товара через items_db - if item_data and not item_data["deleted"]: - total += item_data["price"] * item["quantity"] - return total - -# API для корзин -@app.post("/cart", status_code=status.HTTP_201_CREATED) -async def create_cart(response: Response): - cart_id = get_next_cart_id() - carts_db[cart_id] = { - "id": cart_id, - "items": [] - } - response.headers["Location"] = f"/cart/{cart_id}" - return {"id": cart_id} - -@app.get("/cart/{cart_id}", response_model=CartResponse) -async def get_cart(cart_id: int): - if cart_id not in carts_db: - raise HTTPException(status_code=404, detail="Cart not found") - - cart = carts_db[cart_id] - response_items = [] - - for item in cart["items"]: - item_data = items_db.get(item["id"]) - available = item_data is not None and not item_data["deleted"] - response_items.append({ - "id": item["id"], - "name": item["name"], - "quantity": item["quantity"], - "available": available - }) - - price = calculate_cart_price(cart) - - return { - "id": cart_id, - "items": response_items, - "price": price - } - -@app.get("/cart") -async def get_carts( - offset: int = 0, - limit: int = 10, - min_price: Optional[float] = None, - max_price: Optional[float] = None, - min_quantity: Optional[int] = None, - max_quantity: Optional[int] = None -): - if offset < 0: - raise HTTPException(status_code=422, detail="Offset must be non-negative") - if limit <= 0: - raise HTTPException(status_code=422, detail="Limit must be positive") - if min_price is not None and min_price < 0: - raise HTTPException(status_code=422, detail="Min price must be non-negative") - if max_price is not None and max_price < 0: - raise HTTPException(status_code=422, detail="Max price must be non-negative") - if min_quantity is not None and min_quantity < 0: - raise HTTPException(status_code=422, detail="Min quantity must be non-negative") - if max_quantity is not None and max_quantity < 0: - raise HTTPException(status_code=422, detail="Max quantity must be non-negative") - - carts_list = list(carts_db.values()) - result = [] - - for cart in carts_list: - response_items = [] - for item in cart["items"]: - item_data = items_db.get(item["id"]) - available = item_data is not None and not item_data["deleted"] - response_items.append({ - "id": item["id"], - "name": item["name"], - "quantity": item["quantity"], - "available": available - }) - - price = calculate_cart_price(cart) - total_quantity = sum(item["quantity"] for item in response_items) - - # Фильтрация по цене - if min_price is not None and price < min_price: - continue - if max_price is not None and price > max_price: - continue - - # Фильтрация по количеству товаров - if min_quantity is not None and total_quantity < min_quantity: - continue - if max_quantity is not None and total_quantity > max_quantity: - continue - - result.append({ - "id": cart["id"], - "items": response_items, - "price": price - }) - - return result[offset:offset + limit] - -@app.post("/cart/{cart_id}/add/{item_id}") -async def add_to_cart(cart_id: int, item_id: int): - if cart_id not in carts_db: - raise HTTPException(status_code=404, detail="Cart not found") - if item_id not in items_db: - raise HTTPException(status_code=404, detail="Item not found") - - item_data = items_db[item_id] - cart = carts_db[cart_id] - - # Проверяем, есть ли товар уже в корзине - for cart_item in cart["items"]: - if cart_item["id"] == item_id: - cart_item["quantity"] += 1 - return {"message": "Item quantity increased"} - - # Добавляем новый товар - cart["items"].append({ - "id": item_id, - "name": item_data["name"], - "quantity": 1 - }) - - return {"message": "Item added to cart"} - -# API для товаров -@app.post("/item", status_code=status.HTTP_201_CREATED) -async def create_item(item: ItemCreate): - item_id = get_next_item_id() - items_db[item_id] = { - "id": item_id, - "name": item.name, - "price": item.price, - "deleted": False - } - return items_db[item_id] - -@app.get("/item/{item_id}") -async def get_item(item_id: int): - if item_id not in items_db: - raise HTTPException(status_code=404, detail="Item not found") - - item = items_db[item_id] - if item["deleted"]: - raise HTTPException(status_code=404, detail="Item not found") - - return item - -@app.get("/item") -async def get_items( - offset: int = 0, - limit: int = 10, - min_price: Optional[float] = None, - max_price: Optional[float] = None, - show_deleted: bool = False -): - if offset < 0: - raise HTTPException(status_code=422, detail="Offset must be non-negative") - if limit <= 0: - raise HTTPException(status_code=422, detail="Limit must be positive") - if min_price is not None and min_price < 0: - raise HTTPException(status_code=422, detail="Min price must be non-negative") - if max_price is not None and max_price < 0: - raise HTTPException(status_code=422, detail="Max price must be non-negative") - - items_list = list(items_db.values()) - - # Фильтрация по удаленным товарам - if not show_deleted: - items_list = [item for item in items_list if not item["deleted"]] - - # Фильтрация по цене - if min_price is not None: - items_list = [item for item in items_list if item["price"] >= min_price] - if max_price is not None: - items_list = [item for item in items_list if item["price"] <= max_price] - - return items_list[offset:offset + limit] - -@app.put("/item/{item_id}") -async def update_item(item_id: int, item: ItemCreate): - if item_id not in items_db: - raise HTTPException(status_code=404, detail="Item not found") - - existing_item = items_db[item_id] - if existing_item["deleted"]: - raise HTTPException(status_code=304) - - items_db[item_id].update({ - "name": item.name, - "price": item.price - }) - - return items_db[item_id] - -@app.patch("/item/{item_id}") -async def partial_update_item(item_id: int, item_update: Dict[str, Any]): - if item_id not in items_db: - raise HTTPException(status_code=404, detail="Item not found") - - existing_item = items_db[item_id] - if existing_item["deleted"]: - raise HTTPException(status_code=304) - - # Проверяем, что не пытаемся изменить поле deleted - if "deleted" in item_update: - raise HTTPException(status_code=422, detail="Cannot modify deleted field") - - # Проверяем, что все поля валидны - allowed_fields = {"name", "price"} - for field in item_update: - if field not in allowed_fields: - raise HTTPException(status_code=422, detail=f"Field {field} is not allowed") - - # Обновляем только переданные поля - for field, value in item_update.items(): - items_db[item_id][field] = value - - return items_db[item_id] - -@app.delete("/item/{item_id}") -async def delete_item(item_id: int): - if item_id not in items_db: - raise HTTPException(status_code=404, detail="Item not found") - - items_db[item_id]["deleted"] = True - return {"message": "Item deleted"} \ No newline at end of file From ff452f095ea8c5952f603bf5fc731e8e6e3596a4 Mon Sep 17 00:00:00 2001 From: Polina Kremneva Date: Sat, 4 Oct 2025 17:35:25 +0300 Subject: [PATCH 5/5] Revert "Update test_homework2.py" This reverts commit a4795b1b9a6db1f39b147445b265e3e2573877da. --- hw2/hw/test_homework2.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hw2/hw/test_homework2.py b/hw2/hw/test_homework2.py index b32126ed..60a1f36a 100644 --- a/hw2/hw/test_homework2.py +++ b/hw2/hw/test_homework2.py @@ -6,8 +6,7 @@ from faker import Faker from fastapi.testclient import TestClient -# from shop_api.main import app -from hw.shop_api.main import app +from shop_api.main import app client = TestClient(app) faker = Faker()