Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 87 additions & 1 deletion hw1/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,93 @@ async def application(
receive: Корутина для получения сообщений от клиента
send: Корутина для отправки сообщений клиенту
"""
# TODO: Ваша реализация здесь
if scope["type"] == "lifespan":
while True:
message = await receive()
await send({"type": f"{message['type']}.complete"})

path = scope["path"].rstrip("/")
status = 404
result = None

if scope["method"] == "GET":
if path == "/factorial":
kvs = scope["query_string"].decode()
n = None
for kv in kvs.split("&"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше использовать urllib.parse.parse_qs

if "=" in kv:
k, v = kv.split("=", 1)
if k == "n":
n = v
status, result = compute_number(n, factorial)
elif path.startswith("/fibonacci/"):
status, result = compute_number(path.removeprefix("/fibonacci/"), fibonacci)
elif path == "/mean":
raw_bytes = await read_bytes(receive)
status, result = mean(raw_bytes)

await answer(send, status, result)


def compute_number(raw: str | None, func: Callable[[int], int]) -> tuple[int, int | None]:
try:
n = int(raw)
except (TypeError, ValueError):
return 422, None
if n < 0:
return 400, None
return 200, func(n)


def factorial(n: int) -> int:
ans = 1
for i in range(n):
ans *= (i + 1)
return ans


def fibonacci(n: int) -> int:
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a


def mean(body: bytes) -> tuple[int, float | None]:
try:
text = body.decode().strip()
numbers = [float(item) for item in text[1:-1].split(",") if item.strip()]
except ValueError:
return 422, None
if not numbers:
return 400, None
res = sum(numbers) / len(numbers)
return 200, res


async def read_bytes(receive: Callable[[], Awaitable[dict[str, Any]]]) -> bytes:
body = b""
has_more = True
while has_more:
message = await receive()
body += message.get("body", b"")
has_more = message.get("more_body", False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно дополнительно disconnect обрабатывать, но некритично здесь

return body


async def answer(send, status, result):
await send(
{
"type": "http.response.start",
"status": status,
"headers": [[b"content-type", b"application/json"]],
}
)
body = b""
if status == 200:
body = f'{{"result": {result}}}'.encode()
await send({"type": "http.response.body", "body": body})


if __name__ == "__main__":
import uvicorn
Expand Down
40 changes: 40 additions & 0 deletions hw2/grpc_example/ping_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions hw2/grpc_example/ping_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Optional as _Optional

DESCRIPTOR: _descriptor.FileDescriptor

class PingRequest(_message.Message):
__slots__ = ("message",)
MESSAGE_FIELD_NUMBER: _ClassVar[int]
message: str
def __init__(self, message: _Optional[str] = ...) -> None: ...

class PongResponse(_message.Message):
__slots__ = ("message",)
MESSAGE_FIELD_NUMBER: _ClassVar[int]
message: str
def __init__(self, message: _Optional[str] = ...) -> None: ...
140 changes: 140 additions & 0 deletions hw2/grpc_example/ping_pb2_grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings

import ping_pb2 as ping__pb2

GRPC_GENERATED_VERSION = '1.84.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False

try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True

if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in ping_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)


class ExampleStub:
"""Missing associated documentation comment in .proto file."""

def __init__(self, channel):
"""Constructor.

Args:
channel: A grpc.Channel.
"""
self.Ping = channel.unary_unary(
'/example.Example/Ping',
request_serializer=ping__pb2.PingRequest.SerializeToString,
response_deserializer=ping__pb2.PongResponse.FromString,
_registered_method=True)
self.PingStream = channel.stream_stream(
'/example.Example/PingStream',
request_serializer=ping__pb2.PingRequest.SerializeToString,
response_deserializer=ping__pb2.PongResponse.FromString,
_registered_method=True)


class ExampleServicer:
"""Missing associated documentation comment in .proto file."""

def Ping(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def PingStream(self, request_iterator, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_ExampleServicer_to_server(servicer, server):
rpc_method_handlers = {
'Ping': grpc.unary_unary_rpc_method_handler(
servicer.Ping,
request_deserializer=ping__pb2.PingRequest.FromString,
response_serializer=ping__pb2.PongResponse.SerializeToString,
),
'PingStream': grpc.stream_stream_rpc_method_handler(
servicer.PingStream,
request_deserializer=ping__pb2.PingRequest.FromString,
response_serializer=ping__pb2.PongResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'example.Example', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('example.Example', rpc_method_handlers)


# This class is part of an EXPERIMENTAL API.
class Example:
"""Missing associated documentation comment in .proto file."""

@staticmethod
def Ping(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/example.Example/Ping',
ping__pb2.PingRequest.SerializeToString,
ping__pb2.PongResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)

@staticmethod
def PingStream(request_iterator,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.stream_stream(
request_iterator,
target,
'/example.Example/PingStream',
ping__pb2.PingRequest.SerializeToString,
ping__pb2.PongResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
Empty file added hw2/hw/shop_api/api/__init__.py
Empty file.
Empty file.
35 changes: 35 additions & 0 deletions hw2/hw/shop_api/api/carts/contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pydantic import BaseModel
from ...store import queries
from ...store.models import Cart


class CartItemResponse(BaseModel):
id: int
name: str
quantity: int
available: bool


class CartResponse(BaseModel):
id: int
items: list[CartItemResponse]
price: float

@staticmethod
def from_entity(cart: Cart):
items = []
for item_id, quantity in cart.item_id_to_count.items():
item = queries.get_item(item_id)
items.append(
CartItemResponse(
id=item.id,
name=item.name,
quantity=quantity,
available=not item.deleted,
)
)
return CartResponse(
id=cart.id,
items=items,
price=queries.cart_price(cart),
)
41 changes: 41 additions & 0 deletions hw2/hw/shop_api/api/carts/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from http import HTTPStatus
from typing import Annotated
from fastapi import APIRouter, Query, Response
from pydantic import NonNegativeFloat, NonNegativeInt, PositiveInt
from .contracts import CartResponse
from ...store import queries


router = APIRouter(prefix="/cart")


@router.post("", status_code=HTTPStatus.CREATED)
async def post_cart(response: Response) -> dict[str, int]:
cart = queries.add_cart()
response.headers["location"] = f"/cart/{cart.id}"
return {"id": cart.id}


@router.get("/{id}")
async def get_cart(id: int) -> CartResponse:
cart = queries.get_cart(id)
return CartResponse.from_entity(cart)


@router.get("")
async def get_carts(
offset: Annotated[NonNegativeInt, Query()] = 0,
limit: Annotated[PositiveInt, Query()] = 10,
min_price: Annotated[NonNegativeFloat | None, Query()] = None,
max_price: Annotated[NonNegativeFloat | None, Query()] = None,
min_quantity: Annotated[NonNegativeInt | None, Query()] = None,
max_quantity: Annotated[NonNegativeInt | None, Query()] = None,
) -> list[CartResponse]:
carts = queries.get_carts(offset, limit, min_price, max_price, min_quantity, max_quantity)
return [CartResponse.from_entity(cart) for cart in carts]


@router.post("/{cart_id}/add/{item_id}")
async def add_item_to_cart(cart_id: int, item_id: int) -> CartResponse:
cart = queries.add_item_to_cart(cart_id, item_id)
return CartResponse.from_entity(cart)
Empty file.
Loading
Loading