From a6b752b5e9b3a077f9aba194f0b4f3b3cf108d05 Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Tue, 23 Sep 2025 22:15:04 +0700 Subject: [PATCH 1/8] Implement ASGI application with fibonacci, factorial, and mean endpoints --- hw1/app.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/hw1/app.py b/hw1/app.py index 6107b870..112daa23 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -1,6 +1,71 @@ from typing import Any, Awaitable, Callable +import json +import re + +def parse_query_string(qs): + """ + Парсит query string в словарь списков: 'n=5&x=3' → {'n': ['5'], 'x': ['3']} + """ + if not qs: + return {} + parsed = {} + pairs = qs.split('&') + for pair in pairs: + if '=' in pair: + key, value = pair.split('=', 1) + key = unquote(key) + value = unquote(value) + if key in parsed: + parsed[key].append(value) + else: + parsed[key] = [value] + return parsed + + +def unquote(string): + """ + Минимальная реализация URL-декодирования без urllib. + Поддерживает %XX и замену '+' на пробел. + """ + string = string.replace('+', ' ') + res = "" + i = 0 + while i < len(string): + if string[i] == '%' and i + 2 < len(string): + try: + hex_val = string[i+1:i+3] + byte_val = bytes.fromhex(hex_val) + char = byte_val.decode('utf-8') + res += char + i += 3 + continue + except Exception: + pass + res += string[i] + i += 1 + return res + + +async def send_json(send, status, data): + """ + Отправляет JSON-ответ. Гарантированно совместима с ASGI/h11. + """ + body = json.dumps(data, ensure_ascii=False).encode('utf-8') + headers = [ + (b'content-type', b'application/json'), + ] + await send({ + 'type': 'http.response.start', + 'status': status, + 'headers': headers, + }) + await send({ + 'type': 'http.response.body', + 'body': body, + }) + async def application( scope: dict[str, Any], receive: Callable[[], Awaitable[dict[str, Any]]], @@ -14,6 +79,137 @@ async def application( """ # TODO: Ваша реализация здесь + if scope["type"] != "http": + await send({ + "type": "http.response.start", + "status": 400, + "headers": [(b'content-type', b'text/plain')] + }) + + await send({ + "type": "http.response.body", + "body": b"Only HTTP supported" + }) + return + + path = scope["path"] + method = scope["method"] + + if method != "GET": + await send_json(send, 404, {"error": "Only GET methods allowed"}) + return + + if path == "/factorial" and method == "GET": + await handle_factorial(scope, receive, send) + + elif path.startswith("/fibonacci") and method == "GET": + await handle_fibonacci(scope, receive, send) + + elif path == "/mean" and method == "GET": + await handle_mean(scope, receive, send) + + else: + await send_json(send, 404, {"error": "Not Found"}) + + +async def handle_factorial(scope, recieve, send): + query_string = scope["query_string"].decode("utf-8") + params = parse_query_string(query_string) + + if "n" not in params or len(params["n"]) == 0: + await send_json(send, 422, {"error": "Missing query parameter: n"}) + return + + n_str = params["n"][0] + + try: + n = int(n_str) + except ValueError: + await send_json(send, 422, {"error": "n must be an integer"}) + return + + if n < 0: + await send_json(send, 400, {"error": "n must be a non-negative integer"}) + return + + result = 1 + for i in range(1, n + 1): + result *= i + + await send_json(send, 200, {"result": result}) + + + +async def handle_fibonacci(scope, recieve, send): + + path = scope["path"] + match = re.match(r"/fibonacci/(-?\d+)", path) + + if not match: + await send_json(send, 422, {"error": "Invalid path format. Expected /fibonacci/"}) + return + + try: + n = int(match.group(1)) + except ValueError: + await send_json(send, 422, {"error": "n must be an integer"}) + return + + if n < 0: + await send_json(send, 400, {"error": "n must be a non-negative integer"}) + return + + if n == 0: + fib = 0 + + elif n == 1: + fib = 1 + + else: + a, b = 0, 1 + for i in range(2, n + 1): + a, b = b, a + b + fib = b + + await send_json(send, 200, {"result": fib}) + + + + +async def handle_mean(scope, recieve, send): + body = b"" + more_body = True + + while more_body: + message = await recieve() + body += message.get("body", b"") + more_body = message.get("more_body", False) + + if not body: + await send_json(send, 422, {"error": "No body received"}) + return + + try: + data = json.loads(body) + if not isinstance(data, list): + raise ValueError("Expected JSON Array") + + numbers = [float(x) for x in data] + + except (json.JSONDecodeError, ValueError, TypeError, OverflowError): + await send_json(send, 422, {"error": "All values must be numbers"}) + return + + if len(numbers) == 0: + await send_json(send, 400, {"error": "No values received"}) + return + + mean_value = sum(numbers) / len(numbers) + await send_json(send, 200, {"result": mean_value}) + + + + 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 27efd2897670e02bc67139f39d84906a94caa09d Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sat, 4 Oct 2025 09:21:07 +0700 Subject: [PATCH 2/8] =?UTF-8?q?hw2.=20=D0=93=D0=BB=D1=83=D1=85=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B9=20(=D0=BE=D1=81?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hw2/hw/shop_api/main.py | 4 + hw2/hw/shop_api/routers/__init__.py | 2 + hw2/hw/shop_api/routers/cart.py | 95 ++++++++++++++++ hw2/hw/shop_api/routers/item.py | 170 ++++++++++++++++++++++++++++ hw2/hw/shop_api/schemas.py | 36 ++++++ hw2/hw/store/__init__.py | 0 hw2/hw/store/models.py | 26 +++++ hw2/hw/store/queries/__init__.py | 0 hw2/hw/store/queries/cart.py | 154 +++++++++++++++++++++++++ hw2/hw/store/queries/item.py | 101 +++++++++++++++++ hw2/hw/store/queries/utils.py | 9 ++ 11 files changed, 597 insertions(+) create mode 100644 hw2/hw/shop_api/routers/__init__.py create mode 100644 hw2/hw/shop_api/routers/cart.py create mode 100644 hw2/hw/shop_api/routers/item.py create mode 100644 hw2/hw/shop_api/schemas.py create mode 100644 hw2/hw/store/__init__.py create mode 100644 hw2/hw/store/models.py create mode 100644 hw2/hw/store/queries/__init__.py create mode 100644 hw2/hw/store/queries/cart.py create mode 100644 hw2/hw/store/queries/item.py create mode 100644 hw2/hw/store/queries/utils.py diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index f60a8c60..7c801dc5 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,3 +1,7 @@ from fastapi import FastAPI +from shop_api.routers import cart_router, item_router + app = FastAPI(title="Shop API") +app.include_router(cart_router) +app.include_router(item_router) \ No newline at end of file diff --git a/hw2/hw/shop_api/routers/__init__.py b/hw2/hw/shop_api/routers/__init__.py new file mode 100644 index 00000000..ddae3a8b --- /dev/null +++ b/hw2/hw/shop_api/routers/__init__.py @@ -0,0 +1,2 @@ +from shop_api.routers.cart import router as cart_router +from shop_api.routers.item import router as item_router \ No newline at end of file diff --git a/hw2/hw/shop_api/routers/cart.py b/hw2/hw/shop_api/routers/cart.py new file mode 100644 index 00000000..6345108f --- /dev/null +++ b/hw2/hw/shop_api/routers/cart.py @@ -0,0 +1,95 @@ +from fastapi import APIRouter, status, Response, Query +from fastapi.responses import JSONResponse +from typing import List, Optional + +from store.queries.cart import ( + add_cart, get_cart_by_id, + list_carts, add_item_to_cart +) +from shop_api.schemas import ( + CartResponse, + CartCreateResponse, + Msg +) + + + + +router = APIRouter( + prefix="/cart", + tags=["Cart"] +) + + +@router.post( + path="", + response_model=CartCreateResponse, + status_code=status.HTTP_201_CREATED +) +def create_cart(response: Response): + cart_id = add_cart() + response.headers["Location"] = f"/cart/{cart_id}" + return CartCreateResponse(id=cart_id) + + +@router.get( + path="/{id}", + response_model=CartResponse +) +async def get_cart(id: int): + cart = get_cart_by_id(id) + print(cart) + if cart is None: + return JSONResponse( + content=Msg(msg="Корзина не найдена").model_dump(), + status_code=404 + ) + + return cart + + +@router.get( + path="", + response_model=List[CartResponse] +) +async def get_list_carts( + offset: Optional[int] = Query(None, ge=0), + limit: Optional[int] = Query(None, ge=1), + min_price: Optional[float] = Query(None, ge=0.0), + max_price: Optional[float] = Query(None, ge=0.0), + min_quantity: Optional[int] = Query(None, ge=0), + max_quantity: Optional[int] = Query(None, ge=0) +): + + carts = list_carts( + offset=offset, + limit=limit, + min_price=min_price, + max_price=max_price, + min_quantity=min_quantity, + max_quantity=max_quantity + ) + + return carts + + +@router.post( + path="/{cart_id}/add/{item_id}", + response_model=CartResponse +) +async def add_item_to_cart_endpoint( + cart_id: int, + item_id: int +): + cart = add_item_to_cart( + cart_id=cart_id, + item_id=item_id + ) + + if not cart: + return JSONResponse( + content=Msg(msg="Ничего не найдено").model_dump(), + status_code=404 + ) + print(cart) + return cart \ No newline at end of file diff --git a/hw2/hw/shop_api/routers/item.py b/hw2/hw/shop_api/routers/item.py new file mode 100644 index 00000000..db7d3836 --- /dev/null +++ b/hw2/hw/shop_api/routers/item.py @@ -0,0 +1,170 @@ +from fastapi import APIRouter, status, Response, Query +from fastapi.responses import JSONResponse +from http import HTTPStatus +from typing import List, Optional + +from store.queries.item import ( + add_item, get_item_by_id, + list_items, update_item_full, + update_item_partial, delete_item, + data_item +) +from shop_api.schemas import ( + ItemCreate, + ItemResponse, + CartResponse, + Msg +) + + + + +router = APIRouter( + prefix="/item", + tags=["Item"] +) + + +@router.post( + path="", + response_model=ItemResponse, + status_code=status.HTTP_201_CREATED +) +async def create_item_endpoint(item: ItemCreate): + if not item.price or not item.name: + return JSONResponse( + content=Msg( + msg="Не заполнены нужные поля" + ).model_dump(), + status_code=422 + ) + new_item = add_item( + name=item.name, + price=item.price, + ) + return new_item + + +@router.get( + path="/{id}", + response_model=ItemResponse +) +async def get_item_endpoint(id: int): + item = get_item_by_id(id) + if not item: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + + return item + + +@router.get( + path="", + response_model=List[ItemResponse] +) +async def get_list_items_endpoint( + offset: Optional[int] = Query(None, ge=0), + limit: Optional[int] = Query(None, ge=1), + min_price: Optional[float] = Query(None, ge=0.0), + max_price: Optional[float] = Query(None, ge=0.0), + show_deleted: Optional[bool] = Query(False) +): + items = list_items( + offset=offset, + limit=limit, + min_price=min_price, + max_price=max_price, + show_deleted=show_deleted + ) + + return items + + +@router.put( + path="/{id}", + response_model=ItemResponse +) +async def update_full_item_endpoint( + id: int, + item: ItemCreate +): + + if not item.price or not item.name: + return JSONResponse( + content=Msg( + msg="Отсутствуют некоторые параметры" + ).model_dump(), + status_code=422 + + ) + if id not in data_item: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + + item = update_item_full( + item_id=id, + name=item.name, + price=item.price + ) + + return item + + +@router.patch( + path="/{id}", + response_model=ItemResponse +) +async def update_item_partial_endpoint( + id: int, + item: ItemCreate +): + + if id not in data_item: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + + if data_item[id].deleted: + return JSONResponse( + content=Msg( + msg="Айтем удален" + ).model_dump(), + status_code=304 + ) + + item = update_item_partial( + item_id=id, + name=item.name, + price=item.price, + ) + + return item + + +@router.delete( + path="/{id}", + response_model=ItemResponse +) +async def delete_item_endpoint(id: int): + item = delete_item(id) + + if not item: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=HTTPStatus.NOT_FOUND + ) + + return item \ No newline at end of file diff --git a/hw2/hw/shop_api/schemas.py b/hw2/hw/shop_api/schemas.py new file mode 100644 index 00000000..9ae1402b --- /dev/null +++ b/hw2/hw/shop_api/schemas.py @@ -0,0 +1,36 @@ +from pydantic import BaseModel, ConfigDict +from typing import List, Optional + + + +class CartCreateResponse(BaseModel): + id: int + +class CartItemResponse(BaseModel): + id: int + name: str + quantity: float + available: bool + + +class CartResponse(BaseModel): + id: int + items: List[CartItemResponse] = [] + price: float + + +class ItemCreate(BaseModel): + model_config = ConfigDict(extra="forbid") + name: Optional[str] = None + price: Optional[float] = None + + +class ItemResponse(BaseModel): + id: int + name: str + price: float + deleted: bool + + +class Msg(BaseModel): + msg: str \ No newline at end of file diff --git a/hw2/hw/store/__init__.py b/hw2/hw/store/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/store/models.py b/hw2/hw/store/models.py new file mode 100644 index 00000000..d2fedf44 --- /dev/null +++ b/hw2/hw/store/models.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from typing import List + + + +@dataclass(slots=True) +class Item: + id: int + name: str + price: float + deleted: bool + + +@dataclass(slots=True) +class CartItem: + id: int + name: str + quantity: int + available: bool + + +@dataclass(slots=True) +class Cart: + id: int + items: List[CartItem] + price: float = 0.0 \ No newline at end of file diff --git a/hw2/hw/store/queries/__init__.py b/hw2/hw/store/queries/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hw2/hw/store/queries/cart.py b/hw2/hw/store/queries/cart.py new file mode 100644 index 00000000..d67f8e88 --- /dev/null +++ b/hw2/hw/store/queries/cart.py @@ -0,0 +1,154 @@ +from typing import Optional, List + +from store.queries.utils import id_generator +from store.queries.item import get_item_by_id, data_item +from store.models import ( + Cart, + Item, + CartItem +) + + + +data_cart = dict[int, Cart]() +cart_id_generator = id_generator() + + + +def add_cart() -> int: + new_cart_id = next(cart_id_generator) + data_cart[new_cart_id] = Cart(new_cart_id, []) + return new_cart_id + + +def get_cart_by_id(id: int) -> Cart | None: + if id not in data_cart: + return None + cart = data_cart[id] + if cart.items: + recalculate_cart(cart) + return data_cart[id] + + +def recalculate_cart(cart: Cart): + total = 0.0 + for item in cart.items: + cart_item = get_item_by_id(item.id) + print(cart_item) + if cart_item and not cart_item.deleted: + item.available = True + total += cart_item.price * item.quantity + else: + item.available = False + + cart.price = total + + +def update_item_full( + item_id: int, + name: Optional[str] = None, + price: Optional[float] = None, + deleted: Optional[bool] = None, +) -> Item | None: + if not item_id in data_item: + return None + + if name: + data_item[item_id].name = name + + if price: + data_item[item_id].price = price + + if deleted is not None: + data_item[item_id].deleted = deleted + + return data_item[item_id] + + +def update_item_partial( + item_id: int, + name: Optional[str] = None, + price: Optional[float] = None +) -> Item | None: + + if item_id not in data_item: + return None + + if name: + data_item[item_id].name = name + + if price: + data_item[item_id].price = price + + return data_item[item_id] + + +def list_carts( + offset: Optional[int] = None, + limit: Optional[int] = None, + min_price: Optional[float] = None, + max_price: Optional[float] = None, + min_quantity: Optional[int] = None, + max_quantity: Optional[int] = None +) -> List[Cart]: + + result = [] + for cart in data_cart.values(): + if cart.items: + recalculate_cart(cart) + total_quantity = sum(item.quantity for item in cart.items) + + if min_price is not None and cart.price < min_price: + continue + + if max_price is not None and cart.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(cart) + + offset = offset or 0 + limit = limit or len(result) + + return result[offset:offset + limit] + + +def add_item_to_cart( + cart_id: int, + item_id: int +) -> Cart | None: + + if not cart_id in data_cart or \ + not item_id in data_item: + return None + + cart = get_cart_by_id(cart_id) + item = get_item_by_id(item_id) + + if item.deleted: + return None + + added = False + if cart.items: + for cart_item in cart.items: + if cart_item.id == item_id: + added = True + cart_item.quantity += 1 + break + + if not added: + cart.items.append( + CartItem( + id=item_id, + name=item.name, + quantity=1, + available=True + ) + ) + + return cart \ No newline at end of file diff --git a/hw2/hw/store/queries/item.py b/hw2/hw/store/queries/item.py new file mode 100644 index 00000000..2a91c2a7 --- /dev/null +++ b/hw2/hw/store/queries/item.py @@ -0,0 +1,101 @@ +from typing import Optional, List + +from store.models import Item +from store.queries.utils import id_generator + + + +data_item = dict[int, Item]() +item_id_generator = id_generator() + + +def add_item( + name: str, + price: float +) -> Item: + new_item_id = next(item_id_generator) + new_item = Item(new_item_id, name, price, False) + data_item[new_item_id] = new_item + return new_item + + +def delete_item(item_id: int) -> Item | None: + if item_id not in data_item: + return None + + data_item[item_id].deleted = True + return data_item[item_id] + + +def get_item_by_id(id: int) -> Item | None: + if not id in data_item: + return None + + item = data_item[id] + + if item.deleted: + return None + + return data_item[id] + + +def list_items( + offset: Optional[int] = None, + limit: Optional[int] = None, + min_price: Optional[float] = None, + max_price: Optional[float] = None, + show_deleted: Optional[bool] = False +) -> List[Item] | None: + + items = list(data_item.values()) + if show_deleted is not None and show_deleted is False: + items = [item for item in items if not item.deleted] + + if min_price is not None: + items = [item for item in items if item.price >= min_price] + + if max_price is not None: + items = [item for item in items if item.price <= max_price] + + offset = offset or 0 + limit = limit or len(items) + + return items[offset:offset + limit] + +def update_item_full( + item_id: int, + name: Optional[str] = None, + price: Optional[float] = None, + deleted: Optional[bool] = None, +) -> Item | None: + if not item_id in data_item: + return None + + if name: + data_item[item_id].name = name + + if price: + data_item[item_id].price = price + + if deleted is not None: + data_item[item_id].deleted = deleted + + return data_item[item_id] + + +def update_item_partial( + item_id: int, + name: Optional[str] = None, + price: Optional[float] = None +) -> Item | None: + + if item_id not in data_item: + return None + + if name: + data_item[item_id].name = name + + if price: + data_item[item_id].price = price + + return data_item[item_id] \ No newline at end of file diff --git a/hw2/hw/store/queries/utils.py b/hw2/hw/store/queries/utils.py new file mode 100644 index 00000000..20955f51 --- /dev/null +++ b/hw2/hw/store/queries/utils.py @@ -0,0 +1,9 @@ +from typing import Iterable + + + +def id_generator() -> Iterable[int]: + i = 0 + while True: + yield i + i += 1 \ No newline at end of file From 126163cb2f73b56cf05d23dd083d40c9684a0cc0 Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sat, 4 Oct 2025 17:33:24 +0700 Subject: [PATCH 3/8] add websockets --- hw2/hw/client_websockets.py | 68 ++++++++++++++++++ hw2/hw/requirements.txt | 3 +- hw2/hw/shop_api/chat_manager/__init__.py | 1 + hw2/hw/shop_api/chat_manager/chat_manager.py | 73 ++++++++++++++++++++ hw2/hw/shop_api/main.py | 5 +- hw2/hw/shop_api/routers/__init__.py | 3 +- hw2/hw/shop_api/routers/chat.py | 22 ++++++ hw2/hw/test_websockets.py | 36 ++++++++++ 8 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 hw2/hw/client_websockets.py create mode 100644 hw2/hw/shop_api/chat_manager/__init__.py create mode 100644 hw2/hw/shop_api/chat_manager/chat_manager.py create mode 100644 hw2/hw/shop_api/routers/chat.py create mode 100644 hw2/hw/test_websockets.py diff --git a/hw2/hw/client_websockets.py b/hw2/hw/client_websockets.py new file mode 100644 index 00000000..016d972a --- /dev/null +++ b/hw2/hw/client_websockets.py @@ -0,0 +1,68 @@ +import asyncio +import websockets + + +DEFAULT_HOST = "localhost" +DEFAULT_PORT = 8000 + + +async def chat_client(room_name: str, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT): + uri = f"ws://{host}:{port}/chat/{room_name}" + print(f"Подключение к комнате '{room_name}' по адресу {uri}...") + + try: + async with websockets.connect(uri) as websocket: + + welcome = await websocket.recv() + print(f"{welcome}\n") + print("Чат запущен! Введите сообщение и нажмите Enter.") + print("Чтобы выйти — введите 'quit' или нажмите Ctrl+C.\n") + + async def receive_messages(): + try: + while True: + message = await websocket.recv() + print(f"\r{message}") + print("Вы: ", end="", flush=True) + except websockets.exceptions.ConnectionClosed: + print("\nСоединение закрыто сервером.") + return + + async def send_messages(): + loop = asyncio.get_event_loop() + while True: + + msg = await loop.run_in_executor(None, input, "Вы: ") + if msg.strip().lower() == 'quit': + break + if msg.strip(): + await websocket.send(msg.strip()) + + await asyncio.gather(receive_messages(), send_messages()) + + except KeyboardInterrupt: + print("\nВыход по запросу пользователя.") + except Exception as e: + print(f"\nОшибка подключения: {e}") + print("Убедитесь, что сервер запущен: uvicorn main:app") + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description="WebSocket чат-клиент") + parser.add_argument("room", help="Имя комнаты для подключения") + parser.add_argument("--host", default=DEFAULT_HOST, help="Хост сервера (по умолчанию: localhost)") + parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="Порт сервера (по умолчанию: 8000)") + + args = parser.parse_args() + + try: + asyncio.run(chat_client(args.room, args.host, args.port)) + except KeyboardInterrupt: + + print("\nДо встречи!") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/hw2/hw/requirements.txt b/hw2/hw/requirements.txt index 207dcf5c..fae256c2 100644 --- a/hw2/hw/requirements.txt +++ b/hw2/hw/requirements.txt @@ -1,9 +1,10 @@ # Основные зависимости для ASGI приложения fastapi>=0.117.1 uvicorn>=0.24.0 +websockets>=1.8.0 # Зависимости для тестирования pytest>=7.4.0 pytest-asyncio>=0.21.0 httpx>=0.27.2 -Faker>=37.8.0 +Faker>=37.8.0 \ No newline at end of file diff --git a/hw2/hw/shop_api/chat_manager/__init__.py b/hw2/hw/shop_api/chat_manager/__init__.py new file mode 100644 index 00000000..5fcbda19 --- /dev/null +++ b/hw2/hw/shop_api/chat_manager/__init__.py @@ -0,0 +1 @@ +from shop_api.chat_manager.chat_manager import chat_manager \ No newline at end of file diff --git a/hw2/hw/shop_api/chat_manager/chat_manager.py b/hw2/hw/shop_api/chat_manager/chat_manager.py new file mode 100644 index 00000000..a73bba87 --- /dev/null +++ b/hw2/hw/shop_api/chat_manager/chat_manager.py @@ -0,0 +1,73 @@ +import uuid +from typing import List, Dict +from fastapi import APIRouter, WebSocket, WebSocketDisconnect + + + +class ChatManager: + def __init__(self): + + self.rooms: Dict[str, List[WebSocket]] = {} + self.user_data: Dict[WebSocket, tuple[str, str]] = {} + + def generate_username(self) -> str: + return f"user-{uuid.uuid4().hex}" + + async def connect(self, websocket: WebSocket, chat_name: str) -> str: + + await websocket.accept() + username = self.generate_username() + + if chat_name not in self.rooms: + self.rooms[chat_name] = [] + + self.rooms[chat_name].append(websocket) + self.user_data[websocket] = (username, chat_name) + + await websocket.send_text(f"Вы подключены как: {username}") + return username + + + async def disconnect(self, websocket: WebSocket): + + if websocket not in self.user_data: + return + + username, chat_name = self.user_data[websocket] + del self.user_data[websocket] + + if chat_name in self.rooms: + if websocket in self.rooms[chat_name]: + self.rooms[chat_name].remove(websocket) + # Удаляем пустую комнату + if not self.rooms[chat_name]: + del self.rooms[chat_name] + + + async def publish(self, websocket: WebSocket, message: str): + + if websocket not in self.user_data: + return + + username, chat_name = self.user_data[websocket] + full_message = f"{username} :: {message}" + + if chat_name not in self.rooms: + return + + disconnected = [] + for client in self.rooms[chat_name]: + try: + await client.send_text(full_message) + except Exception: + disconnected.append(client) + + for client in disconnected: + if client in self.rooms[chat_name]: + self.rooms[chat_name].remove(client) + if client in self.user_data: + del self.user_data[client] + + + +chat_manager = ChatManager() \ No newline at end of file diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index 7c801dc5..53442985 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,7 +1,8 @@ from fastapi import FastAPI -from shop_api.routers import cart_router, item_router +from shop_api.routers import cart_router, item_router, chat_router app = FastAPI(title="Shop API") app.include_router(cart_router) -app.include_router(item_router) \ No newline at end of file +app.include_router(item_router) +app.include_router(chat_router) \ No newline at end of file diff --git a/hw2/hw/shop_api/routers/__init__.py b/hw2/hw/shop_api/routers/__init__.py index ddae3a8b..5bc6a52f 100644 --- a/hw2/hw/shop_api/routers/__init__.py +++ b/hw2/hw/shop_api/routers/__init__.py @@ -1,2 +1,3 @@ from shop_api.routers.cart import router as cart_router -from shop_api.routers.item import router as item_router \ No newline at end of file +from shop_api.routers.item import router as item_router +from shop_api.routers.chat import router as chat_router \ No newline at end of file diff --git a/hw2/hw/shop_api/routers/chat.py b/hw2/hw/shop_api/routers/chat.py new file mode 100644 index 00000000..796cff91 --- /dev/null +++ b/hw2/hw/shop_api/routers/chat.py @@ -0,0 +1,22 @@ +from fastapi import APIRouter, WebSocket, WebSocketDisconnect +from shop_api.chat_manager import chat_manager + + + +router = APIRouter( + prefix="/chat", + tags=["Chat"] +) + + +@router.websocket("/{chat_name}") +async def websocket_endpoint(websocket: WebSocket, chat_name: str): + try: + await chat_manager.connect(websocket, chat_name) + while True: + data = await websocket.receive_text() + await chat_manager.publish(websocket, data) + except WebSocketDisconnect: + pass + finally: + await chat_manager.disconnect(websocket) \ No newline at end of file diff --git a/hw2/hw/test_websockets.py b/hw2/hw/test_websockets.py new file mode 100644 index 00000000..b2a9c66f --- /dev/null +++ b/hw2/hw/test_websockets.py @@ -0,0 +1,36 @@ +import pytest +from fastapi.testclient import TestClient +from shop_api.main import app + + +client = TestClient(app) + + +def test_websocket_connection(): + + with client.websocket_connect("/chat/testroom") as websocket: + + data = websocket.receive_text() + assert data.startswith("Вы подключены как: user-") + + websocket.send_text("Привет из теста!") + + response = websocket.receive_text() + assert " :: Привет из теста!" in response + assert response.startswith("user-") + + +def test_message_format_with_extracted_username(): + with client.websocket_connect("/chat/test") as ws: + + welcome = ws.receive_text() + assert welcome.startswith("Вы подключены как: ") + username = welcome.replace("Вы подключены как: ", "").strip() + + test_msg = "Привет, это тест!" + ws.send_text(test_msg) + + response = ws.receive_text() + + expected = f"{username} :: {test_msg}" + assert response == expected, f"Ожидалось '{expected}', получено '{response}'" \ No newline at end of file From 204bbc923c5098e23e8e802b8c67de607a18b355 Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sat, 11 Oct 2025 14:01:42 +0700 Subject: [PATCH 4/8] add hw3 --- hw2/hw/Dockerfile | 23 ++++++++++++ hw2/hw/docker-compose.yml | 41 ++++++++++++++++++++++ hw2/hw/requirements.txt | 5 ++- hw2/hw/screenshots_grafana/1.png | Bin 0 -> 105312 bytes hw2/hw/screenshots_grafana/2.png | Bin 0 -> 96097 bytes hw2/hw/settings/prometheus/prometheus.yml | 10 ++++++ hw2/hw/shop_api/main.py | 4 +++ 7 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 hw2/hw/Dockerfile create mode 100644 hw2/hw/docker-compose.yml create mode 100644 hw2/hw/screenshots_grafana/1.png create mode 100644 hw2/hw/screenshots_grafana/2.png create mode 100644 hw2/hw/settings/prometheus/prometheus.yml diff --git a/hw2/hw/Dockerfile b/hw2/hw/Dockerfile new file mode 100644 index 00000000..1dd029ce --- /dev/null +++ b/hw2/hw/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12 AS base + +ARG PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=on \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=500 + +RUN apt-get update && apt-get install -y gcc +RUN python -m pip install --upgrade pip + +WORKDIR app +COPY . . + +ENV VIRTUAL_ENV=app/.venv \ + PATH=app/.venv/bin:$PATH + +RUN pip install -r requirements.txt + +FROM base as local + +CMD ["uvicorn", "shop_api.main:app", "--port", "8080", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/hw2/hw/docker-compose.yml b/hw2/hw/docker-compose.yml new file mode 100644 index 00000000..d2d1f35a --- /dev/null +++ b/hw2/hw/docker-compose.yml @@ -0,0 +1,41 @@ +version: "3" + +services: + + local: + build: + context: . + dockerfile: ./Dockerfile + target: local + restart: always + ports: + - 8080:8080 + networks: + - monitor-net + + grafana: + image: grafana/grafana:latest + ports: + - 3000:3000 + restart: always + networks: + - monitor-net + + prometheus: + image: prom/prometheus + volumes: + - ./settings/prometheus/:/etc/prometheus/ + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.console.libraries=/usr/share/prometheus/console_libraries" + - "--web.console.templates=/usr/share/prometheus/consoles" + ports: + - 9090:9090 + restart: always + networks: + - monitor-net + + +networks: + monitor-net: \ No newline at end of file diff --git a/hw2/hw/requirements.txt b/hw2/hw/requirements.txt index fae256c2..974044ed 100644 --- a/hw2/hw/requirements.txt +++ b/hw2/hw/requirements.txt @@ -7,4 +7,7 @@ websockets>=1.8.0 pytest>=7.4.0 pytest-asyncio>=0.21.0 httpx>=0.27.2 -Faker>=37.8.0 \ No newline at end of file +Faker>=37.8.0 + +# prometheus +prometheus-fastapi-instrumentator==7.1.0 \ No newline at end of file diff --git a/hw2/hw/screenshots_grafana/1.png b/hw2/hw/screenshots_grafana/1.png new file mode 100644 index 0000000000000000000000000000000000000000..8d75ba36327d90b88c5711856749c1448cb85cfe GIT binary patch literal 105312 zcmeFZWmsL?k~WIF1PN}zgDnEV-3bKu;2zxFodkEc0KwgL;cmg*-4^b0SGxQ3>AiQ~ zFMsa$@AAy~tU2fK8dYP|sCtWU^0E@BNCZd_5D=)JBt;b=AmHR7ARrkK;J{y6Szp${ zFIr|IBJ!U^M9AdrZA{E8jUgatW2~Y_rJ)qCd&l#PljTLj!l81slLI3reg+|?$dj4J zob>JG`H@y|U}4l+uP`Aj*6oub+UY|{MRPV;*$=k#i1;UxuO?|#-mBY>?S2`K`_vn16AG`c zFpMRD3GZO#o)a;g&v;W8u)JEJmN@khzDS}f@FOMyStPSE_htO}t9RW_R1Q)kF@j~b zFZt(YLaLx#m<&!oBs^gR%#aUa#4%j2W8=Sy=Q>8X)|*{E)LqS=~`x<}vUd2dVSxt- z_?-gyz{Cvr@4CT7d4GT9mN#=Xw$ue=8u=?5C#-tG{m1SL0Krr?CtGNK1?#mKV0i5*f)R51Fj^Lsso?4O0-c9 zic;|Zc**Y~PWUPAwm?Asv70@Z+0ayp2)Cd9H1Hu_NN)KdPh2vHKMaPq1q222e;y7v^^LSITD&^>GR>dJM0*LdFZlja+ZW=G^JoAj5~g(E;~$7A z2xXiJK}>7hj{N4&bIt%JlEF9ItUqy4_6Sy?d^Ak8#Z036^`W%5cyK(u#M)<}YDT7J z_EKG4t>RckzS(kZis3}YxSio7ssxGGGTpW7wQ!W1wY-i=%1W~-Y9WE{J%pEJDR{rp zG>+lT-mok0rvk^1p=Ez#I9g?>nPQc=RJQ6o#hoEDfXj99Ol(31)zNI3w1);m_7#?T z{`32mH;-6DB?%df7IS5c_Kiog-#VS%N!@oeP zPc6bPGeCby*h9g=p`ln~^Fp0w$4^4j5!~P3`QeOy8LBk{;&!L@Rw$k9`%yoE>F zeo-(*ezfsS405V9zEr&!7m(fo0q?mK)klzXiI&6I%%}69^O^N(Q2xSXv_UZIybF2I zHL9hwcme7tvuO7sZw=~PR< zMvfby#doy=r{wi?xbkrwes*JBVmuxkuWi5{f_at@D zQ|(U~Z35M3s+7Xk_sBDf`y;$ax1AHwXhx#|>0>9FZ!O&sv4OLnH zzea3BUS`2Fp$G$S;7={Dxqwf4Al$4&gn zY)DI7v;iXuelEWK!G4Xj_LysFkLQphPoL0+? zLgG;`S~{fnazx1qK&AcCXCdJ@=h`^C>sOJ+9`zsZX%OSP&i%zNdrWiQmWwoj;)D`e?3~M?0^dnI*>u2w5$Y)m|+GOvhpTqIZ3;SXZDt5z-6txfAd6 zX6-xF&3kLS@xM$=v^bPZ>c==_K%+EE(g;Bt+ZfN!eQqnD**8IrR3>mDKGx=ZLOYHUDfqbLvGRY(&6X`n%~|sqFE>`P*sP{|?u!IwoFTYq6xjZngLo{nL2+S>We1 zcB-hE`HH~hAM!v@uO)UxHMpHT?%Bijn{pJCR;V6-R@^hk(a;u z1IRc(8Dh1Mc)#2~&UaIp07XK^ROj9+Q70vH*ZlZ9ugE7N>g^(?(xDq zU8-->zyIWuy9r9O0mduIaY-;Wt|PJj{K<`#?^MEWPRgmZ>V7m+%PD_@R~u#7a&mLA zt%P2b!_jVN`uM^#XxE?d<6UgAdYbp$OPUs1JORN4;x(9^D)t3GNKtL&=Ptfm=e^%e z1MGaGA&qxB`T_0gqY^^=(*AgA(X{?FT=$yE=(6hM(DS0C%-F*RAMoNZ&Q}iY0vy?x zPGQ+m>s$kqw*C?Gb~n_5NoIk48Mr1p*>YRol*;aOXmc3e^eg>ilWZTC?Ue!zg30yM z^;{N<{^qzHCDN|^9@EN6dq5m*23mZx(~%}uRh>?wt#^@2HueJfg?7so^B!V0h*$4c z{YJADJ-1wLyzMS<+5NHZ{Tdm+?p>RG{*e7_3T{dPY5!cQb}n~COhKj_B}4tp5?)zU z?QjR5)t~ZE{`M_7DW4c!xQPTrKhaubno|pjAz#rmun1~i zRo0AZx^{6N)j&RWlF4pRdxMvU(?SetAYP%B$_PWwqzr9M)1qVLbrtI(3fG70l};0& z9$fhA(NCRQYg6)ntmpIYP*z_W;+Cx2*5Gcf+UoBJ zm}zlXUC z7?#crU?Fb}Vebf`Y%<6crJ#w5Wr?lHxX=|im&eI+Pnl%)aLaWW8{SNX0r>M-x!tAL zPMi_4=&nvs7tv$ribiktbLHY6PrbCvXNogQNTv#2%WK-KSz46u_(!Oc*E?2jm+86z zJQ5@x$B=0%GqUiFptq)Q;{M0}M~&7iUz%>3H!dEI0K1qXK*mpA4bCUrv%yhL^@CJN znZ)-k_lX!yi>ixg*b6A}klZDPCyRCLTL$PEtMC*@0}ur+blt?`+e_9?9RY zAE$M9aza1}_sF|Q;bBR|`u8POx{?BI`WwuC1?N>XBohh`Y4%!Kklfl9{lsuX4QpP; z{U74C1}GwN!}mjOK><;#i2!jQ^S#Q(x%2*XgA9*4+0m{hHC+##7mwE|=a=<&5|RaI z+S|>9fTXQRK9%-Y?@}4m?m@2`etG9LD?m|2?#73Niy~9fn1!0x@6*?;8ME^hph(3W zVOZj#*Ua8E)_gmiL`F@zf(-Y}beGQe&T+lpQMk^S7c^~DKdYpbOjVz4zyVB>htZzl zlN;{*;6d?1Euh@s9q(%8g;lTd(V+_tr6v%+h!IDb1K%~t%TJ1x*T)I%i-uq-dXo1d z6&6ietT?u1b`jmd51R^WUl|wcPRJyP-TbdVgfDh5ymf7jS-0fes=dq)<3`5?Xc&35 znnv;44;@G>h)61j4ZM5stuq-lZ!+%@YraeH;Jm*$Qm`M6xE=BiKu?4xX%&g&w@+vWx3rh(6KhxTCBJyt<7> zya*$r7GMBvPvOsf{rBM66?9%S!}Y7#mO~Z0N(knAxSB$D91Ua5w^=(WOU2c34XW4w zu^44Ns9;qKtw2NKen-M}lN?tXP6YLVhuJdAtq<1R5|n=v>UQxhk(CyLwurz|WqQcc>v|UX1=`6w+7Fg4D}MBY+IxDHFGY}rUXfBnXQZ^hX+Ur1MMt?l{dh>>n=w*I}s3w1=Ah9i35 z3DP+4HyU)p!}dqC7!p4BCCg%0mLR)hHYf4VFK=&C9n#Bm+7zvZq_=SRWHM+C`y;D7 zuHLq+De9_|u+s02rdjwPXgaUDbvvnQgh+BhyR5F9zDGIhJ&qdZf0Yz3LVbf_94YXk z+{u8S*K+Eq-u`sq;C7#|2jfK;8G63u6Pw|(D#PVXCC7gsT{|r_g^&yP`0>~exPQNw z<-=k?7#wg}nbSX?Z62sfD6nDbcE4jPGpmNcI&(9_rmwg5c%MYN%|{%5(v9GrQs&lA zW|Ox-@B{Fx8mDNQRe|{XL%XyxfN1>rQ)ZGQWJY%v|1+D(<{hE{Qh3)LS@8K>LF!`X z`kimpQ*PAS4EnUS{leMN2)$a70hI&Eam^g$Y|8qZj-;Lmk;t0`3ee1qgHDY};z>7_ zTdy&PWL)3q&WVfomlV?syO`QZPCJBQ*n-c_&WvtiBB;-77Qsa2Ip4U8yJ2!)W2lSc zpvKTCr_q~Z;P!D>KXs2q`>>r-gzub*8ze%YOthCjf?PtKmS^0d$v)%aEXl^}Oaf3i zBhh$1x_H#j*c$Z{IsF)!O}pq#%B+up+{@hR3cas}JG3OhJEm7DrkvCAD(O9bTI#8#=2g~jDnFEH^l&SrU&TOHYO|%TX zyQslv(7fdfK%v!g*#2H9z++mWdwV->9MiMOj}R}*_Q@AQj~ z?{=cH`?H;$xVZ)00w(k+&cx(YB3aiC?w3X-cG`p_u_n#a zE_k~ODSMny?6%3u17_(P3JTMnh5?#~j(KWKp3UZ9BQucMO7V#Rl6R=b5-m%RUPT%; zCOu@@bzhunZcmD=0wb!j=Yl%Fur;hX1G&;X?-6tzhevM-)O;AULCmwc<6Y zThH`3wW#ra0UtVAXx3W;M{Q?IfzxKytH)}%={_ktgCir#{lf?~`cYF8ZppB4G@A+% zBsP1`!|jhGl(zd!bDblh_4c)k(a0<_NG93Yr!H&Pp`)?fH}i39rMf@g#+mbB%@h@a zT12nA&dF2!{P8gwv)9a@cs&R7XJKdY-5xXMD4cpjZqMk)>byQ<^eC6WT@#9p0dHS4 z*^gkxk~~g5r%g0kuZCD-E~^Onns2Mn7mu9a5I1GJ7OIxYJyHtL^qa=`UX8*s)D(!( zQx2c@SIZhcDn+f(WVPG$4ZY_^hO448oQmW*Hn;|!C(>Jqqndzs`7zsr?6IT>^N-^0 z*VlV9A1>3!VD`xxRz0(>PV2P0l4I4c_9dRlgQj+dE5tRtU;Wdzmyu=b#nPFVjF+q) zmO@n7o;S}9f^f5{>-C+{dE=5bSPc6E-G3((Y~PpRR;bq64(^Sbu)H6Jl~ zELt|`dcO!Qer^5Y`Cfl$2W+ZZ>LzkD2t+1fI_t%e#rx)8Z;-f@{2^;egnrEB7yMyF zbL1%4#5VG&uKR{(7>an1dFN5r$_V1{{B}(mW=gtyupvilouQM9bExI3u4>bwCd>&e;uc~_X+PogANE$W898(XvUusSwJy>}5&e%-Ytlq*+Q^C=ON9H8cs z&&v0HMiv$PCUeIZ36tw7L|Hn@Y`-ndP63^}+p?dEfOoLku1%v9CH6C>{caP>?Z~HM zA)6;KQm4{36V3W%&^rl@U|Mq3>sF>lH`#Wd>ZZYGUwK(({+y}#?z2YIg>Ib;aam+K zw9~qt!2MabizV}WI37d3q!^o4%X`2C8=ZVsxpvEu!FmzT59+TgRU{IYqN$AuL;XS{ zNO;)+IsiiNGBRF_MW2N;i&{SVosx?7fsmh!xW5&DV(){x*`zAh$ESOT1}%O6^vVP> zeN|{nYP@)So84)KIu`>~tZltV+TuW`pa-*L*HW)o^Hf&T`fm>&Y%`vCfnh0(j7}yJ zw`5vRqS{!5W&%#CdTpBMhPz4%l6pw}FRHKB8|^32TRsUNlbBT~@qm zc7QTRld2hpw%^}Q?`5IK1wZ26&@-@Af<<-F_gK@qt&d+~ed4pKobKBE?WXNo@9ZE5 z_ZxKYIQxQh_j8AyW|8902q>G`+MtnCzNtgFtvorOLTHGhPjNqEohaf#*LTvxDDVyg4Zim^Rc^Fm1e3F4uD8d+Wn+L+pNTj1U5g z`7`+4M&qmSmAC>hI_h>LMQ;R|Kc3XIgL%1NrzMMg~ihH#Pa!dFv0YZG}5yGo!>T*Y=hq>x5i^#5MSU$ zBF*``N6&ceK^bd0&XJ@a$C_tvd-QrO(N7Ab4rny&th0{o%R9ptVzd&~hn??c8F5!( zZ^vXk2g3<(BlsLgr}4RSoeo*VY4y0z@;+@@#S3=_N7!@8w~mQ|@Yn(uq|`@FTI|La zf{NntP<=0G_Ty!#|6+vxZRi|^xv+QwNUB!izB~5(p)3!K)wlRMnU_KUL}xRk{-Hpo zA7e$y`H7hV_NZKsy3>eQH1tdx8E2&76870&gJG44O~L1~%MU6ICpPPx`3pSp!&Sa@ zx1YZs@ZK&vocPh$k4H|vK&3==$f`ltRF;-gLC--wC&tuz$|>>nd2V6Jb4B`r#{~CX zo%~Y50?u@Mw3_+q4`U<5;MVO)r}Bj0VS+pb0lfyl6~+V@uF|CqtFEGLKa|rH+lTdl z21aaRxN5^_X3RwWP|CW;&LUrVo2rd9a+c1%q<0eO4XEUbsBps+ZqK&qkEF0F78jIQoz87~uBi-YuUwG`(Hu>+RCG|ZOG+40=qq!JJLcx<_3&bbkMZl9 zV@-AiA(MwevCX~rG1(l{lqfn?(Lj2&YS~eueO1>u}D73<2?GW=^;8 z{O&z6(CM+vI#E!E+YXHtsxs1_6g9G$?Ea!MV+~*t{#k;y$}o~2F=dAowJxX@v>q_x zb`f3v>4U0iH&J%(2s%M5YhW}YwVR)}R!Ge_&tPjlf_^Z!K}P&$Eb|hKY`h2>*AO{D z`@jfy6a;aT%$021r>-6MB4N#aG9df1Js74A+0y+)j=*JN^aQdo3B zM$Ot7X>Wc7xm;o=r;= zG@;q9mQTZ%6FX6>GipB;B}P&TBJtRCKU^%{==U!c*7OLFZ7sgjr^dqiy9X~`3<|I4 zHS2X*FXuq_|D8vUxf0Z5bc-t06-X;?#sQn9~kKGN=@k2hr*JPynJHpV)Rx`@YY)jkh%n zK@sD5{V2zR=_eI-(7%B{_L7`OtDuR%=N^MgUuJ98?{?P>mu0XGxPSIFPd{_7N@PCv zQHyGv7rt;hogNkTvdWz+X<_oSUGr*?xGF`0o7(CK{y2gu?9t86u}onb-Q_kZd~&s) z3qD&aZg3Kcd9C#p#S+sw!b|y}3Is+5N2@Z=lWzo^ZM^bZ*ga*wW;h{Ge=s8ZPGdJoNR#5LIdkJ+>G6txBB_=IBLutRCrQhXU(81LNii_Zt z%_2tzq8r4AT}11}YZthkbw)KtGmCCX)1)(36CYuu+bzQ0PzA{wHqM{bH+&^=x(K|? zt9}+hHDwpMvkHA+exIvyQ%`KyN35KAyxM4FCuI`YRFud=~y3$ zQXFP2zpeU@LDLK&b9Oga6nByW3SC_1FYOh}kxfE!T3ffz^x#a< zPb=QOGDRt)-(J8OxyOS@HNymRwER#nU3kQkG^$#I8vVC_o`>@-{;*y zn8kyPH=P`p%zFCFa=ZnJiI^3%!Tp-6kFk+w@^?F{U$qnjhVYBS9;*MfBI1- zj?x70SsLEE<#WOb9{Jb=KcSuhtQWov205KQk0SoOVJ3mOV^ijvU}D<4lgJ4PIo|th zG5H0Lif5})>_8&)@w*P2TSpqv?hfIpJMC}{W=RC6sE-ttB2T%{p8DwBi#?V3>n?2K zE&bf`3`zvLdTT8&!Stmtcu~mooz~dSnSr@i*p9?{@SC5K0+BhkH=w*@eA56YX(``>nM*OF&8sRGxvFl z@_safE{ZiD1n>=|Kozpcp8K9WOx#o`;-dhkn|{AXQgdQBjR_%UDAF25=Hab!5Bt84 zJ-8b=+&qwoVduP;%enOtNfCs(eKNp^GYywjuQ(K*7e}tw*UqJW@yd5q>`! zlJk?ZjpHlJTJ&I!Rd(Q-M-$UrB(UyJNw^M zIgGXUh*PhN+=P*(WB>Fq5PDx472w43+wB*Apd)ej-0(im>IPwx%-|=+18q6i&yKD^ zzpLRp($5?y7p}>IqBlH`? z8y=|lY_=U_DXboeuKnNUK$n}tFuG*XjIv`~x&p1H@eX5%oa*P%!M~Vu#l47{Y(6Bg z04WmAdc)xj0zY=AzSs4BtNjRD#rmNHWlZSGhI7Fdb&E=12fyZw*#Y+bvGTQSY*5(l zQWEEw;R^Q1dKz({NY96VYF}Y6KV>t>U6$SA((NAZ>rdgy;jkUMKqtR)1(N)rT^i~f z;<&^r#i?t3WYp%KXGm+?Q zhyR@P$2?x~8j1+8av8jQN@1Gelr#1-#WH{cxsl*~Ntmg?C$38zq~=h}G^^cD+!-|O z?&`Sk7SmeUzvOh3Lwp+y?=OEla}Xae;4a-Yg9LPwL5*wtYH6b+V7KX>c}pt4mwEYS z4clTr*PfPTfYI}(Km(ucUyo1L?WXA@r|f*?N96{;T+ed$8fSqlg`#pen%GdE3lN;fi{nf$d&KaD`r z_Q_z_9rVlr>%+ftE9fYdRB?&b9LgKsuu29pzRygCAj0}q{?7iMKe+NsGRFEN-Wb@m z)jONlfvw?cX47)d??6X+@%i=Yk~6}Ab4-{gL5V^DCy8foBGL;@7`|5$^BYLhnZ>YD^wm07E0(Br;kK?(EiwcX|Gy z?7=|YS-Gs<7v$9yS44R#L=^DtOe>AWgpf9IY>Z%X%E|Iw6NRz(@!reGq_H;yrFP%z z)wu5@?2YJ!2aW5K`a=J(StBMluxUO2{;_av0A>B|8JPzLPSE>oV~P5*Ecjap7PK?+ zQ|9MhKTLpVbOX;_XMy}_)V&J>v)3!MOENU91!Zo#y2DjfKPYN`y8U&7(5x)j2t}*c z#^c%G1ev-{dwnBdYFeuGfB(cIfo4%+PO-SZ`PNX{n&BAW$hhK0t_@4*gA}| zg_n)C2Lad8Eg30vAeOM|?U6SFjYi5T+0BkSjhIt8#=l2-P;lEZkt4ArI5mG}>o&uu z1MK1p)_CV}kXi#OlCj<%NGcUDSU+ARU_!4a9TDtqp5^bfZZT8h0$PC1P4x95wjWPl zqfq#+zAgySkm%h#XZgs^JwCXlu|Y45O9S{Q0^m*zkMN2hCoI*Yc-?(uSKjed31C$4 z`Y-`Oc$Qk)Cj;s(E?^6q#x(Vui5hw>MS}Zo!}2Cs)~{^4n$ctfRyBzj8>_=Y(puOJ ze6QWXIVYj*icw}FO&Z5pi;tzhP&u^qEu?!g_q^Q^YroF2^cyT5FqY}n8m5}NLY|3! zK`wr!o{WliOjdx+WhV+)Sg3AT<&xCkm_$%-kEs-uo#qvAtm! z4R#ASB(LW@0?}8E(SM}Sg#i@8oeQ()44cN}yCfk-M(C~I&v;Zd&ZJ$x`>R`z)*UK2 zA}XjAnZjzmQr7gfVS3HNL5T?sILOqxZciA>;eKVxPG%3@NfSu}j^5-$u*$vpK`5>p zKF_ot7;}qP7YTGl{KZ(=M+1k|_n4uRFZK2@qlq0Gi6=ADpF3Dyh53xS7|?~KpKa3G zjH679B!b@M-|%&TT=&_`;&5lkqI{?6I{PvgIg%srx!)eKQd2*h+QhyMY(2)o-(t?7 zsv2K~SD&xIb*z`7D-w?&_yq5|d;8Lt+Ko`6QXtExIqx-+JZ?q(_JLV=_jdByW@EX7 z0d-!N`p9?TwV08X_X7pM1P?_Y|Mv>PH#wQG;WTYg) z;=YrvGL_Z*eDhkF258tZI9}#Do4G*}$)8XBP4k?P8=_)&fSy<)fsOt^Qr~ZqTf+#s z%FI7t5yM6dN8=a~msGI5Q1mO_bPbPZ3lTE&AnU?;$D>5PHS!ODL;e+HIaN?}!5 zF7pE>LqnWL285169NTdg=G=c{V-eNCg z)`G_q!#G5Sevgkx`T6ISO%f%%6rEZ(j{?bLXNe)(lX7+Z_jexBr#AC~XuNubsB{q- z889^6uZ~`ux2qn~#i>bu5tbrmEWBs=hv>Xf;ez6YVnYuVA})eke@?y%j|y%|pTS z9sSPBF#@ja%*a@YFSV{QLDy1F5E*mbx06EIwOsPHDsNt%8rG zNlBS3GE_tL&{>;B&*Zw4%kT>5U0#`l*KT%fSYSnn8;ef^BLbUJdPv@1vB?|IcX<8X zl=a^zK3YbYb=v!vM;j?=hj}#20kQR?im8TKtMaZ`BJ~;*nPpmW{w1)%?3sG)lU^9Y zw$`9Ym@@M}ECc@p8pkt%p@#A0M}xEfO#9mxYzQ#a5D9`@ZuD`A!zxyKi={MT-|0n7H zwtX!0>tcd=vnGk&cuF`siTLX;qE7iKU$z zm<%1N=;d@k1Xk`h%32T&3U*hpC?$ijM6E2|csOyF)|cnsOdwxo&Q=tYkTC7nwTF1~ z_HSa#NELkuie#J__XL+PjuP|>IvW}oh)y^Z)c?1A2^?|ZD&A2eT-~$x%_z_{^KZSu z@Kg42p&T0D`rYUo>1;6Mz3O|@|A->dhbrQf@48r~{;P^a=J$lT4vKuj%;g1-LI)gJ zF&Kt6{zu?={HNdIGdMgZ{zviI76XTMwC|rBwf?{=tBO^3>nL!%?`t4s0kg zQ5-gC)Vk(;&Uh#pN#SW>E>SEUGM_CCy4s)BOl7xXVtq}0w?*?r>99LOr|KkXT&!9| z7jTPD?lYSDZq57F$#1&CZtG_?STmEEo44s7I9~ACnLsY+E{DFYHN>KRW&l33zwf_S zcJW+!O}`HbfAqF2UWgsK=Wn|LQC%z)Mqv(R*-tK1gJG%LhZanUpW^zLyed8KS?=%R zZZUU1-6C>wJXkjbIPOnquv^cQ0bke^z^AaKz~0mLrBy}xmio_WFD*~tb8?);QhmVH zVO50Z^XPt~7duZ$BJaieC5!f@mi@v4qSGbJZ?bHM_`V+RT#Cw?qaJd^L-L%Mz^{3| z%4pP?8_W7BR^ob`a9FKU`n=qSJh2d46Rqi)!*S%o1v`cGOU48Myv`JcybsYotM<;V%baZb4KmwI@H0&!g zP6SLYc`(Y}0+roSeX; zU`1y}jG=e#Z!~3}+1}FWBk2=hQ}EzdS&}voX!49d=XY^CNp&$Lx0UAtI?1z0B4M$c z1n5w${YDq)g(2RJ|8hkdI=qBYKmPEw@;4~7`(nI0-|R8+TYp<+BvtE88po2uZ~IK# zO>vJ0+;L$5#+jFH+ZQKp`AKhB^?!&WHi_T9a&#BH5)RWBGSf^4e=WxY{EJO#xnZ!L zw0)M-k7QyHAFrHilIVCiuR_57wYFD!u!VC^EBo3Xs{5X$YkZV#3fT9)U! zRe0}LrI4Y8(kOw3=JL<7!N%?zXv!LU6H!yN4JYWguOPMP(} z5aOaE9MzN~vapDj4Eu z*y8i_L56_Cnw$rHeg$Q<#$@y(#lr`QEXpa=~i_N0;Hz0T%#oLzLoONp9sueDqdPh@=U7oQZkTsE9+)0E@7 zJ(iY%gEJjjx#v+ML*1J-&>Ib)ciXb( z5<&{Q)%Of>ynJvJO7&Vh{2&=>%2kgN`oj52UAnf*iqc}Ob;Y~dms$-no7Vg4j1tX) zOiBw-rpIxKY_Z{X1cz?b!w;FY*RzOIAOqM}N(V+j<{Vc(3r@N0>~N^B*t9zZp7gha zqq0PTb?wV8P8q?`FATcjFFiy8cusEP+EIhweUgUn%_Yyqpg6zZ5c7&Q2^2i7MXH!S zrm6{dnBoeaGSEbt z&Df%_)G(6!*Qwv(kVp9{AUWrHW-j-$7#Ie>byHWg7`0g^uq;k1n3cli5F9>z1bSR; z*lK49(Mg*odGr?f8n*Kr zmecGvo$gbDpON2w@8(Fc5Lq-oKJqvn(m5T?&ZSft=Go=`oYctDKRjZ^O#cjhjepc= zD3>TF@G6<<@p3NtQ`4qd&D4iI8kvb-Teac~l^dUjw!<}}Vu3TC$F8tsKLGf!Y*Olc zx;$!v+h9GTdTpZ4IUwPLQ|2?)?XV0+nofg8`5)SvKAE1h=e%QtAM_8p+*v#d(bQ&k zC9kNac#CFLI%7JRLXe1M1_lT0@SrlfzqZK@Kt5Oob<&ooRbWmx*=?EF51Cn0(;I=> zFEJKc&1$%HW$Qq~yaLZp!{|fBIgVgcPI=n~%l%P>0G2nrp}c6TX$KfV%A%&>#-~K- z!6c%c@=rngo+)T9ax$qkPB3Hf1@W#5S>ztpe0>${6dQd9xy9cFqwO>5Lr$@!A1&}X z1Cq@N2Z0D}>FU5@ES`bQTCGrC=#{Q`D?hPg;8-bND&biD+MMn94N8}qws%|^q@Tjw z(OgB`Z?N##a|p3dDuY&|pa@0;M^ircCPwg%j5(Qj zXl{n{^31Hmy==sVsiG@w$Dlg#vd+i|8KAf>QZyw${Fb=lvOUGj&>lH@;i&;;59IMK7wR2$-|`$=J2y zXg#ftQ%8`yz@tSr4TK}7c{`SE-<*qzK9Ue6sO#g&CJ##7^v}@&e&h-d;A8zU_Tu3b z6Awr~ZL4obtA=u+?onv&ToCW$lfFz;ka^y@85k3ob_u;vvshTQ$AH=mX9}grGPX0U z7Ck(T*zgDhhyiXzSLP;6RXkNG}o#6h+_@x$y^I>-+g{lI0 z9y^|SL_8K}zS!>xX)_C~ASAVtvBFk=A<|3BP7lDK}=cf1=Kr$nV*{+$?G-V-31 zXD0kNBf|H_T~QAsG5HqXPxJ#mN5hZVdkTQ-mIqEHQhVJ%Em6y)E4k&*$0chZfhSa9Xdg>ndIZ z&??LO0_dWX#CN+a$8Nn`xCf~I6~cV6dQHlqM4^(Nyy8a+`t1vDy<8={33#ke1I3kV zd(20jaD#(#)T?6g5h|%+hL$|WpJ(3iDw#ZSX-k{}jjnr+lln7VJHGeQdQuJ;Zd1)J z6@IB}t{asN)Om{X7=Z#C`Vv@dUX};+Im6{&RGJ~~!SR4F$R%rD{kcQ7g{?Oa$QVt^ z?CbK42Z<%6ZRpFl@na7MMju+LlDUXT17-#yH0a`doLJd04+77LYl6NUd`?`lZdyy1 zw%IQY0Bbc9=`;uw{?|;4C3bMZn3m(ZZ>*F`SJ!5md^M=Fr$f=<2f^W~= zNhgjy1#)iME!+e?L>7&fxn8j1=-Y%>|N375*uYHqa9&B<_zdsastXX1-qQJ(-{IXs zMPyz>oi5eZviMPmMYx6Spz+>5+d}q{4^c%;a^#iD@O7juS#li|4H^-55%tv|sZ);g z$eXUvl_|D7LLyIuQ8v?4%My6U-W#^7IM)+7RVJLK{_Fl!es590U8jPQ&JsihVXZE; zRMSI(j?0x2r*E|F3C52K=Q3`w<^h`@50{+a^U@$#-9U6%SufmQv!U@&?b!QZ@`K2J zA_l4eDHH1r-4)HVY$rokIrousD_Y;#MmztM#`_{(k9!h-5Hv3<*#;L9Yv6{jUk&LR zp&Z{!aqZ+D3yP>&dDi3IYL8y>MpBl=T3WKuF}$y!g!fRer;gdF7Do$yjs8W=E{;jX z<^n|eL~v_+07fFqDyyDlTP&ZF>%G=mR;NP>>GbB#-9POm@*iQo_xSmz;BMf}AOM~A zRyYx;l;5Gep%b>_wtAj7#bNB7g;UcP&Y4G zdlW%>(JWcdSAJ{DO}FEwfD*{t*mr%3NwVx4lq1-r2lPXE!%)23v=Q_nu^KYRVu-`l z0@%yctCrDJDl%_FJp*mJQn9}RP;MoerCwQH;M0Vlayh!9@>APiS4VdR)k_fx6pM2R zc0RPc6A+lj5JnlpqU4NwnZ)NUn^yPx6>kW{XQat;O~kh+aXa?b7iji#X2b!!jmwJvLb3NLe4jYN0xEYOa%IKq%|DTdeOo|a=JArZmk`N zFncKJZs@%Gr(B_+wmC?XJ7H3Bpv_X>3hTLYFGvlQ0u7t9C%U@2W)u-$4nFEXYy{qj zXKq~>BI;6}QJoqfVY6>R6uk}@{4E6stT63{8-n=R&_rj?z%4XrEBQ`5)ZV%PG)h6K ztK%IhyNcZQJI?B`@f(3@F8_swWUlZDeeWAUmwg>#J4ZW`mks%q+PvQ;d{&;KbKW`( zp-OQhXyD0nPxvj7l z*StjUA&# zq|rs|Vs;B?2B1^C|Dd$dB|Rn&{g_iPjzq-s{CGm!=3ztA@VwVtSvAiHK zr~jeZ`2g&Hv&3hx{~j&30b>Z~o!2l`iHmm zsr!>cR@-ITNx5shbkU9YdDC8dmJHBFHjes57I^O8xg315dgZxi+v#3S)*`Y&mUOPn0v?d$t>`W$8rzFT5cp@}Incl~Wnb z|8S>GSm&BYU;%0ZsXoUD^hN@7TIElh_e_?ov*92at|G-6KM32V^figkGXXixtx%Vy zf4E7<^&-z|`Nj~2bv%-eA{=1Ls=MSFhA^4P->r)@RJv7UE)`?s{CLV?<$EKJ^sbxP z{GzCU6erNP8I;*?++x)bg*TalZXrR|2~pV=Jl+9yMN!_OY$~T*f32*iwg1z{u>EOy z`w2N6iPhwDL#2v(;S2ceRi}hN==_suKeD&+!x79$Z#WjigN~f3REIiD0Q=RChzUQ^ z!@8bP{F51fszqd57HI2Et+s+psWj&TbKWFJ2%cr`x zPqX$bRq}!V5%!VbC+8DSuEE8PowH0NQ;^_-^U_PbD{s1vm*cdJ4k8+!7ONHyC)LwP zLhA)tI$~GFQ~A*(3UE^ki|=J#i5x9n`SDBj4MjMK!7qSLwPbnw4W{AxkxIE%@R+lm zEIMar7h+Chb&bW2ZFb;z|B^T!$2I0If()))WdCpWArpYP<9+9!61ikOH4gV>*$c!N zQ<=l*4GvOX_#6igPM#m@wqtS4g0|xOXxn$ordVwX!1{ja|NoHp-tTa3?fY=_P7)E_ z5TbX{dk7+;M2$8?i{664=)JcfIzfohB}DH=C((Nuy@bIqMtf)Pr|f4x&-XY!f57{T zam-r7J!{=pS?6_L=Y1q6WwRzuz@%5x8aL_&AK34$BOkS}?&p;xJ1IF_8e;MJJ;ao4 zh8O>!d}gZE$oWb=3u3O5bUW>_$FJ}h>prX6^;|9yL>H1a6!cIp>p)VBJ8Q>RB-JL` zNrEcq&qK}MGW&caIr~FARjMIKl{8;7Fy4r17Xqcm70^|7*ls6MU~h8PE04)-Byz#wgFmo5p+Gj*&+xll@%86@YqR?PoFS@r1~BY+lJcXVpUDO;^s>RSs0|bM{M86S>rWl*@o1{d zw;f2`>*+o2kff%L^ZO={qKxn%Ib>|AVFbG4)+h?Uz zhnb;h-kxkCiPiuRzS5Wo`L57Lb^7(X)$*lj?|h?W_S$%6mHkA=efKE}(E--h+fP<0 z+?|P7%Gz9qWK=i@+R`4$f3y;zi3`A;_1_aE_5oTpSe2ax63ivx;Z}2A$MO#v(IqC< zL;IS;JSp+*OcDI0_3oXU3eqO#sibxCt{FdR&u_%i?*P(|TC1u}NxSx#%I7UKu4iJ{ z{GeTM{4hM_*9WC5MHw7_Cc+#K*&$=(pR|IH){a8Pe)IE|Q%7t^=StTnp-;loxwNZt zK`Hn02BfbPwtx2=Elo>p5?0zs*(zV|~kl8ey@ z-1a4X>~+mpU{wpsnD_fYE{B`d>VHAse*i}BUNYLyg*`8QqI7tbsKNdlkn{Q^O+tP3 zUT4>5d=l3sMEWibK$dX2spCm0#iT3z7uQ&1Li5d;P(sr&`JQ6nDUQ;;VO9K+bW=Y) zwAO;}mDZ9X$NO*_GV!GD-TjLzL=xZp0a>h%L!}VwZ{JlfZBhU$4qr_X0!(z~?%zC|hY zIbU5^W(&k@^%+&x`v0o)b#~7Me=hW@L6*;8wAK(1?LP@h*Z4tpf11!-zboMNBz`Eh zj)v^TrxCl_Hc=OiERL`iGYrxJl%d0wU*oNGi|PGJ(dR8lSy>p~%snCN>y^y!P8Iau zL_JyK+C@xdmv1~4zGwi2%KZvw(*A*?M5A#Ox`DRNc~_vnG$*B@>=lLjW0rEGaM_|U zWj{LCXGJFm6i4n1LWcg=w%IaO9{eTXocSgt6aM$uD>2B*AMh>62ATpk8ARcZE%X`y z+Nq>l$@u!wk%F}K$B|~vCB(kLj1l@K$M)o0l6iCR0i}|)m;&8Oy9=tjaiUNekVO(J zPjV~Cy<8_>dt|l~=ZEXC^d-kn6qc=lz7boDr7Gd^-rjBhTRM5f8}z4E8JbVuSZCv? zI}n#8Qm6UCsF;OS=()Bo#BeE_TcE^&l9^XP0>DJW+qh#Fv+p-g4V@+Xz?a~nJfPHS zQ9qz0tQs)`MR%j2wE+2PEId^mo*R6z(}iUGT*!lbHW%Wwi*>xl*AQNVNZRWZQ6P~&j{hx z%MUzHF=@_}_(cIpX7hVqLoLG9%g^CqcE0*VKX}ru@GBue5tsAPp$Wg zx?3LKLaXItd6nbKD_ClsFqD6YP(4#ZART`%2NQx}G}S~i4gZlhepQQ^h0YV} zQWjPI#cuCpebp@~dDBpEKbLut`R7motS0`UnBk(qjbfu4Afo$8?iv#}0khA+yR)^^ zS@q)98t-=(+OC9*8ZAwX*idv|cv_`N6VZBx#ZAhGFP1aB)Ds|&S7Ab3J;j+Wua~`# zNak#+ZiHiB+Qf9{s+z{inG_M9st%y$)4s`8ot*eqs^>KQsy1h)MZ|>d{Rxoev*Uj( z-j1r+%!<{Oy7?qd!`B%Va4Vi}H(TjI4XKkq_M0`Dc`g6^NB=B(F`#>q4ed{bB;H(- z#$OOq8ToULJ*x5kf9T3-g7F{O|B>(-(Yq3actFe%jPFNWey~H=Q|hj;^zRG{Xevvj2#|W4n#q=lx!tPi2<$RY>dp zAd&f7Srl?9m@6{#Z&|=UROlFAV@G@7S3UclVfXJ=$>=~iPVL=^2VD^S%)uwH`*LCs zMJ5hde(L(f34uJIe)JT|6`^9^JhLbdS)@U=`+2I z|7$8^D|(?`@&WDsZ#};Mzb|R2Cg%Py=fB;{M{~^YhBhx-)#u){YUy$S<)Oir*!&{S zX>qh7L+z(}1D}C+NyP!&b>trNlOIu>z(Oqwj*=5_%K)1JeiA<=RZMXnvKtza9v_jUGSRt9!d&XM1D)_RGmB zD_{DH3H{k9lw;Ye6yCR-z_)|Ye&=VG-vw=c3BDD(0j4;Q*Ec;oe4}lLb0R(uIeE2R zkvf->HWX~D-e$gF_pAJs$JZ$qZO)$Kx%@*D37fd#%(JTDy!u~fqI#E)O(3rh`wa8M z4sU#a8)k^Zff=;@qPF_FhBzk_oR%A_b><=X8#bla*)st`N{8kcb=Rpx(HDb@3XON@K)Ki;*_j#qVq~+@&}>~uCiPH zJVY7bgk0zL;_JRz0AA|Raz*kptsD5pcIh>eVBb=eaYbv>_d^BCYx2o}`oTLv*PQFe z{a$Y_b4|EZg?18lo%g@DkG#3eW2;ODwt^Q_e>KMpifYMl0(sw8Ew=^VirbIkUb-d^ zYPchg7VAHs?Y?=5NcTc1fU|GoPP&Uc1idpwOtsTRI9D2zpE(zu^sT@{{f{s0a|bm{ z*;2kpURH9pzb=tuKyXm@JHPQpFGl;FM~>RrNd?zneGvHRH&LBDkDzO@{=2m^FYU`i zowgStNJK`XSs}ROguDd|iT>44m{MgqGc$#EF(jc&aOoVNeMzkzaFJHluJF|MD@rSm zUt@AxV<)dxz3*;!qpmD9rAc^kJO3yPmlq^2PL%GPY{f+p_~oFC6R5rEv2Y3F-L2Q_ zBe;!gv~CFoW*~aY^E8W1@~@U?=bMjL62aEuFJJn<)&%tHlbk_0I*?U5H|Cd|rZQf* zDZuYcoIq+67fXxjYU8~V`w)~4FCQWwxIlqd^6A>HVr)JRm$CUx^vVA7lZg-RR$cdm2xzVOsvJQQhjyw{O&Oz)%=D-oOIw=(*J60od3$@F!qV1TST-;CFNsfM zEoa61kvf1A1gB((LjUqTC{ZoUxHSmrBgkaX`FreSqEj>YezZJV*>iI%kMr@`?L70#C zwWJ>T4!`TrhPt;h`Ka)rrtp&$wT8MqajbZVs9*u34fe>idm@}*K@9h8N+|nn@ZeN* zlJi_a^y;Ua@GZKNciWkeSd9t^>pcN_a_mhN8HN0N+lC$;2(PbkgEP|>m84p(lXy^< z%Ga@A`YM2x;_U~8yP3EoX9#Nl{Y@ND@$qH8timRj>O$hJCp{MgtQk;Byuo;cXxPuc z;ZjMg(&w{0CLWXvPt*A%`m6KDGRX~9`33Il06*RC>?Xs+r%07rFmt9m$Sas&<96%k6JZDN+{tr zBTT!YT1xRob`p5OL`I#b7GN@!fc1sOt==3;v)fpG%dmIPPKV7fFVg=b*3BOqf_M`V zpYHYI2c2G>9C|yO4j= z5itfn#|m(ogjg~>C|2WbD&tXQiR@#4Jez*x$70e4kN^gfz*vnhKr3<+zQFzh0Aiq_ zWq-eIc=m!0HPxA<(>{p&n5t%q!vxbt|B-R8gIolj!chkHX2Z?5257K&mN$T=+R+pi3mQmD`f>af^eVg8xV&xT2|2MjihRhVqp8_n8ZaR5%Bu z(w#LhM4KiXjHqudD{YNew`3 z5!B+z?%*tPqZRUSgJ~b)yuhwjH|L4I?NhvP7_*$~JOGxOml7^w*k4UG^FSz`s==&o zmc%OBor{a8s)QxprjYZkG_Qh*;>8sfMc&(k$@gJtK`xW=GA~|vftaL-LsxSyix2bzBhUu0J?Hgc?^Wb1~W?G1fhViz3;HFO|VD4 z1HTemw{jsICCpRvO2lw>5@L>s^>UJ%w#-iaahnf4Q#7zCoW}_0H^SA=wCWbvT+y@w zch&EbZt!SmQc6~ykaV4VH(su$`>@7aZGrm5pV$fKOf9!`U{y2EP|Lwd;SRGB)E)<< z5p2KKQ8)OaN{ApB3T+_YB^%QD`BrM+jR9**4m;3*bumrxnZcBC?D^TjJcF~KC2B)E z>E*|C=sz>^{Gx{{w>qzENH#UGhzXn_iyWa)amz1O9&2cZ%j|Q-ioA`R&B#MA@W~2ER^OKT9!LTq5Kw zCF7++&H8aP(WO04Tpi)(fJdlD*$w*?zm86DYYT|K5M8=Yu1rBWJgt2vn`2UHW1c+5qXq>1LSjuUhKA&!JN8VE4T0s)m3R9v zyT?1k1I>b(nr=6GKtI=;m73~$q&*%&lcJ~^{Chu!rr^lOHhtz~M}8mEhxBUJlg{k~ zZq;~ue}pXmw{dKXwuKb)MLNUj1!bjbsRU_UZi5z#Nyc=v9iqZgut<0EULjxf++UHk zU-`;N)7f6C{>CG|bS6X$ox?fddVH7}mEthh;-VEa#A;URC(5nNp+@D#v~P+s>5D!J zM!k>bN)XJSJnj6lGJR3)pZy=y*FSK&Uy6+Beg)5j;~oFqIBLM{JPczZ8z~M#IP5-k?VvDO!0id#-yoQhP4iUdu@H1}nt5woge|)Em z{+Mp{g5JU^1WRmfGAl5JET`K#F&}6BJ)g)E#LQv7s;3P1QGEysZ$|QoLsQGMWfOwF z2|~ibm>p=Ri`({LWG>+ho>h;kQL!!+RBOd003WCi3=XRl^j--#{!!s0?EJ0u=bNsR z%Hw=ef=xfus+B{9hN_lyxtuPP`^<{fz}%i3z|c-&=`F<9K;Wa%AjgsS)>E>$ucmw{ zx>o!L3MFmpPX`}vgLTiAmiXT5YX&TJU8?wfz*~^<_AYtm<7dKZIn3kSq)AYudD#|g zaHH4ecWZ#F?dX%V^tFTFRcQjmbjM$yV6~+!4AC6EX0U#aOX!Ee`wG*0;*fK7L%6Og zp|#MzY3;?eFXOQ2-;Sp29)FyzJ<>mt{FApF^h>>ime)kGrqs0upK!?3MMAZ+uRysx zUSyqCPxfj;nJK1~JT-i6N(Wm|y**b{QjAn7E}|aG_Qu)f?5V;BxbZ=0O9lgM3QC!e z=#|2Rw3EP;{IaKaAk%E)N5tt)&`ZZ}`Pe6=A-Y&#P0E9wHFBq_U43szv$L~ieHGb8 zQj3hQ@)BZEGSprFP|-Wd|7JaHLsFP8G!iv-6wziut%4e!QHnG z;;9!iqJwPx5mNGgiw_Q+o5;MOoQXs+P=^J?v_%?QX#!#f}v^No$V(|CA70!E|!~w2PGCrTM zcbq!=Bfe2__XCv=ZVpHi{;^UK>w>%UCxPy8Zto*_5m%RiknZrbmND_Ig!$A5O5bAkLi&YJM)%+mwhKYAj z`PjPd`=v!3%z$sq@10c^_s@K|iA)nQFU`j2yUa^R;M?;@2oIRl)N` z9EIta7?bLS>Yg`Yu+QN*N4N=AmY143;5@}c1WHYpy3~CE2N0#-PgAyrROyl~#M_ih z?ZGTtTPBXKYz&qaU-Mbs06*GjKI(bG!I^HfOhc8_6K-RhB~L9+^5lleka< z0>u7@43oLj-+Er;I2g?m`it}F9sd0s3pr}0d{#-DYohX4Ys=6zU2-}+e&XkSY}uUx zbb>ul-u-H0bJXqXuyaPTJIl z3Y$MoteyJ$nJ!P^A#{C$7}$>D-`)}IUNo89GA6)@L&2AAfo3bMyO3MufR6870=_kI zj9B#uJ`j-@YCp?pc|N4OpZxj-J=y#nB3t067fbu0_pHE~&qmxtUaQo{EdP?;de{Dj4?4e&${h#=s+GVfr=1aud2C5k%!{-jNn zE!vnblSpu0@kuTsT{NbcK(0NPc%yOB3wt&e!tqjJ`ihNOzsDNnn=0`}z9Y z4010kFKf-}rn%j874Q=PnI(crcL{MOpn6}49h~hyzV(%H64ie^L4JlONzk~;S5LNU z?nMf|7CGCOLCJ2oE|x^9F*>uICPFXY`(^X$Q|jkVvDM$>;U3g(sA#Rc`}E#GrqtjJ z6~3|b^h`IQRg1`Yd(1JIQu5u^VicOXl1u7CK=v&(~?`tf(!HhL^y;_rxgWO)D z8}LJJR;fl`ycq|oO`lHEpNV?ODzPjn75R+=j?Yp(I?A$Z+=*cMce=l%TnjlG$JtIn27og>>-@Iz9vdn4WlM}ja z;fu~Lwb(ppGgio0^3Y9+;+Tc^}W6MnCF-0&a}EA7PI-3 z{mo{wKA3the1qq{I-e8B4n2 z&>7K^n6A78r{uY}Aoln@(fs~~7A|S%rj}^>($rk97-7AiQQ=+%$Lx0$W0mm2fHIYl z{BN9EgIRhhEi_7CWoL3mXW2DSNttcz(-*s$225B_bc`rwDt^l4z7w?*QDjxEmPE{o zMgK!d#dF@-g3ZQ{F#+X!W6A(6kgdP{0!UREH`Tc>_;aJOD%D=qwXenvn$MMq75?6M zg*|{yi<&~<_KPg{ku{~_hvz1-x24Y}ktl2r{1$w4v?>J5O9d>{mx<-VkqWHC+Ecdv zUWZZ|{m9U!T^_7i&;Fr>czDB?1zuCe1lj7yL+3s=u`1^@#*nKafuGZbT!nmZLkhZg z33^_av;z)JiO|Oq_rb!GFA59qh3l+;IC?D+2kJY=h51{2_Q0QkqB61WCqcKf+S}hD zjP3};ki>l~i>8kNcFqoWXD5~Iwtkr2fsyB;RrF1t5dty-s0tFF*uT?HZpW6h?ittW zZq7|Dxg@=`^if5qpmF5yC>h3wZi!EQhHuL*Jd>L@?CidBWlsBC+Fnu^1I? zSvx2AhUb1!&)c;dHKNX~g*D=97DYH;r8_PT-=seYsQMXE!1RSS&AR>yXYO43kJQJH zWlu$2Pn_YSNn9jDR5rz9;%KVtA?anoX$Hp{MS?Rgi}`i+Z~B(14=N;4(9TaGWt{HA zcG`=SnICgK`SRo_p22HO!y*dTwLDC4Zw-M+g1DP{ZuqMqDgS#k&D)yuwgHuAHF26V zSmR{~t3?ThQcBN5d5tskDn#)p-=6Pm3bHfLIM_B8?;vf((J`BQ#($FB_L>G4V+_7^ z5pxdn3CoSJ;C~NyA1f`J@z3WIuu`^#uH+!8-q zh21!@x_;eXy{SwRMY=QHXBzf-(;%ZLu`137yAbt6axqL%3&-lzu5$U?D+`nPRO@(e zR}JDG!CqtKL`F>9bZTF_jH1eMca*rs3BO@h$o=cZ#h5Vr?x-A=X+QSmUz_qCC;mCc z)06(q&Hay~o;{)^-H&bCX8S(qvXSqaNa0~LwvzH_fGLrr^wWw!E45*t5J_9C?aWx8Du=yz)AEn@?BO2y6 zjnc_&(~C5hGCTq70j~Ro1#sHCwV`P-`T{DLE?#h>&NN5wJ^VRs6}l%Q%As7T$N@Hf>K+Ld z?$fxH7~XglaV6yG>BPn0^UiMw2=alg9Ju~Op3KZHAa8EpJyL1@&t3RqPn637@A_na zH6p(KX&HYS$SM^D<6MO|+{~5oA$V|A+(d~#0CmYj_u4kAP=BvW&M+ftKwnYCDC z%dsBOAQ;9XwEY~$EhPX$fXI;VOp z1VdxBRJ=P!WUIbM)_o1!CH-vG2C2bbs??A5i)1S&9)VFql{Q`4 z!YVwh01&fWCI+ZRq<>ApDEOT^!-o#Y!fb~(NC*b3r%7Gz(hnH%>JwbvyOBh)1X;YS z7qgU_9oFE+2K%g*NFPI<626Gdp=_oHo4P!{5if0Q z0L`xpq>2c*9b34sP_XFt0gRivO|>!yrg3W@vi!QKwPY$2{!JUO+}uvYDa z%zDwITt91;uP>5n`YNQJ{>ZHICRX|JvJ|q84C!bSUJfMVTito=KirBe3}B^S4ZBcD z1>|0(6L!g0Wf2t6!gh2;DGKR{WAIq{dxZA$*vDp~DH!;3=I^)rbz5TlEzD85cfrkC zL}Ps2#2AcHex90_<7hOLgpT+qaVs%~aoM+6guejqK16MR-TD_d3HY|YtzDd2Np*C! z6PIN6mV@3aSNp=DK0g5h?oxr0ld`A*r1Q)W!>fDL0j`28Wf8eVlftmCVbbt9Ud1zg zNXIe3m^Xt56M==dREFDw(yyk?3gF%5-fleE8?}OHXwaCwrk?QAWhbdFn#}!Ryx;NE zwAWkRKR$+xi6U`~cgMh+wVM&WY#I-bYOu@Qx$!_MI^ZiQR*1-_%hT~z0=HJtzdYBU zeyqclqhsZ{$zK+ePw``qU9NJJTI$f_b9a=q1i`;^P|&e5d%&KOPAn`z`rhc!OtnnEr93EhfXV!;}^$&9cTRz{gudPOD_nj_Th$yH?wKQcioF zZsBH$B-K)?V;O)00yw3=ce_gqh0Iw;NRs&^Yz104TZf~=&&`hzZT4_~zfJ+usU z4pNc&cK-5KoMG*9wiF3#{cwBtl8b8at>plF3qpLdDA7R$GVrP$@F(Oazw@HQ7=9{=AD*6Cq-{E8_*0g;{gE(vrJ86MXp`XgPBRvj5QFwo6u>Jj1;xt>Kaa=(pD5Zgmy zSeL_bV*QS77Kt}NZpJ^tK`vg|GBRKBmem+6<3#Cv_q0)XQT1me*I@|=%+yoOkuyn< z$`1(^Jvk%u(uI(=AXza>qCmMa%k~-9kc)imO(dMupDZ{>kl`*E6x*YJP5pY@5spNH zG>xO#;_>!sPif$>(0kLsPm@rkgI%oHWL7|4zKy@1>4=|B__{z8R5s+cx3yDY1L<{q zcBy-9_LV$fZHbaToNRxya`}HkPf>?({Ci&M)V~t59}?{OmN{d7*;`p>oR(y*G0r+w z$pN!3e<$sqzKkff&l(t25upi8!ff~SASx^pb&R!~tnKW71^@Ag{qQ`3pU81NA%#?s z6qH=-M24g?_^NdZGf*u1^~}5`f<$d(2DZ(w-uFv1Pl^ycEE*ZtfG%-AAH`Y@5j1|7 z0Qx926sR0vbXpVy>jnHCj)wM&jH29=F`9+VH5%YL;csfMK#3a3;u&ggzJYJzKss{_ zWWR~ml=Ci;4_tc~84DmaEZwf7UErl%{7`a!_~2dyb2zsQ>FO`Jh-=|^--9)zZ5LO< zI+-ICdz5{|@9S6o`{K|9`qgzyf9`3#TgG<{hB5p`8v0(xJ(le~wIQ0!>Q7kU* z?=h8#YsY-1;b{hk!)&W=*LW7?smEtLC(Vc5!U+))GJXeGi=7Mgy+Zo!Puim2=kmcN ze={+>W-$z#UWSgTUk|i<9D`jWR+_^A1r7_tqN`mH_v|X+V2coCfQv7PjDL0Q_pkqs zKq;7hn46#U>bCzqVodVLEcUuf>$83?jrng3;1koKkF7%)uZ zQG*BJkM`Zlrh@t@o=S%d_>^0)fC+PGEE`%W^;~-eN@+J&b!pK?bQcWCggbmvxgP3_ z+^(L*9i`DT7NJ}1L{Ti+aWJ5}+Ts3m&*${_zvha)O?+vR;9=;OiS}4%$tIbzD%9JB zo=zFoBQ3BLe`$2e)ULfx-{LCQr4!|;vJ|uIr^fqai_4k`mnb-mV5Ut6dBS@OV-L5S zj~3Oh4XXjiu~zXLe9|~VFOT_1v#)6`iJZMi9$lXkMsIi#+cLmv@~U|G7wsSH7q7k0 zGdN>8EIr|o=jat6gwuxQbCG=0<`lMzDm>%qu$~IrT=9!|8Q`nk9Nq} zmA_|8XZ&kq)h<7f+$-riW^U`^%UF)b^YOWtYz*2&Y(%;?fGm}l$;>(@zs3Zq;2jV3 z*|6U)EbaC+F7(e@N?B6qau%x!{`%U_kEdgl8UA}U=Ya0meM(((1MDZPz$#~R|Auqj zqo-Dx(&%$?)jL%SITAdo)dv8P%dnrwUIOLCosHfp3L2|!paq5U0S0ar1r37IjG}oy z-gw{0c=U^8B3&ju)9}?PoGlL8Nv0h4-hYqACX%fgtME59nY#b>2R_b+2zP>}_hqJR z3FCQgE-g<>mYx}`Sxh(Tu^p2yM9KnW{d(H^04Kg}ed|F94W~W=39fBtl%alWur$xJ z3V5LY54Ur8hbq?>*S6ja_1+_~59Q)(&ZO(yPIW&Xm^df|K$|;l#+T8-imZ!@{G9z1 z&fyXo9cJ(jtWn)DuUvIbdrcfAA0rhwAbdJ=g>#Tk_8JQF^BBOCp|baJqG&@!uN*4d*CiQkndx+*bSlh&X?oHRCmc)@F`iS;k|ff2DEpT0v?zQw_yA9YY(N*}H17 zUjV{SA5Dp}8RdZJQ-Wx7HXcQts5He7%!qTnJb#HUmw8DG7DF1B}+WwA*O#6%95LuibVYY9Uy@TkZ z1i=B86x;aTB|*@l6e(Ehy+jzmZ>#C@RBZK!8EQ`4!i{mhE%QkN0HEAg*w>^Z`K(FK zUz|suuS0)7Y}($x0at42bRhJXgG&?w6FYs~mB8PgY=%E3l2&tFx&wuL#u^zow}9gM z&E5rhgKDnng!W+SA7zPyv+F>&3^0BM9{E_=b%CG;Smp}+6W2huUsQkraFOda$e&u{ z-u}yyYbm-#C$ekXI+>_bxcI^Zo5^77@dM-IZj+lKe3Sp#k5?FXxdF!7dvkw1W~lq_cguLAU-m5>iBp&OpSMDiE~UJ{na6cE86-t zcEy4Oq#s7+f0&YY>898r+efwx<;K z&P_Yr*2~=JuAbPB9Q|f`Ak#fE@@chk^9@pHNG0Eb{oDDJGdS*rYz-{MHIA1`6y@@U zr;lpDf_)#23&lRrjsQ%Fyuh8dhXhp2;5y!YI?9(V>Zw|?Ypou>{uOai0F1J<;X9v9 zo`7@vpgi5DpK0Ryl7pl-P2kQl0jgs`E0{v+Yf*7jU=fxl7grz##!D^+@b1uclt3m7Q8+=ra}*@_8I+rP2q9up-=AD3^63<68=iJXZt4q!U_I~8o00!A~kxM zGhS&F{$)s&xP)Ra54`oBjL%6w#Yun0NzmTNK0t|M;Td~l5aIdM^-w99|Ee>T$Nf&e ziN>k{*LmrtZ_7?>^Wvm?F?m7Vt(a1*il5OnJHL6bcR5qO1vE+ zBMU5u#OCHvYDxLpTSH9S6R+bs&lI^HiX(^1=az|`i0;%FWK&%HZdj%I876SDpK)Sc zbJPK3qMOCLodOG`RmhZd2mvF$5qJ1xWZ9N>3B9_05|xLynH`zqewx9?e3(MBp# z0X#A)WSN7^$$R1BO3H^^{tM#F#Q}5>_m}?jeMGByvE)0Z*L6$ku@tV4$$V8S^hu}} z9Vo9=H4ZQ(og{y*?meA*%O|W4ie-Q{$NF_U=k7G*YU+`bCFw~qG)t@@@`K2PMzf=b zkW+Ms?CmTl;jqx%C_Kgdtn#l7_y?3mgVVP%lVJTuI_Y152aT^;<8U4-uWL|C`Db-1 zPNW_#?hY4OuFt%D0ZgwE<2Z#t@p`5(Pfm5Q=3YsoR)>=_-YL2K(uF??h%Ms@=Pa9v zRJSGtF?yt`basnMVdfF2ofbjm5(urSq0chz!J+Pc9+Jd}w8KbOsm!h@Q9gh$X zifO#;d2val!5lq<-SP@)MP*w?E>95j2{aiRM%Cf|xr2MIA)ooSkwk>f(XD)Hw^hQ1 zbe+JzKPG_0#`rABBiES`EVZMUhQ-4c51878SPI|#z?13Y2H_GJ@S#5M`D<)A#O9E` zcl2O%~C*1(A$1XWp&!UOz}~N1jD%IOeiY2p^n*FxJstsz)duG2ar< z_^`+Rjm3R@s1e-Pd9P#lrnW2Q#_cj@fr*er77-0ajBg&_(8A>~aufL8zTwl2j2PwQ zsD)|xH&Ovyo#XFL_Wl0XjE=9I|CTyb3KI6bychOr+Id*2%yfjhyS$@DKxR*Um-7`B z=qYzx0bg?bR39B1o{C-Nm0Z#{(7&_*I^J;3y(e^k7-2ZYG}cJNyX7YL0pB-1wr($5 zm0x3hwgOB*#opd2u!13~|N1_+yKqWT(8{nr%$}XPrj?!w;CTfX|K3@g-`VT4rt*&6G*>``X+F^yEzrx z%=aV46G4e9tTr|LG&)YMF5jZ@hxL&SzabnDJKj&d4uaHl6(VitQbwxV>T#Ym#1tdN zmyut^9QXj8HXJ&5LVGGeVmi`X;M^?aqvQ94$_%bP6=mVQADvF^J-+1|9@+Q$kW)Q6 zI4gFwqBE*_7>PHuAH(?fX_FWz_5W~}gOMzetQ>~KMJ4=a-r8u>o{79x{TFxyIx z1L>WHksd@=H66|q_wtCeaBh_?j83t?;wZi7qAh#ft8_No^j8r22QCUN>p)(?Yes9f zr~b;spkB8JL@$@Veq^K5En`}U9qTDN>Ee8b8&UpFf*QEdNcp%SQr2!9McF5l!*&ru_u?i74I9njt0OBoQG9ul|s zNig@&=Y^WRpVEv0pGL3s`}1khU9V}P72yCE?4h)kGdgCij0PwBI@&vi0Ev!-aHm?_ z$)b{l0re$R{~%lsndm3pH`BRV*Rrx3Vk2`4#Ta}5hC^iI2?Fi?0)0&)BMgE9XX0Ti zdESAwKY$+XK+of2%0Au!UC@}<1{(veB>O9=2f)}y%DW(Y+W!P^{z#!A)tGRveO(YH zz0Y4}W8C_vbkUUMG4ttmXs$nm!^r%3hJ&pa?~y)?Wh0xTH~{YQn)ZBqU7n_l>nnT9 zl^w7)<*rD94|x;kRa@9Q#-K(4voczndJB3-ckJI6R;?D#C9r2lY^CjZNL ziXu-a^4;u*Jz@%UgY>HCBkmWcaY#6U#UWPW!U6LZ4$u#~{*eK3N$qC(2%OL5c{aRF z5K0vbm*~Okut3VwXXeqsyP&oZ~Yb_J+uAbCh1(2-_j6I89xSjD6A5*vTox$F^AZM5gC~_0 zdglRs7P2x`|6>!%<#Km@zCAGu+W$`G;vKYUp+AxIt9nM6Jbzp9p@?;Y*?lig73ky_FkDAQ=6pL@czc|-im0Le)n~svgxO7elx6Q+IEQs zcxX4P{}w5kVmv@bwGSpRDUe-fGN|!Gc7QL}cxbv{G%o0d|qwIu+^13y4g3hzu9Qx16D_ zgvR&=Lq2yBq*h)x;^D5TRP1*lpu;C+zvo9i`>lgDLU)~5rd9A8ZO{RHnN(e!IE|FK zEuMUpQM{y^%kcZk4s+dQ7@n%%mPTB6isPPb2(rTVY+irLwTf>KfnVDg37~ zl4wns8s6v`i_w17JqHW_-cncS_1~!H0}lQ>X;($}y{~_{u8t(GRm&og<^8NpF!>@I z&yyk1B6|h=6+<>Sn4BKk1*z|Yi~(D!&dg3Ey{Rf0oYSayX?g*18F{<`+Bt+TZZLjU znd@w9(@NWF%`HtRUw*)cZOJf4bsV>(}2Q$HId;#8r zpK9tLTLd6R=71k^_u*-|xw1L6&In7jyVbP)%}dthX79p_^u6VWS+qLQPq$>d%{{&L zDtvay?Q46xM`8e}(IxYOad0ew%6e)IpNZB!BD%zAB5Z_12-$OqC!TcC?J@zihf9%s zdFx!@dh_|TaW?2mCtz3~rrVZ%GK}llS}in>pHS>q8@+hg0k~(9DTKb^`sH_7}|mNhK$JA zgEfCP#O2`&B!KXL)psrU)%-o8cDHmI5Uk|!b;d3nkL znlV_lB0$*OvUqvP59gBD$~kbgG)X0#S>bhEO|Iwz20l>xNrGrIT;TfGtU(DfBL4$C z!*udB^69pl&ddM38-;pi>hnqP3CF1Hj-dnK#tEsA&j;ntV?aE8>4%w2lfhmUndfhh zSrbR}?|tmDONdeiMy(aBR>?%xYU(N@8iP58=Nft~Nb`hEf6NIW@icHGzESaOx2GzS zkm1JbKTWBjRY=zLV_w#TzJZ6zusAU*Rr1`^ecEzZ0xE+VfQetlO%(IrmIZhh33v*9 z#wGY@`*=0j&`u0ZhQI^(Vaar+jcpZIIZX2~bC6JV+z1c|YrnD=9Ts&J(pR(=aWDt= zWqS|t!IiQDMT<}($kjTpQM0+ry~HlEL@NqPo}~)g5kEoxR|j>kJ+{|l+&g?Lg*>T; zk!X3t&tom~q38B3?~Nnd`*?2;UMlvcxH=tE1e}Gx%Dy&VEc(P{?(SYq{UW=}tkR;_ zLt;t!=nbKLNgXb>Qza?L!B%u8l5$gwm05A&L#0L(AL)79JHBqX%7C+4?je_yMdixT z?rig4r;dLBVJd$DVYP|UasL`w-TQnms(BDoqA{I~Abb4u@Y6>@4`t3b88UMR5}8~I z5)zL{T{c=K6r!H98CZRBHX2mwOUER;=Ct~({(MGL!2+6Hv%`3oaoMilyP|vVA-OY4$tF~iU379H;QI3Ji$T!dzABg+sdsJS^#@%TEqjG1~ zC~q$YHc@W%SQtVjM%w49c0X$VR}wdRCaY#)HD2H+>wtje>ldvcKI)vBTRR zqS^4hR~-!t!eb+?iq6RY>I@gu8eej9m$F;%Dr}cq9p)?F|CZ9OUOrd!x>x9QER*Gq zHFsSo*WgE3I^j=UDf2mps5}rZcsPeEA?+Q{)S0Wus&w$Xg}!!>XT^V?7)fzZ*n_U+ z7Gt0PHS%MgU2~vSMKyUg=4&2|^_jPCh8FQL3;Eo1rBtBu$_~aOZi4>fFBczcYc}+^ zF}`Z6z%%$Gqlx`ppa^^_ez|% zVls82h_CQLtFV<8pxJUa%>Js=XVZ|f@qA0;dJXOka(vNRT(Elc|FQMuaY;bywwbAE zC7GF;1C^SU10r9xS0WlCmgPT)*AQ8|wwI8T70 zq98oadH0=j?>+A?{^SRH?{BYft+m&8ovAa5?J^t7?lkofRa0dw@7_Ze@|6{It&Xpm z4#?_J_@bT8(cXLo09lj0?Zmd8rXfR`84jv}F1o)Sz(^o%C|?ZtS7G|E)1h{i#v&xD z<$6Is{=e1OA}+0?&Eyrl0U1em=#Y)fSY6WmEekpCi&rVYJCc53!pEA8_fqL$EtfK$ zQ==5j|4fEive7?({4F!O@H+Dpd&j7VsZ5(~;%j&Mjb^}0?t|oPOnoHCSl`(<`2mo5 z{}ke;c1uhV?A*Q~qfukdo8PRbDL@Fb)>wPe;1&>g@FX88ioqG6Cyy4`h*t;LbGz`( zPOkhX(g6}6)TOh+J4D^7h%Es>^&20e@hNuK$srqApHeI~tLY7fwtZJx z%kWsmQH~;fpBRp~?scvCf%JgN(UgN$Mr)Og)AthIJ|DbQ#7Mxg-@W8*2b(I6m(NHX zJ|OrTF-|cRN2=_~Jq1eE^xK&!mK<=#nMwG{+k7fG)Qg~;Vyjvun(7@9>;coCqrrmj znN~HX;G_h6G<*{rJZ7?YDUOP2L`wE4vvxsRuJ9w-yk##yz|cMbU)q$RxX#$t^tGig zx17yv_7wboRI$_W=-_it2d?}}z$`f+^=$9eR0`mrrpFCUgVM`VoT{dc?G(rabdCdpdAjxhvUg9` zrBz*V#Ga&dW1TsJ+3dPI-h>A7hBl)9h`7jyA~_dArf+<_-(tbXpqP-9DPNb*(SKTA z3J$T~{3xj{z0-u-+?M>SeD2XZwUlqi2P(gAe@odoY1yr9f@3vV#5dULF#Dap?h&2= z^$?;@T;&a-5jENS4sH)od$g)|=HQI`rxsXO|2RDQOX$D;{r@^KsW#Ez|Gh5KQtgr; z&3(3s{$Hb}Iwu{1dmnIb9BkcsZL4?YC^9HR!nV=wE?HJ&pH|&*jXoIHw+>iIP9)zo zo_gq$`^O`OfH+XYH9-`f)(SGGzz)6WbP?n>?E{G4R?|-JKq|Hp3|NXX2UvEFc=O)| zr{8FDyQ%7ijBA%UP7k!pa3TCZ0Nlrp=1{uxc_F=Nf6EgHH2Gx=JNXJ&277bN3Y1j3 z%hY*005cZzY&~NBUdxVgT}lPrMDn%Ks7Dc#g9Jq#E>Fy1Ox+^>0umJ|Pk^Z>Z)G>O zlUjJ!>nrS*_JdPJ=`wXczJtpx+(Qp;S+L3JhlaS9UTRr94*6p++{ZQlb1wzPHyp2a zBPjo%V;NDUvEHXl(UY`KFt^B!ol`3#wEG*aFZEQXXAY^ZI)4qSgKp_IWx>yGEYQ7C zyI=0<-^v#8eM!gnwO#NtX2(uOukk{sxUS8zF)i%lV~(1m!GEV1*_)S#B_#0@N`f>x zyr#PM-{itB%&gFJHksCt(JyF^{`ovb5}dG)0||a@CD((mPMPp4TIj_P+A=*Vmb| zx_yJFNSaes8A^7%*d)7Ti$T|IbMw$+H~xyZpO?qnt|z$N6B~V-f&1O{T|5Vsf<>@S z@dwYd1LvPz?=|dXzw}Q97Cf{8W^_vof5zmQ^^2dgSQoqYhg}C=FHIH;?d6){toiE< zEMpAGrfe8|Ni}KVxPBjSA?z$q1>)>*GX3|HaX2Id=Z*5lWPQUeBYRY_6lJmBAe(cq z$Xi2&3um32?@gRGY_D)s?vCi#N{SRKkEAiqDTyn5yEwep4)Ke&W=;lVG*sh;8@GLN z!8pGB32qVn+){7Z8U;+nBYEU3u@x{2EThJ62eySp!JD_pY9LUj1u#aqF#8{#zui(S z?{oi?eKeOkw%mp##Svmh@9!&Z9WYOlS0ww6zf6W%^7y&|!8QAb#w%O5J#hm+H{FqM z|E8ofv1vj|VgHjj!*?VZC`jFIC2(^3CH|8SK@WZ5Pa_VR=BgoG``2hEON@|Dq@6(a z=eFy73HpV}Xevb_vJ3%TlbVZb-zAStMh*g=P10fi zF5RGH9>0%lZajCJsQ1eZmu}qz$veL*|1OdCR^>J#YxuW3KiLKGZd`EpUb*&rz_1I* z^Yb4CWV+OH4`j{?Bsg_2-cPay_|uYRJIb7y5t01sqoF;6Nll2<6prm|I7p}rvj0MC ziAnYnC_KmF$2A2FST=!t47KytZz{RK*J9%qsDnMJILke(3M3?KklF1Kl75P^(BJpk zIW3a?hib5K-BhyXp$o2=KhEABBQUy5O=-Y9FPg+r@)a_s)PO1z@Qu|g+N_4oKc=*D z;yr0y?<-LeVw_Xc;n1AMmm>w;w@4&wCNzt?v6r*2bEBzjbyZw8rjAfx(IlEjg@0W) z=o8hS)IAMz`G;lppWyoceyClk!@N=%$c-@nH$f0MF7-@7ePX65FAb4iZ}F3ORGk+j zgSIsw_kCzn8h9N-N_l>4MaSOpU2wjdZdsH5Ineypa2>VX@Gfl$t_tKFE2h}MoW8bA zOuW%{PjdJDeV9!t7B$Fo5F&zv$SYS%mciKTKD0Gs21IGBs8t z{29b=mdt2S3cHEF9Z!xSZ@Yr1$-ka94gePW=UU2~CS#?tPQ+C>JOTnFX(tq06BE+7 z3*JbUl^GSCbm2fCcvQM~*XVEWae%vuq$qKJanb7wH4G2*$>&(1Vjh3TjX&pl=I*=H z0E;dEMiBi^N&jw95AF;CA@-9XFeiZAa!L?mbDDOp2_a(@E@o7p_#E63hfh4heh9l{ znTkK%jvz(HN|?|-w(vGV{N@xbzV9J64b|A`I3NR(NRB^@?#PzC znY(b?;%}dB?SCtS|6IA$^CANC>3^9xw#b`0XKRr=bGcP*pWWtjYhjn+r%=@q>3=RN zm)nTE$T-SQIFm#@rCR+&(ct->(d#SGI=H>3SdN!pTz@xnuGr}pgf6-#KGS$(N=x~x z3ctQ)P-?E}OQ5#_`?G}NIlk5{!>nsB?TQQ>o8c&#$DYrRHynQUZLU%RX@OxXN=YkA z$mvcV!hNdNG`=8%o%(_!^!da&I5L7RDW$o~k!1f)q%GV3l7%HP^0o*TkF$P@bzP^0 zJvu)Xt#^|T?!EZp9a!$kS`wh?>s?N>=xkR-ZZ}7{XVNs9R1$S_7hYNv^5*qZtc3GZ z>T0Y3)TXh(kM0)4FOj>ce(99u6ylImBQsOX59H?hQq+WND)?Abs+_o*KXjgZ-6g0K z9gWx9R)qb;oy^|m%wIBY$&Hsd5`XHU{S$_!KiN3-S!mZugv->H!Abu-e+B(8%8V)o zPfn$<)owJO%*9rS>HN)l@`(F)dw@?1BSzgRV!?HQa@BcDQ*Znz8^U9Nbu3l?9{p;)zXJ<@+ zt1Uk!7PsNr&$xZa_7|&TG<-@27GnU`b#wv{HsRMpRn?Y$wcH|L3V zeB#^kMGG-0&p(~$ZNJ#N^Nn|D>`|lHQQOCMANoEdi;O0X6bqs_OSZ?H?@8&)r|v1x zlV^NS!Whd#MhlVq?p@%$ex(Kla#0{trzSVX{cyArNK=uX<;If3AAga6*xOZMd&Tqp zM|n1%-)oT&g?}urNBLby$0CAnMX6SEn)ae~fB5V-;VBRt>HE>w+_=$s{^aKp1m2V2 zOscA{A=)vDk@g_>+W;$PD|02ufS8I&Hn`uNvI~T&Ct=~Pj1T!L>;&-cs$0RzrfYQD zsUCLHQ%Pw?>5Z5LdKlMJkibP?lElu|($%b&-`7q+ch5OH2ZCjqRY5BM!Sbj%(+*mj z6L8o5trIBMZuepu+G;Xw=qg90`u6PrzxY#i!bOKCP{?PR7MdL_Czc@g_qOV&K3 z4)7(v&?w`H_v)#d-D)P_RinUbKiOJwB_8@Xxp1RH7cpE1`*SB)&U)fj?SI^GI-0>h z#12GpMAJp$Ek7nF=my5sU-pa+Q&6~iM%W-<|!6d2sPlFk0AM55qzqnlFr=;Ir9}O)air(`X>8GF8LW z)10G{HB}DJ^pWY~c~d${kmudqs)Zrxi8@!Ge{yioJZgJxH7N}BtlVOqTI+I%Hl@Tg8R8fhRfqs-6n1=c7#O*KT6&CY8?kg-dpqoJh`5ykf-Kt79vzkz zqz8+pK%VS}hlS{$5lWfB8qz%^(hlmui3^=SCX4}R&Nr3SPFBl9em9kc6d`paAWKRS z`$kVUzqNG6i8WNP-X0b))(DhOHG$3g`dM{nNv*umJ&BmRmYRYn?x_a6CsB1` z8c*78ldR*HYvC->R*CR*MDxaCCrp82!uS@DtJ)UW7ttV-D~((Ur)LEZXK9okg*r|98Z&Ro_gd z#OQ0nw^?@3f3ZII#7Z2c0+qD(AwUw*QlX!Swdt$QA70Rl0+Qu%8|J zMAvc(!OKOdVD~jehqyCuesUG52QPrC`4ja>2~dZr>Ta8tx1)f6c3dCD(#hlN+6bKz ztJ|0N=1V+hlHee6G^XXP=6(x)DmlM+U9lOjG+);J>+aQo7SU0*eAepgLT}uLQ<&)x zLY!Js{;x~tzl7=6Q5XU^K8d6JZ}y1x;9Zn{Sf}lSR$?h2+flCkPQ_u9i;uavfBWqC z&)2)qfH}t%C9A)GVHG=nDXgaXEOY=&$vt4Ms1m=_=$o+tuG@dUBL)jC7L$j5+KN1z? zRlzMQIzuXZFOZVm4@_jBbgq%3U9 zHRkrfHqI@f0Vk!F@4dyevG=llEr)vxdQDjM(F-(g*j_W>iaf4#7NIny+xawN#uk+c z^cKvVzq1aKoFD#MQ(8^ zR@3a7ZSY2xO>?8Gujc_y$NQs7{Pz?E;qC>yf8RF$RNn$)rC3u}|7Sa;FJ+2S$R^rz zp5MHBGr)1<`A+4uV8yW~85E$GL}iPeVW;hR-}A?m5nEBfyN&(m zhZH3fW)1rg8unu5?+qk5(HbQm;#0g)?QtT9+6(|{B*I?Zz&dZ=McIX9*vO^u4`P_q z*e#3`RVPDQQ^fiozWSckp7}-XK2&XR&^FBotS`8x9`pv<^mai6`~y@FzDsA9=OWE4 zsjgop!RNufcDI`3(B$iw!Z>KlTL9wTF2MN*sJ+-`Es*wEdZY}uY-%iQy1kEmlHU|_ zKiN7ZYgQVEIP!TVVqo%2Rsc)lNQ?5inW`(T@CEk+4lb|y;lCPgo)v?{7(WDFaaYBwU*3<-s-+^r6W6`gs;|8f9aGN)+LhvTJR1Prb!UvJ= z9)ER3`J|pi=sjaL`sDA~W?nbJ^+tkHs?rl50`UD3EE*0qs1b;R+yQ0c=%RzJZ}?8n!6(%mmnbUIc+1Bh9UDeyk5{7+3%FDji|eK>T#)v8>^B zNB^dUe4FxyHU#=}g^0|@3*2)u9~}f-R7<=`#OJQM4RCz2M(q_xpBVKB>Kc&^yJFr zyfEl+@Pm3r^AEGfcdL=$rqU-s!m&4JRPn~A5cC}`-5&!0J&vo^>`VBd`nGURU6#Hj$IP1@S#F^tDNy}X4tDumcm!WRuHCg1uoAAB62 zyXd|_p9#mX=Vy{gD>+?d_~qo0b?WRjAZTReA`!Xq0n6`qhG;sW7ygh8EjRiw_wNwI zG4mrcPl*pVlA+aa(-Q9p$rygZ%$krER$#r^jd_*UU5kF-IVOPfw`44$V2(KRI;M#U z7xaXD_ow8d*I|N$Z^*giSCyhc|aby>9x<+3hF;Q!GVB~f{s_BE^&gh! zX0P_Z*H^l{ksB&1i)tADSXTgl@-6HQ{L-nzV@<#Eh^&i+6v1bVQSDsy=&i|#5N0-I zV|hjsy(A+$XPj|!t}T)TrYB&6BPznG$2fS7a8&q57%BAHWqbKWvOst}^v*U0>2A*o z!S1RW+YT2{zw0@1MPp;B-Z4LLQi3+Gx8sw<%oZ~sJq;9pLUTd3b!i0!c* z-9w5QY=T?qm2lX|F-_w%51`#(F=?;*i);epfW2L$MWi}Ow%Up>-Te9#CL#ir$yHPM z*$a>O$TIsCo#B1x0@S?b(mssB?Wcq-w8LpvII(|n#@8@mKp)<+j0CVOHe@s0d59b5 zFYa!p*ov2^4$@T$=}#D#Vrc9eE}2#IJfasVdTz>*^@}nc=RJIDlooRG1=s#{s`uBm zl19i6Q)NHo(gkid5On5v=Ov7*<&+=4;mYT(ItXlRjNJJt@)qdb-0u8ZR*VWG2^=lK zckMttfplJ@l}-|Z=D0dF05$UdPxP6~L8;3c+a`0k=Sm(mGr3z|#l3!=ObY%_hIEJ2 zI9}XZH!ZiJ7)*;PkQio0UeknfCk>Md00Nz>=`d zO~$(o!U5hcu6^~S5y1?#L(Ag(0zIE}*RZxV8aNk&H~g-v-Uy7Lq?T6`@Ov-BvabWVolWqh>6qgGAp4?@($1nqjCS}{&oc|?s$Xw+cbNKL5pHoI}{`a&&newiDMSykW$mHLJv{W(eN zQ3s{YS#ZM}L?ORv0O4O!ZyQQfRhs;ZeQzeD7Kzj9yJZUYFv6{=g&zDUY&Tj`601N+ z)U4>q2qg0%MJ+`hXurE~T~VU)Btry~dtuXrt#lUj_>36uC!|F*!;v189>E^6?bTDI zC5462eTbYC#H-0IHrb*13Y$S1Bfr1+zhUP|sgpN;t7ZM^2v}Y0LNPbph;CBUX#tbO zD(mXP39eE*W79O(oVE+LLXtcL<=Pc-^c>F6QX^yu)x`u)*t(Prg({VJ&*apGZM>k| z@DOZTl2BD6npAhHs&HY(10WoAhOZYV!(znEyG^L;k2n%2T5%zvhTP|1(q%rv8zP2m ze2ql@K|23*@JDvd2-Z-nCO^+nR=kP5kRk86AP_L&b7`l-__>rdTfG-?!}*hJC}RHe zA(%31n2gRJA_=nK**KO9Hb-!+!Z#8dhM~-afO*RWnDt7yZmp0ZgI*>(kDJ6j6YAogI7JlK^rD`VRM&`fPlC7BRp$f(mBUsS@WYtm7uw% z>j~u*2>e=FK^X4G_Bpr@MQ#U)Uqt$%#fU>*STJ{wVoy}9beW7-;Q@ju3G_vE?|mE` zbA7Z6ApDK34kZNeXJ_8)M6&0JVRN-%-L^pEUzE>H_0V0kg`+{8UOL6h*IE&*tAM)$tDnW6g+nWW9-H=@5}ZN8ybR!k%nN7CN*Oz z1p&eg2}k(m_b@j%qide<_iJ{&>p2v!4V(PlEi?+SJz~x#`vNjJHP zN}#Cmy9wZV1JzG274Ghc&yxc3ZH9}Y;)IJ)D*xZl^lIvMELBuc#) z#+C(i%I#!as-c`NXa9iEC6yOV11}y+--8|pt}Cn=w3({MLsl5 zU8bS-QTXSZlodShS7-2PGX=mzxCKI*7mkvJ7#u|##U8Bt4KWo}X&b|(iw*%VE)JBPH7!Z)rb>d0GpBIe3a0Vp@6;RF27zR+~OM|poxQ8>I~+bBzYFeQN7J0ouL;|>1GQZ0m4H=~E$l+*lV8qLnc ztkldz64yjzCBvnXZk5;#9QZQ4=)-hQI=wn1Ft`0W1xF8+rq*`U%&k=QZEkY9A%d70 zZ?y4?&mdWvD3~VdExEY9LNy^g2)$&mX zev0ZGd7!=g)XV?6i2XJ+4YK8QyvuN3zWxo$>W#{5=5XR(f^471}hV?lg ztElX-UTpobrg#N!eQ#s+<#Le0g>*~T6}Lx^HrFK|xvcUDpUr&9Rr*6K;VptCaF@c` zDt==|RZ#J8=%+k!I>3$U9{05{E_yKcokm4rO_%&=&zC~WO>7}kSTEs#St`Vsx_z2@ z7}Qo`4VlcG8Y&1{dL?_p8usvmMV#GsRMzT; z+uqyl0#Z13&OUR_^mui6;NwHQn2?yhq|4;3No>k*e!N7giw~^Fede=w`j}o7?lx2_ zrS0sC*zp1{osOQdYOENkx7l}<=lu8^$D_MRDNX$@d`6ZLu4S2aO>S1vA#Wl&4Hi3= z)EP0TeSm(cRw}eN4dy#PdE@aNZ8e-syAA@Bp<$>8ylyUmjqYud13Yp}-LjL=R{pqS z&*;f6ylyW&^fAARVHL9nS6FDeAwo&Fo@~T2Zh(S7fhWa;vj-kDIDjb0wZl}*^Q|1-aN0rpIdu!M?r%$lI@vuv9oVs z4AHpN^G@l)=Wd9+DXj7Y&3t@;~RN=jR^&0=L74*Jo0|ySC zN-6#?yx_SkIe80hJ`e^)VM-MfHfv-Q9rP z!eOViIjs7xVWa1{{8ip?NYxN&)1<2r$o~EQtq;s58iJg?G7s|!i?bQL4-;`b6xe`m z0!`qV!;AO(N>hX|xqhC#+9Ch9E2Y3?$<&lIG88q-tVZ*oo8Mw6$HQEk#HCw(+<9SL zR+#DPO|yzn%whC({WqNO}oZ*b2pED6#xEqT{gIq z=7qnUs5usw^6=11z0HLe_1_4x&ksK2DZPveJ-^G$-W!y(P{USF`auag+RW|JYyoOo z?kBesHf;Gv7`dN3kCrD>*@yNk-)OhaW3#i@ofF`YSd+{p2aqfAM9d>PfK#vNzhDRK zvVn5Rh^Xxspt^jjKgu@)oWs@2adl}>M^iGZ*87&z2bzaJIfE3!b$Q0BWZS|M86M-A ze>U0=HF~9lezH8nW~9kE?LmBS^J<)@-a#IDvTDoTRuJ~o8W@%xJaEje?nID&@qzsU z6pB9H@wlS8^Mmh-cfRs>Jd;fflOujBFeEbs+L={(lBjgQVZiQ+`#BUDrlcbhU*7bY~#Gf zs6<-8@a7xfS630XJZ9Z>=!e|jP3AIf=0F94Bqv5)!fbIduVqaFTn4Ix*iysO@h0V6 zkXbLUOf7h{N$pI^^1@7uC%+Z=QIT*`Y!%!^o6Jaco%ksk>94UKS=E{tT$VG6jEL-8 zwF(uJnlPh;Pd4d=TpoM3oW~3%&zzy6wqzsNjcVC;yVdiz^FHJ4j!K5B#^$VBoH6{? zJnw}+<>6GI=dW}QJ}^+^@ybTJGjF3f>0!vr=KA}-*($KzUOO8*wnW^-@uF?gZECM> zwM-qVY(vDtTd?X2%EF(_FZKJr1Um72?Zz*uF!!OGTCdqgYCLk+Wg*=bTd@eB)0tzBSZ^?~h8w2s%7z6A0`uF@>u@Avvqb6&hn2!}mgG z%apKc9f(txlMN*DiJ#Y4QhvZIG2L*Fknu{+xy<{k#r^Klp|69h*+W(qZExox}I9LEo$GOY}}pteHGfX}j@k$5?$KQH7JfR8@HS zcKky|!~?-W6RuAd*3mRYzV8WMBu&`;QACH=4mkSsO%mi3-}PngNj~UU-eh3|jBYJx z@X3t^zKfMPfE-=6=?E#LNNK(n6w@UFFdWd^kLm-;l!HunKSvp;rIWZIOTl;UaULSe z&CBgA+!${5aP}+5!9+YT_!>ngi0=U*Pe|?Xk@fR{!3c#(=>6d znrGkLgOK#1d_5IxIRmL8?O{3qWdFeH8GY$%-qF~CI(vuTVj;~p>r7~HRgC>J)}~Ef zuw5p)u&%~r+>-|h4kU%bX(e)y%}eBVkmN%Z?0Rhy!v3|xv;x;n&`n0gUS1wyHF`%( zUIg7*k?H|3ME+-r)IY?yqhj%-LBe2HmH*L5_w`R(Vt-q>>9QYW=8E)DZWdWE%Xy~> zLEpiISt4@#%2`fR;OMQmKK=rBeHx4J%uQ|DQ&Bc{cnPVMvm_tkZfj|?j`XQe8IvO* zDjoW&7heoBdaVHwEEKXjw|cg|wW4OCxL^%UaN#zKj)(h`q5N^|;^;x&HtK>C?Lq|i zp+?l^-YxK1J=E8;@YhsvWt_TRoH48Nu5M0n*nceamqw=jJ<6%9mZemFa@Fd0f+cFU zkpDF3N2Q>Kgjh{~w0%OwEWwNxc6#D0q-)eln1FnyssLRdDLlzc*A#Zj7U~d}M+%|D zg+hCX;Dfb&KzLh@j=7z`$4~Q1?(8R-Zr?Cwfk+hB5B%vBtiJYZm>0`t?)iY)P%9)j zygY5%MgH<=;nH{#Ug8!~U3;d?r}Aq2v+)hzD<`h3w)ytcI*JiCqp)b8>gpmvdbQ~+ zJb8A1IeYd6#TJ3L*G;i~^C&nJ5RkQ(E&BW8b>}1NPnfPC_z#*}M1E4|Bm7v1c>1`W z_5dS*oa0jjQEo)u9|X8?ia9#9`Uk8YC>tpRJk^XfmC0A^V{gOeWh&HnkZoolO|sm^ zl|A^II+w2U+l%ONgBrea8tizRJJdch+51cgan+KRuvePS{*%V<_Dr@y-5P8GKwl~p z-n?HXn2o+{q05UXfYU4H&NE*@ZgYMaZcMJwcWi~l+%HNCF!S_~=c@|!sXM`rT$w$d z$r_C3V4SC;@hI$%NxoHNYVltKu31KskhYn{1`?^wZm1%WCu&sqdjJfD7y&Q{pib_Ib%ol ztQHCREz!Uv&;NbVf%1uXyL|y|0dujfaEOplKB@I`+Wb)ED@8<9u;sb%TysE-uw-ih zt}lIj|DAw^yH3|euTVz|x+4k)EO6&ph0w@ZPQ9HgnzLoxbbPgGfIkeGahma9pjZ>%V$f&fNFY+q$(D9Wg9IOw^YsbJa-d1J-qg^02GQ-~?;psRQ6vni72O@kZ+ZMhF#Lf)F+`26YO z(v<`q_Mg3@eDTSntkW|ttg)bdqn$slfl|kIpg+W=Ow~6fgX96NNH@Lr)_FOz&(%8G zzV@+m4dGdj*b}>X_9f!WDd$#hkKP~zXZl!v7?S;q=*0wGtLBPmsrq-s3u|s{S&5^M z(-qkYeF)i!y^i{tkQYsR6A&A;HOXiZfQ2#Eq49msNmfztk8_0xASZl;PdwP`$x#+>WB66gMVHL8#vejnE*YnZS8#x&Zo`7 z3ie>=(K~h*H1%t2Idu@kB$FNZn4Yge?o{*9yBAU_wd(^mJc#}V>h@&@xUESps8A5B z--2Hz)TNrmt01TAoSuT?*7RKV98!eFn%0m`_;O)vK&PAEza7K>yuDLTN53_TUb<-s zjA^KNK;_j2=+&~~FmuK^aBiH?e6v)QesK)PS*u+Ot0t}~OYCF8)IZ*782!ATn?u^1 zoB2*i3Jsqd8a20sYZl;&Tp*TE7(SU=bM(3OfJ?R9n}bZD0C-Q9Vl|+p&ii4cK)?%` z5KC0IHG2Lbg&^#m8R0~&>u~uxi?YWe^B4MmQxUNkPaj2FcaGTs9v!Or zP5Bn_)1pMkmH?XRo^E~W^M1n{3G?RBhUvxLdR)1Qb-znPv^DO9>ZDZrd4xHAG)wG}Y9h9#d zrVQ;r#YXHvd~iB}Gd%Zr=|}o zYUv->>DmvDq?o<8YKFp_fTmSzbh3NM&d%SmL;0HDk43Y8-``n7ZaiWQY14?PO);t{ z+(=+_loF_<*I8Znmru*{dtt_NkkLnW(qn4YuamrQ<+n68csO}%#peP?^&b$coo!a+|9+G&Z>P?qslTtYtxyb>7|WQjQ6tr>yoI zE#s|D`Fz`evD@wm^l=mjeD07WkWgxso`8O*Atdl}pA z<<#_mg4<}26u*@qXb{ub8Gc}!Cb;GikmO#iSCvEMTNbR$y2)EE zu4t|t78vMa?1)B=TO*V46IV5#=^SnK2F=ZMMb`H5rad?nY~H65`cN>4B|^18WvQA{ z%6y2+)vI{FvX#o#Z?;R9zF|L=cWS=izV)=-`Pa^gZg&37)sJjGRrFR2`DHV}IrWUa zth0r@*Jz{BQJ6+B{0rer@GDRh~y0s=~CnJ#OH)Nr5`kcYHd80s zB;7drWYq=9d2{Bp6nQlK4wlflTkZCpFCuv7LuiC?p5f%z2fMFzP#(J~;0}+i(QGS= zra)(D7lG|Et7s61$Nj~%RBc)DI#9eLk#R=u*h&@1@(;XB`XqSwZR^z=zE|9Mr-n3OwI{hcNErTK^$B6+pRMC^)P4`<97erj5uc^2LnIxm(nZFChB)BH>CU zU$$d}YlLiQAnKPqW`M&6uGvbs_H~t+EEZ}d%x3Q;@m^{wHZBYj=PZEcmRsv=f@0?_ zqG<_Z1tztX3o!Ldl^6S4(zjEPlU-BlY!ZJMQ#M=FGBwJESGO)dFdZ0}a1!yKB6i?WKUk9j1YTgyTKSpzE{@k{q%_a2irhoDZx z+2MyVN;N~A1?p1W%&RRUg$RJ+Gd35jvZRU~5l6>zUG_t@?n6$$@Cx9X~O*javESuMYziY%hkQF4}9u%n1O>zgRnp`GPa z&i!J3fJL(dZpCoAaFpJ3wa(_O93RRy8a?Ir8JjxM;NpU4E?rHmw|m-1zD<>MyAGN9dKa9xlw$=T)-e*F(xKA+>*E*b_iyMr zOZR3vj-}Yvgei2!0N=zB5O%^RWHKe%>Y(AHF^1Q(`MrL2@o%5L+0I-)l0N3c@81p# z`rH@Rg!mBEK{n^ROPP=LYv}`3g*=3U9e!uTbwUSn{ z?))!DX;&x_q{#l-^@#1k{Vg%57?Z0xS!8pxZ|KFZdKs_kj8#2p3E%!haeB8^yyT93 zrfl(Yn_X*QZbF{!Rwl$qa5CZw=%b6^XhRfFpI%Yq(x2Vn5r5aKvc7)xHzK5lDI z6eGfZ6#vPgBH4h=5ypd|o6*4VCV)L4oYlzhSP-(9OoPo?!uMwP)_v=8GALAo&t%B<#%Ox1VEV&+Wv8jt7Pm>XRS{*k^h~`X zi1+5CLytJ<8QazR$4SW`{;*C%mBSU9{DqL%w-s^trE@y(buzu)bb1hcPo9+jGCgfzEaxpVb}nn$?@e503pu#8oN3J$6+ ztmyH}15G~G4_L~4Bq!9Le2hJvQSPZGs470dKQG0+P6+b)yt0CnXwM*VYC!1y2`z;2 z3b8$oG%DS)$bnxTu4d?(JZ8%lNDYB&FUSk*>5Ov3u7u;Yk^$wzf{)zv)-_iJgS!V* z^^9-TI+}uSFl!&L7=qslau^uSyG0kT6mIveaTD!5&6J2ZOsqM>XWPQZ1EXY7@ktj2 zqmS1NQYpz0?9>7q8!$F;!cr7Y# zMzb-3VRzWjsH2#`o!X239m8bE-Wr~r=?tsNfx8Ui*9nlIK$kDpo8wqVAmY3k6|6I3b$+0Mu7+yOZaiOEQfFfweO!PfU zB=Os(93h-9%z;;gjD=-XC;vz4l&0l6TXQxTO#SCRC9Tdaf@}4m7xZb>bG=s}n;$k~ z!4>%%4HlT*q#!qY^7`$;oW|xrA+-rTPe$eonTO-6?EH0bRP>T8a(kT1zR{&k@Gbhl z33(UvjS8EZQvd8QGG(1V(-^RP3GHY0kEY#$%G(5RVCD)cODloWiDDF~n=rG#)aJ7D~1mf%G*Fk$n+5HE8S|Pt` z3QjJ|5${Gsg=M(1MLqd{Wihp@>^Bg01M{4@-U5wr?M{-6K%b$g^P0M z1rz!G5x2$$X22@u*9Q13J1s|2*WA09n}W3Ur;ZqE-YY0B@N$aRL|DulkPice+-VS1<#znj52fjNEnMYwcSMa1^A{Ps*jK;P}eF)jEbpOSa}2ZuAgxacec+cm?tm{6-}4=FZdW`?+Vo?+|{#ZrHp_LR!H8wVv#OEB_vCG5HX(7nyf?GEwCRmv_BD67Cu$sHPQz7JPA%KpM8C(7NOHKHfJ-9NI>*>Bi< zN^n|bDYCZP41VTFO~CPZor7Hx)^gt&NPAWvTM{z{nDoIbD7oh9Mrm@_Sj!u!%OmwL zew?Pg&c`X#S^l-R^nqE)#t7oEy|CPO2f~pao?sWy0~qxKCPO=6C(P!dCDBRzjE41(tE_7u`Ie+ut9U64OBlIHC})TUWM$H{WT_$36HL=S60X4T;aI5I!zmg|mC$H4osv42b2D zhb%$6i?e#~SJDe09}DOmbn~qG<=}759)1NQ&88Fc$B~0epqvE3@36TW+cND zFK)H&oST+A^R!&F-*GnL>`0%6M%h(I;)>9wXKcr^jZA9(q!jku+cWyw1?hIrY&8@S zIjST-7pcds@E*A_v;{nSy5wE!dXCG>cEzb0Fn15fwV>XRbt+1$+eW|vCY)V8`@98E z9L=ED-(-h-8a2EK_np4Owi=VdgM@C z|CV&;p=(o!af{??RsNEt~jVfAdRfxSS)E@NV~45h{*3%^^rz(=DgR%eR*awil20aHH^%9ZV$9pCrpJNgc1XN* zV~TAYdl^LyI~FdMz4zbt+JCkVB*?iY$v%1=OYz#1x1esm-ep|MZ?a8o_=zp68;{5B zkJeCZCffI}F?yZ_5_3t*e|3CrNU*6pxHt04Ze(1X@yS!)GZF>KzHFU6CPg*aN!Zr9 z*fiYCft+>CRqJzvJr@GTpVBVCf6yQ4O@;HcCH#6XwI zwv^!_RDIa$`VbuNZN8g;v5Z+d<}#~Me43mQ<6U6^^HBGZNMBEonu%g#c>G}Fhk4Yy ztK-DRg4}yAM0t(tpV=Ube~PP>G_%j7A0PM5Va24Jwlv|~-&3`|wPGuC&qW}oF- z6q`1kS72?RavM_QEq$29_%s&8b9~mYPqCW^=X?C>t`auXui>rxb#7D8-Bs5d4Zm%| zbC(P$jVAU@CM+}CE)$PNhEo)dHLK4>7}59%g|2%xsfcL5Wp|S8#ZDr*$^Vz?q1Z9Z z-VCQ^Eeq<230b*R@&A2S-!8ch%RY+p9Q3vL&reMwCP7y<^p!?@^f=s~Ph3Z&Gp=Xc zV}6aI1YXqA_Zt4R``TtC^5%%VRaf#!4^eyxtVdXkuvZ4ZjLu<2Quk!99BUW&rElbW z>`+fhS0p(j^t5dIll4{QImrv}>7s9}j>&aDShI2Ubgx+Al?s}F4s$jUvW23ScNm=b z%%qyFFp|7iJHPUP)04vwr&T;^RW>bMF<r>(MNe8rXiY%V z7&#RM&5H?7aa8J=ww6+{kteN61RKW;`lCJ-gtHpSA9&k7?aL};mH_L?o|KO4@FO=UOT_&hf_ZB2n|v0&Tmwi6mQAD#6BX1`+IYyqx7yolMw@kW)y8jH z^Cn4H0WR*Gy$X^|WHqtBHP{y3ba=Shz#eEm;&T~ocQ;S;XVHDV6TD~6@}|X|s%}2z zTJ9#XH#7TQxD)78z4iETJaX%ChHxP$RX{89{!1;lNUmA}OLcSLAIs|kK}`BZ_m!m` zdJao>);2N%CNup$B`Cx;p4!Xu*tPIms~Vf}zPq(nk3EZt5#5ZUnQ%QmZVa>ACh*20+P`_#Ugr;sGxsXO9l288>QM&YC=fJ!Ri}QZR2}gPE9|lM z@MASp9y{lj?GhU9{8T3iz3D)H$~Jyplw#j; zdk$ToL$xWiO#nk5-3-TD+ieqN{hnnbM`(ENTGsVIt^avE5NG#i@A)ozZ{yv6zI2^e zx5P!gVtT~VET)`<&oaK!?09O`Hm*zN%1MiRbtxTs)~5h63dw56J_w59WP-bxobPha zQuy?|gYCYa9s_mDUk8WKPFTJ&RXWd4y-ea|0ezVJ+5Ca@n7is^+�W$&LpDk4Ia* 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 z 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 literal 0 HcmV?d00001 diff --git a/hw2/hw/screenshots_grafana/2.png b/hw2/hw/screenshots_grafana/2.png new file mode 100644 index 0000000000000000000000000000000000000000..6ebe9fabc5c822a5386b34ae9a10e09b0a985789 GIT binary patch literal 96097 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%0AYBnAlJrChEVBOiAc+xpI3hv0&SIcQ5ciQg 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 Gp#K9-L2|MH literal 0 HcmV?d00001 diff --git a/hw2/hw/settings/prometheus/prometheus.yml b/hw2/hw/settings/prometheus/prometheus.yml new file mode 100644 index 00000000..6bdf88e7 --- /dev/null +++ b/hw2/hw/settings/prometheus/prometheus.yml @@ -0,0 +1,10 @@ +global: + scrape_interval: 10s + evaluation_interval: 10s + +scrape_configs: + - job_name: demo-service-local + metrics_path: /metrics + static_configs: + - targets: + - local:8080 diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py index 53442985..389f5226 100644 --- a/hw2/hw/shop_api/main.py +++ b/hw2/hw/shop_api/main.py @@ -1,8 +1,12 @@ from fastapi import FastAPI +from prometheus_fastapi_instrumentator import Instrumentator + from shop_api.routers import cart_router, item_router, chat_router + app = FastAPI(title="Shop API") +Instrumentator().instrument(app).expose(app) app.include_router(cart_router) app.include_router(item_router) app.include_router(chat_router) \ No newline at end of file From d9c1bc312f9312fe93da48b3aaa1ee24edc42b37 Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sun, 26 Oct 2025 17:31:35 +0700 Subject: [PATCH 5/8] add hw4 --- hw4_5/Dockerfile | 14 ++ hw4_5/README.md | 22 ++ hw4_5/alembic.ini | 147 +++++++++++++ hw4_5/alembic/README | 1 + hw4_5/alembic/env.py | 97 +++++++++ hw4_5/alembic/script.py.mako | 28 +++ .../a976573e4c55_added_required_tables.py | 54 +++++ hw4_5/docker-compose.yml | 25 +++ hw4_5/requirements.txt | 23 ++ hw4_5/scripts/start.sh | 3 + hw4_5/src/config/__init__.py | 1 + hw4_5/src/config/settings.py | 25 +++ hw4_5/src/crud/__init__.py | 2 + hw4_5/src/crud/base.py | 83 ++++++++ hw4_5/src/crud/cart.py | 196 +++++++++++++++++ hw4_5/src/crud/item.py | 56 +++++ hw4_5/src/db/__init__.py | 1 + hw4_5/src/db/deps.py | 7 + hw4_5/src/db/session.py | 17 ++ hw4_5/src/main.py | 18 ++ hw4_5/src/models.py | 63 ++++++ hw4_5/src/routers/__init__.py | 2 + hw4_5/src/routers/cart.py | 101 +++++++++ hw4_5/src/routers/item.py | 147 +++++++++++++ hw4_5/src/schemas.py | 50 +++++ hw4_5/start_pg_app.py | 13 ++ .../transactions_problems_scripts.py | 198 ++++++++++++++++++ 27 files changed, 1394 insertions(+) create mode 100644 hw4_5/Dockerfile create mode 100644 hw4_5/README.md create mode 100644 hw4_5/alembic.ini create mode 100644 hw4_5/alembic/README create mode 100644 hw4_5/alembic/env.py create mode 100644 hw4_5/alembic/script.py.mako create mode 100644 hw4_5/alembic/versions/a976573e4c55_added_required_tables.py create mode 100644 hw4_5/docker-compose.yml create mode 100644 hw4_5/requirements.txt create mode 100644 hw4_5/scripts/start.sh create mode 100644 hw4_5/src/config/__init__.py create mode 100644 hw4_5/src/config/settings.py create mode 100644 hw4_5/src/crud/__init__.py create mode 100644 hw4_5/src/crud/base.py create mode 100644 hw4_5/src/crud/cart.py create mode 100644 hw4_5/src/crud/item.py create mode 100644 hw4_5/src/db/__init__.py create mode 100644 hw4_5/src/db/deps.py create mode 100644 hw4_5/src/db/session.py create mode 100644 hw4_5/src/main.py create mode 100644 hw4_5/src/models.py create mode 100644 hw4_5/src/routers/__init__.py create mode 100644 hw4_5/src/routers/cart.py create mode 100644 hw4_5/src/routers/item.py create mode 100644 hw4_5/src/schemas.py create mode 100644 hw4_5/start_pg_app.py create mode 100644 hw4_5/transactions_problems_scripts/transactions_problems_scripts.py diff --git a/hw4_5/Dockerfile b/hw4_5/Dockerfile new file mode 100644 index 00000000..115353d8 --- /dev/null +++ b/hw4_5/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.12 + + +RUN apt-get update && apt-get install -y gcc +RUN python -m pip install --upgrade pip + +WORKDIR app +COPY . . + +RUN pip install -r requirements.txt + +RUN chmod -R +x scripts/start.sh . + +ENTRYPOINT [ "sh", "scripts/start.sh" ] \ No newline at end of file diff --git a/hw4_5/README.md b/hw4_5/README.md new file mode 100644 index 00000000..39981942 --- /dev/null +++ b/hw4_5/README.md @@ -0,0 +1,22 @@ +# ДЗ 4 + +1. Запуск приложения и БД +```sh +# Postgres +docker compose up + +# Окружение +python3.12 -m venv .venv +source .venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt + +# Миграции и запуск приложения +source scripts/start.sh +``` + +2. Запуск скриптов для демонстрации проблем с транзакциями: +```sh +export PYTHONPATH=${PWD}/src/ +python transactions_problems_scripts/transactions_problems_scripts.py +``` \ No newline at end of file diff --git a/hw4_5/alembic.ini b/hw4_5/alembic.ini new file mode 100644 index 00000000..7f7f01de --- /dev/null +++ b/hw4_5/alembic.ini @@ -0,0 +1,147 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +# sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/hw4_5/alembic/README b/hw4_5/alembic/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/hw4_5/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/hw4_5/alembic/env.py b/hw4_5/alembic/env.py new file mode 100644 index 00000000..0756f5c4 --- /dev/null +++ b/hw4_5/alembic/env.py @@ -0,0 +1,97 @@ +from logging.config import fileConfig +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from alembic import context + +from src.config import settings +from src.models import Base + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_url(): + user = settings.POSTGRES_USER + password = settings.POSTGRES_PASSWORD + host = settings.POSTGRES_HOST + port = settings.POSTGRES_PORT + db = settings.POSTGRES_DB + url = f"postgresql://{user}:{password}@{host}:{port}/{db}" + return url + + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url", None) + if not url: + url = get_url() + + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + configuration = config.get_section(config.config_ini_section) + if not config.get_main_option("sqlalchemy.url", None): + configuration["sqlalchemy.url"] = get_url() + + connectable = engine_from_config( + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + compare_type=True, + include_schemas=True, + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/hw4_5/alembic/script.py.mako b/hw4_5/alembic/script.py.mako new file mode 100644 index 00000000..11016301 --- /dev/null +++ b/hw4_5/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/hw4_5/alembic/versions/a976573e4c55_added_required_tables.py b/hw4_5/alembic/versions/a976573e4c55_added_required_tables.py new file mode 100644 index 00000000..4b68d1a2 --- /dev/null +++ b/hw4_5/alembic/versions/a976573e4c55_added_required_tables.py @@ -0,0 +1,54 @@ +"""Added required tables + +Revision ID: a976573e4c55 +Revises: +Create Date: 2025-10-23 22:02:14.410133 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'a976573e4c55' +down_revision: Union[str, Sequence[str], None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('carts', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('price', sa.Float(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('items', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('name', sa.String(), nullable=False), + sa.Column('price', sa.Float(), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='false', nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('cart_items', + sa.Column('cart_id', sa.UUID(), nullable=False), + sa.Column('item_id', sa.UUID(), nullable=False), + sa.Column('quantity', sa.Integer(), nullable=False), + sa.Column('available', sa.Boolean(), server_default='false', nullable=False), + sa.ForeignKeyConstraint(['cart_id'], ['carts.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['item_id'], ['items.id'], ), + sa.PrimaryKeyConstraint('cart_id', 'item_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('cart_items') + op.drop_table('items') + op.drop_table('carts') + # ### end Alembic commands ### diff --git a/hw4_5/docker-compose.yml b/hw4_5/docker-compose.yml new file mode 100644 index 00000000..76937e22 --- /dev/null +++ b/hw4_5/docker-compose.yml @@ -0,0 +1,25 @@ +version: "3" + +volumes: + postgres-data: + +networks: + net: + +services: + postgres: + container_name: postgres_hw + environment: + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + image: postgres:15 + volumes: + - postgres-data:/var/lib/postgresql/data + restart: on-failure + ports: + - "5432:5432" + networks: + - net \ No newline at end of file diff --git a/hw4_5/requirements.txt b/hw4_5/requirements.txt new file mode 100644 index 00000000..134cdfd7 --- /dev/null +++ b/hw4_5/requirements.txt @@ -0,0 +1,23 @@ +# Основные зависимости для ASGI приложения +fastapi>=0.117.1 +pydantic==2.11.9 +pydantic-settings==2.11.0 +sqlalchemy==2.0.44 +alembic==1.17.0 +asyncpg==0.30.0 +greenlet==3.2.4 +psycopg2==2.9.11 +uvicorn>=0.24.0 +websockets>=1.8.0 + +# Зависимости для тестирования +pytest>=7.4.0 +pytest-asyncio==1.2.0 +pytest-asyncio>=0.21.0 +httpx==0.23.1 +Faker>=37.8.0 +pytest-cov==7.0.0 +gevent==25.9.1 + +# prometheus +prometheus-fastapi-instrumentator==7.1.0 \ No newline at end of file diff --git a/hw4_5/scripts/start.sh b/hw4_5/scripts/start.sh new file mode 100644 index 00000000..ecc390c8 --- /dev/null +++ b/hw4_5/scripts/start.sh @@ -0,0 +1,3 @@ +#! bin/bash +PYTHONPATH=. alembic upgrade head +python start_pg_app.py \ No newline at end of file diff --git a/hw4_5/src/config/__init__.py b/hw4_5/src/config/__init__.py new file mode 100644 index 00000000..61c140c9 --- /dev/null +++ b/hw4_5/src/config/__init__.py @@ -0,0 +1 @@ +from .settings import settings \ No newline at end of file diff --git a/hw4_5/src/config/settings.py b/hw4_5/src/config/settings.py new file mode 100644 index 00000000..0dad299c --- /dev/null +++ b/hw4_5/src/config/settings.py @@ -0,0 +1,25 @@ +from pydantic import Field +from pydantic_settings import SettingsConfigDict, BaseSettings + + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env") + + # Server + HOST: str = Field("0.0.0.0") + PORT: int = Field("8000") + + # Postgres + POSTGRES_HOST: str = Field("0.0.0.0") + POSTGRES_PORT: int = Field("5432") + POSTGRES_USER: str = Field("postgres") + POSTGRES_PASSWORD: str = Field("postgres") + POSTGRES_DB: str = Field("postgres") + + @property + def DATABASE_URL(self): + return f"postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}" + + +settings = Settings() \ No newline at end of file diff --git a/hw4_5/src/crud/__init__.py b/hw4_5/src/crud/__init__.py new file mode 100644 index 00000000..181a8302 --- /dev/null +++ b/hw4_5/src/crud/__init__.py @@ -0,0 +1,2 @@ +from .item import crud_item +from .cart import crud_cart \ No newline at end of file diff --git a/hw4_5/src/crud/base.py b/hw4_5/src/crud/base.py new file mode 100644 index 00000000..9659cff4 --- /dev/null +++ b/hw4_5/src/crud/base.py @@ -0,0 +1,83 @@ +from typing import TypeVar, Generic, Optional, List, Any +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, Result +from pydantic import BaseModel +from uuid import UUID + +from ..models import Base + + + +ModelType = TypeVar("ModelType", bound=Base) +CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) + + + +class CRUDBase(Generic[ModelType, CreateSchemaType]): + def __init__( + self, + model: ModelType + ): + self.model = model + + async def create( + self, + db: AsyncSession, + *, + obj_in: Optional[CreateSchemaType] = None, + **kwargs: Any + ) -> ModelType: + + if obj_in is not None: + obj_data = obj_in.model_dump() + else: + obj_data = kwargs + + db_obj = self.model(**obj_data) + db.add(db_obj) + await db.commit() + await db.refresh(db_obj) + return db_obj + + + async def get( + self, + db: AsyncSession, + id: UUID + ) -> Optional[ModelType]: + + query = select(self.model).where(self.model.id == id) + result: Result = await db.execute(query) + return result.scalars().first() + + + async def update( + self, + db: AsyncSession, + *, + id: UUID, + obj_in: dict | BaseModel, + skip_deleted_check: bool = False # если модель не имеет deleted + ) -> Optional[ModelType]: + + db_obj = await self.get(db, id) + if not db_obj: + return None + + if hasattr(db_obj, 'deleted') and not skip_deleted_check: + if db_obj.deleted: + return None + + if isinstance(obj_in, BaseModel): + update_data = obj_in.model_dump(exclude_unset=True) # только переданные поля + else: + update_data = obj_in + + for field, value in update_data.items(): + if hasattr(db_obj, field): + setattr(db_obj, field, value) + + db.add(db_obj) + await db.commit() + await db.refresh(db_obj) + return db_obj diff --git a/hw4_5/src/crud/cart.py b/hw4_5/src/crud/cart.py new file mode 100644 index 00000000..94c0b7f9 --- /dev/null +++ b/hw4_5/src/crud/cart.py @@ -0,0 +1,196 @@ +from typing import List, Optional +from uuid import UUID + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, func +from sqlalchemy.orm import joinedload + +from .base import CRUDBase +from ..models import CartModel, CartItemModel, ItemModel +from ..schemas import CartItemResponse, CartResponse + + +class CRUDCart(CRUDBase[CartModel, None]): + + async def get_carts_with_filters( + self, + db: AsyncSession, + 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, + ) -> List[CartResponse]: + + quantity_subq = ( + select( + CartItemModel.cart_id, + func.coalesce(func.sum(CartItemModel.quantity), 0).label("total_quantity") + ) + .join(ItemModel, CartItemModel.item_id == ItemModel.id) + .where(ItemModel.deleted.is_(False)) + .group_by(CartItemModel.cart_id) + .subquery() + ) + + query = ( + select( + CartModel.id, + CartModel.price, + func.coalesce(quantity_subq.c.total_quantity, 0).label("total_quantity") + ) + .select_from(CartModel) + .outerjoin(quantity_subq, CartModel.id == quantity_subq.c.cart_id) + ) + + if min_price is not None: + query = query.where(CartModel.price >= min_price) + if max_price is not None: + query = query.where(CartModel.price <= max_price) + if min_quantity is not None: + query = query.where(func.coalesce(quantity_subq.c.total_quantity, 0) >= min_quantity) + if max_quantity is not None: + query = query.where(func.coalesce(quantity_subq.c.total_quantity, 0) <= max_quantity) + + query = query.offset(offset).limit(limit) + + result = await db.execute(query) + cart_rows = result.fetchall() + + if not cart_rows: + return [] + + cart_ids = [r.id for r in cart_rows] + cart_info = {r.id: r for r in cart_rows} + + cart_items_result = await db.execute( + select(CartItemModel) + .options(joinedload(CartItemModel.item)) + .join(ItemModel, CartItemModel.item_id == ItemModel.id) + .where(CartItemModel.cart_id.in_(cart_ids)) + .where(ItemModel.deleted.is_(False)) + ) + cart_items = cart_items_result.scalars().all() + + cart_items_map = {} + for ci in cart_items: + cart_items_map.setdefault(ci.cart_id, []).append(ci) + + response = [] + for cart_id in cart_ids: + row = cart_info[cart_id] + total_quantity = row.total_quantity + items = [] + if total_quantity > 0: + items = [ + CartItemResponse( + id=ci.item_id, + name=ci.item.name, + quantity=ci.quantity, + available=ci.available, + ) + for ci in cart_items_map.get(cart_id, []) + ] + response.append( + CartResponse( + id=cart_id, + items=items, + price=row.price + ) + ) + return response + + + async def add_item_to_cart( + self, + db: AsyncSession, + *, + cart_id: UUID, + item_id: UUID, + ) -> bool: + + cart_result = await db.execute( + select(CartModel) + .where(CartModel.id == cart_id) + .with_for_update() + ) + cart = cart_result.scalar_one_or_none() + if not cart: + return False + + item_result = await db.execute( + select(ItemModel) + .where(ItemModel.id == item_id) + .where(ItemModel.deleted.is_(False)) + ) + item = item_result.scalar_one_or_none() + if not item: + return False + + cart_item_result = await db.execute( + select(CartItemModel) + .where(CartItemModel.cart_id == cart_id) + .where(CartItemModel.item_id == item_id) + ) + existing_cart_item = cart_item_result.scalar_one_or_none() + + if existing_cart_item: + existing_cart_item.quantity += 1 + price_delta = item.price + else: + new_cart_item = CartItemModel( + cart_id=cart_id, + item_id=item_id, + quantity=1, + available=True + ) + db.add(new_cart_item) + price_delta = item.price + + cart.price += price_delta + + try: + await db.commit() + return True + except Exception: + await db.rollback() + return False + + + async def get_cart_with_items( + self, + db: AsyncSession, + *, + id: UUID, + ): + cart = await db.get(CartModel, id) + if not cart: + return None + + await db.refresh(cart, ["items"]) + + items = [] + total_quantity = 0 + for ci in cart.items: + if ci.item.deleted: + continue + items.append( + CartItemResponse( + id=ci.item_id, + name=ci.item.name, + quantity=ci.quantity, + available=ci.available, + ) + ) + total_quantity += ci.quantity + + return CartResponse( + id=cart.id, + items=items, + price=cart.price + ) + + + +crud_cart = CRUDCart(CartModel) \ No newline at end of file diff --git a/hw4_5/src/crud/item.py b/hw4_5/src/crud/item.py new file mode 100644 index 00000000..36a00268 --- /dev/null +++ b/hw4_5/src/crud/item.py @@ -0,0 +1,56 @@ +from typing import List, Optional +from uuid import UUID + +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select + +from .base import CRUDBase +from ..models import ItemModel +from src.schemas import ItemCreate + + +class CRUDItem(CRUDBase[ItemModel, ItemCreate]): + + async def get_items_with_filters( + self, + db: AsyncSession, + offset: int, + limit: int, + min_price: Optional[float], + max_price: Optional[float], + show_deleted: bool, + ) -> List[ItemModel]: + + query = select(ItemModel) + query = query.where(ItemModel.deleted.is_(show_deleted)) + + if min_price is not None: + query = query.where(ItemModel.price >= min_price) + if max_price is not None: + query = query.where(ItemModel.price <= max_price) + + query = query.offset(offset).limit(limit) + + result = await db.execute(query) + return list(result.scalars().all()) + + + async def soft_delete( + self, + db: AsyncSession, + *, + id: UUID, + ) -> Optional[ItemModel]: + + obj = await self.get(db, id) + if obj is None: + return None + + obj.deleted = True + db.add(obj) + await db.commit() + await db.refresh(obj) + return obj + + +crud_item = CRUDItem(ItemModel) \ No newline at end of file diff --git a/hw4_5/src/db/__init__.py b/hw4_5/src/db/__init__.py new file mode 100644 index 00000000..d4889125 --- /dev/null +++ b/hw4_5/src/db/__init__.py @@ -0,0 +1 @@ +from .deps import get_db \ No newline at end of file diff --git a/hw4_5/src/db/deps.py b/hw4_5/src/db/deps.py new file mode 100644 index 00000000..aa2d3296 --- /dev/null +++ b/hw4_5/src/db/deps.py @@ -0,0 +1,7 @@ +from src.db.session import async_session + + + +async def get_db(): + async with async_session() as session: + yield session \ No newline at end of file diff --git a/hw4_5/src/db/session.py b/hw4_5/src/db/session.py new file mode 100644 index 00000000..5d1600d6 --- /dev/null +++ b/hw4_5/src/db/session.py @@ -0,0 +1,17 @@ +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker + +from src.config import settings + + + +db_engine = create_async_engine( + settings.DATABASE_URL, + pool_pre_ping=True, + max_overflow=0, +) + +async_session = async_sessionmaker( + autocommit=False, + autoflush=False, + bind=db_engine +) diff --git a/hw4_5/src/main.py b/hw4_5/src/main.py new file mode 100644 index 00000000..87bbdd48 --- /dev/null +++ b/hw4_5/src/main.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI + +from src.routers import item_router, cart_router + + + +pg_app = FastAPI() + +pg_app.include_router( + router=cart_router, + prefix="/carts", + tags=["Cart"] +) +pg_app.include_router( + router=item_router, + prefix="/items", + tags=["Items"] +) \ No newline at end of file diff --git a/hw4_5/src/models.py b/hw4_5/src/models.py new file mode 100644 index 00000000..41d6a685 --- /dev/null +++ b/hw4_5/src/models.py @@ -0,0 +1,63 @@ +from typing import List +from uuid import UUID as UUID_PY, uuid4 +from sqlalchemy import String, Boolean, Integer, Float, ForeignKey +from sqlalchemy.dialects.postgresql import UUID as UUID_PG +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship + + +class Base(DeclarativeBase): + pass + + +class ItemModel(Base): + __tablename__ = "items" + + id: Mapped[UUID_PY] = mapped_column(UUID_PG(as_uuid=True), primary_key=True, default=lambda: uuid4().hex) + name: Mapped[str] = mapped_column(String, nullable=False) + price: Mapped[float] = mapped_column(Float) + deleted: Mapped[bool] = mapped_column( + Boolean, + nullable=False, + default=False, + server_default="false" + ) + + +class CartModel(Base): + __tablename__ = "carts" + + id: Mapped[UUID_PY] = mapped_column( + UUID_PG(as_uuid=True), + primary_key=True, + default=lambda: uuid4().hex + ) + + items: Mapped[List["CartItemModel"]] = relationship( + back_populates="cart", + cascade="all, delete-orphan", + lazy="selectin", + ) + price: Mapped[float] = mapped_column(Float, default=0.0, nullable=False) + + +class CartItemModel(Base): + __tablename__ = "cart_items" + + cart_id: Mapped[UUID_PY] = mapped_column( + ForeignKey("carts.id", ondelete="CASCADE"), + primary_key=True + ) + item_id: Mapped[UUID_PY] = mapped_column( + ForeignKey("items.id"), + primary_key=True + ) + quantity: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + + cart: Mapped["CartModel"] = relationship(back_populates="items") + item: Mapped["ItemModel"] = relationship(lazy="joined") + available: Mapped[bool] = mapped_column( + Boolean, + nullable=False, + default=False, + server_default="false" + ) \ No newline at end of file diff --git a/hw4_5/src/routers/__init__.py b/hw4_5/src/routers/__init__.py new file mode 100644 index 00000000..674c96c7 --- /dev/null +++ b/hw4_5/src/routers/__init__.py @@ -0,0 +1,2 @@ +from .cart import router as cart_router +from .item import router as item_router \ No newline at end of file diff --git a/hw4_5/src/routers/cart.py b/hw4_5/src/routers/cart.py new file mode 100644 index 00000000..bf6ca1fa --- /dev/null +++ b/hw4_5/src/routers/cart.py @@ -0,0 +1,101 @@ +from fastapi import APIRouter, status, Response, Query, Depends +from fastapi.responses import JSONResponse +from typing import List, Optional +from uuid import UUID +from sqlalchemy.ext.asyncio import AsyncSession + +from src.crud import crud_cart +from src.db import get_db +from src.schemas import ( + CartResponse, + CartCreateResponse, + Msg +) + + +router = APIRouter() + + +@router.post( + path="", + response_model=CartCreateResponse, + status_code=status.HTTP_201_CREATED +) +async def create_cart( + response: Response, + db: AsyncSession = Depends(get_db) +): + new_cart = await crud_cart.create(db) + cart_id = new_cart.id + response.headers["Location"] = f"/carts/{cart_id}" + return CartCreateResponse(id=cart_id) + + +@router.get( + path="/{id}", + response_model=CartResponse +) +async def get_cart( + id: UUID, + db: AsyncSession = Depends(get_db) +): + cart = await crud_cart.get_cart_with_items(db=db, id=id) + if cart is None: + return JSONResponse( + content=Msg(msg="Корзина не найдена").model_dump(), + status_code=404 + ) + return cart + + +@router.get( + path="", + response_model=List[CartResponse] +) +async def get_list_carts( + offset: Optional[int] = Query(None, ge=0), + limit: Optional[int] = Query(None, ge=1), + min_price: Optional[float] = Query(None, ge=0.0), + max_price: Optional[float] = Query(None, ge=0.0), + min_quantity: Optional[int] = Query(None, ge=0), + max_quantity: Optional[int] = Query(None, ge=0), + db: AsyncSession = Depends(get_db) +): + carts = await crud_cart.get_carts_with_filters( + db=db, + offset=offset, + limit=limit, + min_price=min_price, + max_price=max_price, + min_quantity=min_quantity, + max_quantity=max_quantity, + ) + + return carts + + +@router.post( + path="/{cart_id}/add/{item_id}", + response_model=Msg +) +async def add_item_to_cart_endpoint( + cart_id: UUID, + item_id: UUID, + db: AsyncSession = Depends(get_db) +): + cart = await crud_cart.add_item_to_cart( + db=db, + cart_id=cart_id, + item_id=item_id + ) + + if not cart: + return JSONResponse( + content=Msg(msg="Ничего не найдено").model_dump(), + status_code=404 + ) + + return JSONResponse( + content=Msg(msg=f"Айтем {item_id} успешно добавлен в корзину {cart_id}").model_dump(), + status_code=200 + ) \ No newline at end of file diff --git a/hw4_5/src/routers/item.py b/hw4_5/src/routers/item.py new file mode 100644 index 00000000..de6f0ce2 --- /dev/null +++ b/hw4_5/src/routers/item.py @@ -0,0 +1,147 @@ +from uuid import UUID +from fastapi import APIRouter, status, Query, Depends +from fastapi.responses import JSONResponse +from typing import List, Optional +from sqlalchemy.ext.asyncio import AsyncSession + +from src.crud import crud_item +from src.db import get_db +from src.schemas import ( + ItemCreate, + ItemResponse, + Msg, ItemUpdate, ItemPatch +) + +router = APIRouter() + + +@router.post( + path="", + response_model=ItemResponse, + status_code=status.HTTP_201_CREATED +) +async def create_item_endpoint( + item: ItemCreate, + db: AsyncSession = Depends(get_db) +): + new_item = await crud_item.create(db=db, obj_in=item) + return new_item + + +@router.get( + path="/{id}", + response_model=ItemResponse +) +async def get_item_endpoint( + id: UUID, + db: AsyncSession = Depends(get_db) +): + item = await crud_item.get(db=db, id=id) + if not item: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + return item + + +@router.get( + path="", + response_model=List[ItemResponse] +) +async def get_list_items_endpoint( + offset: Optional[int] = Query(None, ge=0), + limit: Optional[int] = Query(None, ge=1), + min_price: Optional[float] = Query(None, ge=0.0), + max_price: Optional[float] = Query(None, ge=0.0), + show_deleted: Optional[bool] = Query(False), + db: AsyncSession = Depends(get_db) +): + items = await crud_item.get_items_with_filters( + db=db, + offset=offset, + limit=limit, + min_price=min_price, + max_price=max_price, + show_deleted=show_deleted + ) + + return items + + +@router.put( + path="/{id}", + response_model=ItemResponse +) +async def update_full_item_endpoint( + id: UUID, + item: ItemUpdate, + db: AsyncSession = Depends(get_db) +): + + item_updated = await crud_item.update( + db=db, + id=id, + obj_in=item + ) + if not item_updated: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + + return item_updated + + +@router.patch( + path="/{id}", + response_model=ItemResponse +) +async def update_item_partial_endpoint( + id: UUID, + item: ItemPatch, + db: AsyncSession = Depends(get_db) +): + + item_patched = await crud_item.update( + db=db, + id=id, + obj_in=item + ) + + if not item_patched: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + + return item_patched + + +@router.delete( + path="/{id}", + response_model=ItemResponse +) +async def delete_item_endpoint( + id: UUID, + db: AsyncSession = Depends(get_db) +): + item_deleted = await crud_item.soft_delete( + db=db, + id=id + ) + + if not item_deleted: + return JSONResponse( + content=Msg( + msg="Ничего не найдено" + ).model_dump(), + status_code=404 + ) + return item_deleted \ No newline at end of file diff --git a/hw4_5/src/schemas.py b/hw4_5/src/schemas.py new file mode 100644 index 00000000..678ca06a --- /dev/null +++ b/hw4_5/src/schemas.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel, ConfigDict +from typing import List, Optional +from uuid import UUID + + +class CartCreate(BaseModel): + pass + +class CartCreateResponse(BaseModel): + id: UUID + +class CartItemResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: UUID + name: str + quantity: float + available: bool + + +class CartResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: UUID + items: List[CartItemResponse] = [] + price: float + + +class ItemCreate(BaseModel): + model_config = ConfigDict(extra="forbid") + name: str + price: float + + +class ItemUpdate(ItemCreate): + pass + +class ItemPatch(ItemCreate): + name: Optional[str] = None + price: Optional[float] = None + +class ItemResponse(ItemCreate): + model_config = ConfigDict(from_attributes=True) + + id: UUID + deleted: bool + + +class Msg(BaseModel): + msg: str \ No newline at end of file diff --git a/hw4_5/start_pg_app.py b/hw4_5/start_pg_app.py new file mode 100644 index 00000000..cacea25d --- /dev/null +++ b/hw4_5/start_pg_app.py @@ -0,0 +1,13 @@ +import uvicorn + +from src.config import settings + + + +if __name__ == "__main__": + uvicorn.run( + app="src.main:pg_app", + host=settings.HOST, + port=settings.PORT, + reload=False + ) \ No newline at end of file diff --git a/hw4_5/transactions_problems_scripts/transactions_problems_scripts.py b/hw4_5/transactions_problems_scripts/transactions_problems_scripts.py new file mode 100644 index 00000000..a76f1d1f --- /dev/null +++ b/hw4_5/transactions_problems_scripts/transactions_problems_scripts.py @@ -0,0 +1,198 @@ +import asyncio +from uuid import UUID +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker +from sqlalchemy import text + +from config import settings +from models import Base, ItemModel + + + +engine = create_async_engine(settings.DATABASE_URL, echo=False) +AsyncSession = async_sessionmaker(engine, expire_on_commit=False) + +ITEM_ID = UUID("edf925f2-c112-423a-ac24-a70c6faebffc") +NEW_ITEM_ID_1 = UUID("81e5a9ac-6362-47be-bc33-0d740cac83ca") +NEW_ITEM_ID_2 = UUID("dc90ee64-6010-4d75-9062-7fa1fe387014") + + +async def setup_test_data(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + async with AsyncSession() as session: + session.add(ItemModel(id=ITEM_ID, name="Тестовый товар", price=100.0, deleted=False)) + await session.commit() + + +async def demo_1_dirty_read(): + print("\n=== 1. Dirty Read при READ UNCOMMITTED ===") + print("В PostgreSQL уровень READ UNCOMMITTED автоматически повышается до READ COMMITTED") + print("→ Грязное чтение НЕВОЗМОЖНО.") + + async with AsyncSession() as s1: + async with s1.begin(): + await s1.execute(text("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")) + await s1.execute( + text("UPDATE items SET price = 50 WHERE id = :id"), + {"id": str(ITEM_ID)} + ) + print("T1: обновила цену на 50, но не коммитит") + + async with AsyncSession() as s2: + async with s2.begin(): + await s2.execute(text("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")) + price = (await s2.execute( + text("SELECT price FROM items WHERE id = :id"), + {"id": str(ITEM_ID)} + )).scalar() + print(f"T2: прочитала цену = {price} (ожидаемо: 100.0)") + + await s1.rollback() + + +async def demo_2_no_dirty_read(): + print("\n=== 2. Отсутствие Dirty Read при READ COMMITTED ===") + async with AsyncSession() as s1: + async with s1.begin(): + await s1.execute(text("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")) + await s1.execute( + text("UPDATE items SET price = 50 WHERE id = :id"), + {"id": str(ITEM_ID)} + ) + print("T1: обновила цену на 50, но не коммитит") + + async with AsyncSession() as s2: + async with s2.begin(): + await s2.execute(text("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")) + price = (await s2.execute( + text("SELECT price FROM items WHERE id = :id"), + {"id": str(ITEM_ID)} + )).scalar() + print(f"T2: прочитала цену = {price}") # → 100.0 + + await s1.commit() + + +async def demo_3_non_repeatable_read(): + print("\n=== 3. Non-Repeatable Read при READ COMMITTED ===") + async with AsyncSession() as s2: + async with s2.begin(): + await s2.execute(text("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")) + p1 = (await s2.execute(text("SELECT price FROM items WHERE id = :id"), {"id": str(ITEM_ID)})).scalar() + print(f"T2: первое чтение = {p1}") + + async with AsyncSession() as s1: + async with s1.begin(): + await s1.execute(text("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")) + await s1.execute(text("UPDATE items SET price = 200 WHERE id = :id"), {"id": str(ITEM_ID)}) + await s1.commit() + print("T1: обновила и закоммитила") + + p2 = (await s2.execute(text("SELECT price FROM items WHERE id = :id"), {"id": str(ITEM_ID)})).scalar() + print(f"T2: второе чтение = {p2}") + + +async def demo_4_no_non_repeatable_read(): + print("\n=== 4. Отсутствие Non-Repeatable Read при REPEATABLE READ ===") + async with AsyncSession() as s2: + async with s2.begin(): + await s2.execute(text("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")) + p1 = (await s2.execute(text("SELECT price FROM items WHERE id = :id"), {"id": str(ITEM_ID)})).scalar() + print(f"T2: первое чтение = {p1}") # → 100.0 + + async with AsyncSession() as s1: + async with s1.begin(): + await s1.execute(text("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")) + await s1.execute(text("UPDATE items SET price = 200 WHERE id = :id"), {"id": str(ITEM_ID)}) + await s1.commit() + print("T1: обновила и закоммитила") + + p2 = (await s2.execute(text("SELECT price FROM items WHERE id = :id"), {"id": str(ITEM_ID)})).scalar() + print(f"T2: второе чтение = {p2}") # → 100.0 + + +async def demo_5_phantom_read(): + print("\n=== 5. Phantom Read при READ COMMITTED ===") + print("Условие: SELECT с WHERE price < 150") + + async with AsyncSession() as setup_sess: + async with setup_sess.begin(): + await setup_sess.execute(text("DELETE FROM items")) + await setup_sess.execute(text(""" + INSERT INTO items (id, name, price, deleted) + VALUES ('11111111-1111-1111-1111-111111111111', 'Товар A', 100.0, false) + """)) + + async with AsyncSession() as s2: + async with s2.begin(): + await s2.execute(text("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")) + + c1 = (await s2.execute( + text("SELECT COUNT(*) FROM items WHERE price < 150") + )).scalar() + print(f"T2: первое количество = {c1}") # → 1 + + async with AsyncSession() as s1: + async with s1.begin(): + await s1.execute(text("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")) + await s1.execute(text(""" + INSERT INTO items (id, name, price, deleted) + VALUES (:id, 'Новый товар', 120.0, false) + """), {"id": str(UUID("33333333-3333-3333-3333-333333333333"))}) + await s1.commit() + print("T1: добавила товар с price=120 (в диапазоне)") + + c2 = (await s2.execute( + text("SELECT COUNT(*) FROM items WHERE price < 150") + )).scalar() + print(f"T2: второе количество = {c2}") # → 2 + + if c2 > c1: + print("Phantom Read обнаружен: появилась новая строка в диапазоне!") + + +async def demo_6_no_phantom_read(): + print("\n=== 6. Отсутствие Phantom Read при SERIALIZABLE ===") + async with AsyncSession() as s2: + async with s2.begin(): + await s2.execute(text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")) + c1 = (await s2.execute(text("SELECT COUNT(*) FROM items WHERE deleted = false"))).scalar() + print(f"T2: первое количество = {c1}") # → 2 + + async with AsyncSession() as s1: + async with s1.begin(): + await s1.execute(text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")) + await s1.execute(text(""" + INSERT INTO items (id, name, price, deleted) + VALUES (:id, 'Ещё товар', 88.88, false) + """), {"id": str(NEW_ITEM_ID_2)}) + await s1.commit() + print("T1: добавила товар") + + try: + c2 = (await s2.execute(text("SELECT COUNT(*) FROM items WHERE deleted = false"))).scalar() + print(f"T2: второе количество = {c2}") # → 2 или ошибка + except Exception as e: + print(f"T2: ошибка сериализации (ожидаемо): {type(e).__name__}") + + +async def main(): + print("Демонстрация уровней изоляции транзакций в PostgreSQL") + print("Таблицы: items, carts, cart_items") + + await setup_test_data() + + await demo_1_dirty_read() + await demo_2_no_dirty_read() + await demo_3_non_repeatable_read() + await demo_4_no_non_repeatable_read() + await demo_5_phantom_read() + await demo_6_no_phantom_read() + + print("\nВсе демонстрации завершены!") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file From 4ed591baaf95b320cc95269317ebc80dfdc3d64b Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sun, 26 Oct 2025 17:32:46 +0700 Subject: [PATCH 6/8] add hw5 --- .github/workflows/hw5_tests.yml | 51 ++++++++ hw4_5/.coveragerc | 12 ++ hw4_5/pytest.ini | 3 + hw4_5/tests/conftest.py | 73 ++++++++++++ hw4_5/tests/test_cart.py | 198 ++++++++++++++++++++++++++++++++ hw4_5/tests/test_db.py | 10 ++ hw4_5/tests/test_item.py | 157 +++++++++++++++++++++++++ 7 files changed, 504 insertions(+) create mode 100644 .github/workflows/hw5_tests.yml create mode 100644 hw4_5/.coveragerc create mode 100644 hw4_5/pytest.ini create mode 100644 hw4_5/tests/conftest.py create mode 100644 hw4_5/tests/test_cart.py create mode 100644 hw4_5/tests/test_db.py create mode 100644 hw4_5/tests/test_item.py diff --git a/.github/workflows/hw5_tests.yml b/.github/workflows/hw5_tests.yml new file mode 100644 index 00000000..57a9f7a9 --- /dev/null +++ b/.github/workflows/hw5_tests.yml @@ -0,0 +1,51 @@ +name: "HW5 Tests" + +on: + pull_request: + branches: [ main ] + paths: [ 'hw4_5/**' ] + push: + branches: [ main ] + paths: [ 'hw4_5/**' ] + +jobs: + tests-hw5: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15 + env: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + + env: + DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/postgres + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: hw4_5/app/requirements.txt + + - name: Install deps + run: pip install -r hw4_5/requirements.txt + + - name: Run tests (hw5) + working-directory: hw4_5 + env: + PYTHONPATH: ${{ github.workspace }}/hw4_5 + run: pytest -q --cov=src --cov-report=term-missing + + - name: DB alembic migrations + working-directory: hw4_5 + env: + PYTHONPATH: ${{ github.workspace }}/hw4_5 + run: PYTHONPATH=. alembic upgrade head \ No newline at end of file diff --git a/hw4_5/.coveragerc b/hw4_5/.coveragerc new file mode 100644 index 00000000..198cfc9b --- /dev/null +++ b/hw4_5/.coveragerc @@ -0,0 +1,12 @@ +[run] +source = src +branch = True +concurrency = gevent +context = test + +[report] +exclude_lines = + pragma: no cover + def __repr__ + raise AssertionError + raise NotImplementedError \ No newline at end of file diff --git a/hw4_5/pytest.ini b/hw4_5/pytest.ini new file mode 100644 index 00000000..af5970c0 --- /dev/null +++ b/hw4_5/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +asyncio_mode = auto +testpaths = tests \ No newline at end of file diff --git a/hw4_5/tests/conftest.py b/hw4_5/tests/conftest.py new file mode 100644 index 00000000..1e0e62f3 --- /dev/null +++ b/hw4_5/tests/conftest.py @@ -0,0 +1,73 @@ +import asyncio +import pytest +import pytest_asyncio +import httpx +from httpx._transports.asgi import ASGITransport +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession +from sqlalchemy.pool import NullPool +from sqlalchemy import text + +from src.config import settings +from src.models import Base + + + +test_engine = create_async_engine( + settings.DATABASE_URL, + poolclass=NullPool, + echo=False, +) + +TestingSessionLocal = async_sessionmaker( + bind=test_engine, + class_=AsyncSession, + expire_on_commit=False, +) + + +@pytest.fixture(scope="session", autouse=True) +def init_test_db(): + asyncio.run(_setup_db()) + yield + asyncio.run(_teardown_db()) + + +async def _setup_db(): + async with test_engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + +async def _teardown_db(): + async with test_engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + +@pytest.fixture(autouse=True) +async def clean_db(): + async with test_engine.begin() as conn: + for table in reversed(Base.metadata.sorted_tables): + await conn.execute(text(f"TRUNCATE {table.name} RESTART IDENTITY CASCADE")) + + +@pytest_asyncio.fixture +async def client(): + from src import main + from src.db.deps import get_db + + async def override_get_db(): + async with TestingSessionLocal() as session: + print(f"→ OPEN session {id(session)}") + try: + yield session + finally: + print(f"← CLOSE session {id(session)}") + + pg_app = main.pg_app + pg_app.dependency_overrides[get_db] = override_get_db + + transport = ASGITransport(app=pg_app) + async with httpx.AsyncClient(transport=transport, base_url="http://test") as ac: + yield ac + + pg_app.dependency_overrides.clear() \ No newline at end of file diff --git a/hw4_5/tests/test_cart.py b/hw4_5/tests/test_cart.py new file mode 100644 index 00000000..46a2a147 --- /dev/null +++ b/hw4_5/tests/test_cart.py @@ -0,0 +1,198 @@ +import httpx +from http import HTTPStatus +from uuid import UUID +import pytest + + +class TestCartAPI: + + @pytest.mark.asyncio + async def test_create_cart(self, client: httpx.AsyncClient): + response = await client.post("/carts") + assert response.status_code == HTTPStatus.CREATED + data = response.json() + assert "id" in data + UUID(data["id"]) + assert response.headers["Location"] == f"/carts/{data['id']}" + + @pytest.mark.asyncio + async def test_get_cart_not_found(self, client: httpx.AsyncClient): + fake_id = "12345678-1234-5678-1234-567812345678" + response = await client.get(f"/carts/{fake_id}") + assert response.status_code == HTTPStatus.NOT_FOUND + assert response.json()["msg"] == "Корзина не найдена" + + @pytest.mark.asyncio + async def test_get_empty_cart(self, client: httpx.AsyncClient): + cart = (await client.post("/carts")).json() + cart_id = cart["id"] + + response = await client.get(f"/carts/{cart_id}") + assert response.status_code == HTTPStatus.OK + cart_data = response.json() + assert cart_data["items"] == [] + assert cart_data["price"] == 0.0 + + @pytest.mark.asyncio + async def test_get_cart_with_items(self, client: httpx.AsyncClient): + item_resp = await client.post("/items", json={"name": "Phone", "price": 500.0}) + item = item_resp.json() + item_id = item["id"] + + cart_resp = await client.post("/carts") + cart_id = cart_resp.json()["id"] + + await client.post(f"/carts/{cart_id}/add/{item_id}") + + response = await client.get(f"/carts/{cart_id}") + assert response.status_code == HTTPStatus.OK + cart = response.json() + assert cart["id"] == cart_id + assert cart["price"] == 500.0 + assert len(cart["items"]) == 1 + + cart_item = cart["items"][0] + assert cart_item["id"] == item_id + assert cart_item["name"] == "Phone" + assert cart_item["quantity"] == 1.0 + assert cart_item["available"] is True + + @pytest.mark.asyncio + async def test_add_item_twice_increases_quantity(self, client: httpx.AsyncClient): + item = (await client.post("/items", json={"name": "Phone", "price": 500.0})).json() + item_id = item["id"] + + cart = (await client.post("/carts")).json() + cart_id = cart["id"] + + await client.post(f"/carts/{cart_id}/add/{item_id}") + await client.post(f"/carts/{cart_id}/add/{item_id}") + + response = await client.get(f"/carts/{cart_id}") + cart_data = response.json() + assert len(cart_data["items"]) == 1 + assert cart_data["items"][0]["quantity"] == 2.0 + assert cart_data["price"] == 1000.0 + + @pytest.mark.asyncio + async def test_list_carts_with_min_price(self, client: httpx.AsyncClient): + cheap = (await client.post("/items", json={"name": "Cheap", "price": 10.0})).json() + expensive = (await client.post("/items", json={"name": "Expensive", "price": 200.0})).json() + + cart1 = (await client.post("/carts")).json() + await client.post(f"/carts/{cart1['id']}/add/{cheap['id']}") + + cart2 = (await client.post("/carts")).json() + await client.post(f"/carts/{cart2['id']}/add/{expensive['id']}") + + response = await client.get("/carts", params={"min_price": 100.0}) + carts = response.json() + assert len(carts) == 1 + assert carts[0]["id"] == cart2["id"] + assert carts[0]["price"] == 200.0 + + @pytest.mark.asyncio + async def test_list_carts_with_max_price(self, client: httpx.AsyncClient): + cheap = (await client.post("/items", json={"name": "Cheap", "price": 10.0})).json() + expensive = (await client.post("/items", json={"name": "Expensive", "price": 200.0})).json() + + cart1 = (await client.post("/carts")).json() + await client.post(f"/carts/{cart1['id']}/add/{cheap['id']}") + + cart2 = (await client.post("/carts")).json() + await client.post(f"/carts/{cart2['id']}/add/{expensive['id']}") + + response = await client.get("/carts", params={"max_price": 100.0}) + carts = response.json() + assert len(carts) == 1 + assert carts[0]["id"] == cart1["id"] + assert carts[0]["price"] == 10.0 + + response = await client.get("/carts", params={"max_price": 5.0}) + carts = response.json() + assert len(carts) == 0 + + @pytest.mark.asyncio + async def test_list_carts_with_max_quantity(self, client: httpx.AsyncClient): + item = (await client.post("/items", json={"name": "Test", "price": 10.0})).json() + + cart1 = (await client.post("/carts")).json() + await client.post(f"/carts/{cart1['id']}/add/{item['id']}") + + cart2 = (await client.post("/carts")).json() + await client.post(f"/carts/{cart2['id']}/add/{item['id']}") + await client.post(f"/carts/{cart2['id']}/add/{item['id']}") + + resp = await client.get("/carts", params={"max_quantity": 1}) + carts = resp.json() + assert len(carts) == 1 + assert carts[0]["id"] == cart1["id"] + + @pytest.mark.asyncio + async def test_list_carts_min_quantity_zero(self, client: httpx.AsyncClient): + empty_cart = (await client.post("/carts")).json() + + item = (await client.post("/items", json={"name": "Test", "price": 5.0})).json() + filled_cart = (await client.post("/carts")).json() + await client.post(f"/carts/{filled_cart['id']}/add/{item['id']}") + + resp = await client.get("/carts", params={"min_quantity": 0}) + carts = resp.json() + assert len(carts) == 2 + cart_ids = {c["id"] for c in carts} + assert empty_cart["id"] in cart_ids + assert filled_cart["id"] in cart_ids + + @pytest.mark.asyncio + async def test_add_item_to_cart_cart_not_found(self, client: httpx.AsyncClient): + fake_cart_id = "12345678-1234-5678-1234-567812345678" + item = (await client.post("/items", json={"name": "Test", "price": 1.0})).json() + response = await client.post(f"/carts/{fake_cart_id}/add/{item['id']}") + assert response.status_code == HTTPStatus.NOT_FOUND + assert response.json()["msg"] == "Ничего не найдено" + + @pytest.mark.asyncio + async def test_add_item_to_cart_item_not_found(self, client: httpx.AsyncClient): + cart = (await client.post("/carts")).json() + fake_item_id = "87654321-4321-8765-4321-876543210987" + response = await client.post(f"/carts/{cart['id']}/add/{fake_item_id}") + assert response.status_code == HTTPStatus.NOT_FOUND + assert response.json()["msg"] == "Ничего не найдено" + + @pytest.mark.asyncio + async def test_add_deleted_item_to_cart(self, client: httpx.AsyncClient): + item = (await client.post("/items", json={"name": "DeletedItem", "price": 99.0})).json() + await client.delete(f"/items/{item['id']}") + + cart = (await client.post("/carts")).json() + response = await client.post(f"/carts/{cart['id']}/add/{item['id']}") + assert response.status_code == HTTPStatus.NOT_FOUND + assert response.json()["msg"] == "Ничего не найдено" + + @pytest.mark.asyncio + async def test_get_cart_with_deleted_item(self, client: httpx.AsyncClient): + item = (await client.post("/items", json={"name": "ToBeDeleted", "price": 100.0})).json() + item_id = item["id"] + + cart = (await client.post("/carts")).json() + cart_id = cart["id"] + await client.post(f"/carts/{cart_id}/add/{item_id}") + + await client.delete(f"/items/{item_id}") + + response = await client.get(f"/carts/{cart_id}") + assert response.status_code == HTTPStatus.OK + cart_data = response.json() + assert len(cart_data["items"]) == 0 + + @pytest.mark.asyncio + async def test_list_carts_max_quantity_zero(self, client: httpx.AsyncClient): + empty_cart = (await client.post("/carts")).json() + item = (await client.post("/items", json={"name": "Test", "price": 10.0})).json() + filled_cart = (await client.post("/carts")).json() + await client.post(f"/carts/{filled_cart['id']}/add/{item['id']}") + + resp = await client.get("/carts", params={"max_quantity": 0, "offset": 0, "limit": 10}) + carts = resp.json() + assert len(carts) == 1 + assert carts[0]["id"] == empty_cart["id"] \ No newline at end of file diff --git a/hw4_5/tests/test_db.py b/hw4_5/tests/test_db.py new file mode 100644 index 00000000..307e3ab6 --- /dev/null +++ b/hw4_5/tests/test_db.py @@ -0,0 +1,10 @@ +import pytest +from src.db.deps import get_db + + + +@pytest.mark.asyncio +async def test_get_db(): + async for session in get_db(): + assert session is not None + break \ No newline at end of file diff --git a/hw4_5/tests/test_item.py b/hw4_5/tests/test_item.py new file mode 100644 index 00000000..1fa996b5 --- /dev/null +++ b/hw4_5/tests/test_item.py @@ -0,0 +1,157 @@ +import httpx +from http import HTTPStatus +from uuid import UUID, uuid4 +import pytest + + +class TestItemAPI: + + @pytest.mark.asyncio + async def test_create_item(self, client: httpx.AsyncClient): + response = await client.post("/items", json={"name": "Laptop", "price": 999.99}) + assert response.status_code == HTTPStatus.CREATED + data = response.json() + UUID(data["id"]) + + response = await client.get(f"/items/{data["id"]}") + assert response.status_code == HTTPStatus.OK + response = response.json() + assert response["name"] == "Laptop" + assert response["price"] == 999.99 + assert response["deleted"] is False + + @pytest.mark.asyncio + async def test_get_item_not_found(self, client: httpx.AsyncClient): + fake_id = "12345678-1234-5678-1234-567812345678" + response = await client.get(f"/items/{fake_id}") + assert response.status_code == HTTPStatus.NOT_FOUND + assert response.json()["msg"] == "Ничего не найдено" + + + @pytest.mark.asyncio + async def test_list_items_with_min_price(self, client: httpx.AsyncClient): + await client.post("/items", json={"name": "Cheap", "price": 5.0}) + await client.post("/items", json={"name": "Expensive", "price": 150.0}) + + resp = await client.get("/items", params={"min_price": 100.0}) + items = resp.json() + assert len(items) == 1 + assert all(i["price"] >= 100.0 for i in items) + + @pytest.mark.asyncio + async def test_list_items_with_max_price(self, client: httpx.AsyncClient): + await client.post("/items", json={"name": "Cheap", "price": 10.0}) + await client.post("/items", json={"name": "Expensive", "price": 200.0}) + + resp = await client.get("/items", params={"max_price": 50.0}) + items = resp.json() + assert len(items) == 1 + assert items[0]["name"] == "Cheap" + + @pytest.mark.asyncio + async def test_update_item_full(self, client: httpx.AsyncClient): + id = uuid4() + resp = (await client.put(f"/items/{id}", json={"name": "New", "price": 200.0})) + assert resp.json()["msg"] == "Ничего не найдено" + + item = (await client.post("/items", json={"name": "Old", "price": 100.0})).json() + item_id = item["id"] + updated = (await client.put(f"/items/{item_id}", json={"name": "New", "price": 200.0})).json() + assert updated["name"] == "New" + assert updated["price"] == 200.0 + assert updated["deleted"] is False + + @pytest.mark.asyncio + async def test_update_item_partial(self, client: httpx.AsyncClient): + + id = uuid4() + resp = (await client.patch(f"/items/{id}", json={"price": 150.0})) + assert resp.json()["msg"] == "Ничего не найдено" + + item = (await client.post("/items", json={"name": "Original", "price": 100.0})).json() + item_id = item["id"] + patched = (await client.patch(f"/items/{item_id}", json={"price": 150.0})).json() + assert patched["name"] == "Original" + assert patched["price"] == 150.0 + assert patched["deleted"] is False + + @pytest.mark.asyncio + async def test_soft_delete_item(self, client: httpx.AsyncClient): + item = (await client.post("/items", json={"name": "ToBeDeleted", "price": 10.0})).json() + item_id = item["id"] + assert item["deleted"] is False + + del_resp = await client.delete(f"/items/{item_id}") + assert del_resp.status_code == HTTPStatus.OK + deleted_item = del_resp.json() + assert deleted_item["id"] == item_id + assert deleted_item["deleted"] is True + + list_resp = await client.get("/items") + items = list_resp.json() + assert all(i["id"] != item_id for i in items) + + list_with_deleted = await client.get("/items", params={"show_deleted": True}) + items = list_with_deleted.json() + deleted_item = next((i for i in items if i["id"] == item_id), None) + assert deleted_item is not None + assert deleted_item["deleted"] is True + + @pytest.mark.asyncio + async def test_get_items_pagination(self, client: httpx.AsyncClient): + for i in range(5): + await client.post("/items", json={"name": f"Item{i}", "price": float(i + 10)}) + + resp = await client.get("/items", params={"offset": 2, "limit": 2}) + items = resp.json() + assert len(items) == 2 + assert items[0]["name"] == "Item2" + assert items[1]["name"] == "Item3" + + @pytest.mark.asyncio + async def test_get_items_show_deleted_with_price_filter(self, client: httpx.AsyncClient): + item1 = (await client.post("/items", json={"name": "Active", "price": 50.0})).json() + item2 = (await client.post("/items", json={"name": "DeletedCheap", "price": 10.0})).json() + item3 = (await client.post("/items", json={"name": "DeletedExpensive", "price": 200.0})).json() + + await client.delete(f"/items/{item2['id']}") + await client.delete(f"/items/{item3['id']}") + + resp = await client.get("/items", params={"show_deleted": True, "min_price": 100.0}) + items = resp.json() + assert len(items) == 1 + assert items[0]["id"] == item3["id"] + assert items[0]["deleted"] is True + + @pytest.mark.asyncio + async def test_get_items_offset_without_limit(self, client: httpx.AsyncClient): + for i in range(3): + await client.post("/items", json={"name": f"Item{i}", "price": 10.0}) + + resp = await client.get("/items", params={"offset": 1}) + items = resp.json() + assert len(items) == 2 + assert items[0][("na" + "me")] == "Item1" + + @pytest.mark.asyncio + async def test_get_items_show_deleted_with_price_filter(self, client: httpx.AsyncClient): + item1 = (await client.post("/items", json={"name": "Active", "price": 50.0})).json() + item2 = (await client.post("/items", json={"name": "DeletedCheap", "price": 10.0})).json() + item3 = (await client.post("/items", json={"name": "DeletedExpensive", "price": 200.0})).json() + + await client.delete(f"/items/{item2['id']}") + await client.delete(f"/items/{item3['id']}") + + resp = await client.get("/items", params={"show_deleted": True, "min_price": 100.0}) + items = resp.json() + assert len(items) == 1 + assert items[0]["id"] == item3["id"] + assert items[0]["deleted"] is True + + @pytest.mark.asyncio + async def test_delete_unknown_item(self, client: httpx.AsyncClient): + + id = uuid4() + resp = await client.delete(f"/items/{id}") + assert resp.json()["msg"] == "Ничего не найдено" \ No newline at end of file From db20d21046d65aeeac262aa34c685dd80e0b865d Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sun, 26 Oct 2025 17:36:19 +0700 Subject: [PATCH 7/8] fix hw5_tests.yml --- .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 57a9f7a9..74a4979a 100644 --- a/.github/workflows/hw5_tests.yml +++ b/.github/workflows/hw5_tests.yml @@ -33,7 +33,7 @@ jobs: with: python-version: "3.12" cache: pip - cache-dependency-path: hw4_5/app/requirements.txt + cache-dependency-path: hw4_5/requirements.txt - name: Install deps run: pip install -r hw4_5/requirements.txt From c27298b77f412361fe360f323577b20c818cb4c3 Mon Sep 17 00:00:00 2001 From: glukhov324 Date: Sun, 26 Oct 2025 17:39:03 +0700 Subject: [PATCH 8/8] change api name --- hw4_5/src/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw4_5/src/main.py b/hw4_5/src/main.py index 87bbdd48..9ee41150 100644 --- a/hw4_5/src/main.py +++ b/hw4_5/src/main.py @@ -4,7 +4,7 @@ -pg_app = FastAPI() +pg_app = FastAPI(title="Shop API") pg_app.include_router( router=cart_router,