From ec94a44618e68ed8a8ec11b394b4ffd7f1c1b1f8 Mon Sep 17 00:00:00 2001 From: Said Kamalov Date: Fri, 26 Sep 2025 17:12:17 +0300 Subject: [PATCH 1/3] semi working draft --- hw1/app.py | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/hw1/app.py b/hw1/app.py index 6107b870..1172b94a 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -1,4 +1,109 @@ from typing import Any, Awaitable, Callable +from http import HTTPStatus +import json + + +# Math backend +def factorial(n: int) -> int: + result = 1 + for i in range(1, n + 1): + result *= i + return result + + +def fibonacci(n: int) -> int: + if n == 0: + return 0 + if n == 1: + return 1 + + a, b = 0, 1 + for _ in range(2, n + 1): + a, b = b, a + b + return b + + +def mean(numbers: list[float]) -> float: + return sum(numbers) / len(numbers) + + +### + + +# Endpoints +def factorial_endpoint(n_raw) -> int | HTTPStatus: + try: + n = int(n_raw[2:]) + if n < 0: + return HTTPStatus.BAD_REQUEST + except ValueError: + return HTTPStatus.UNPROCESSABLE_CONTENT + return factorial(n) + + +def fibonacci_endpoint(n_raw) -> int | HTTPStatus: + try: + n = int(n_raw) + if n < 0: + return HTTPStatus.BAD_REQUEST + except ValueError: + return HTTPStatus.UNPROCESSABLE_CONTENT + return fibonacci(n) + + +def mean_endpoint(numbers_raw) -> float | HTTPStatus: + try: + numbers = [float(number) for number in numbers_raw[8:].split(",")] + except ValueError: + return HTTPStatus.UNPROCESSABLE_CONTENT + return mean(numbers) + + +ENDPOINTS = { + "factorial": factorial_endpoint, + "fibonacci": fibonacci_endpoint, + "mean": mean_endpoint, +} +### + + +def parse_query(path: str, query_string: str) -> tuple[str, str]: + parts = path.split("/") + if len(parts) == 3: + # return endpoint and params + return (parts[1], parts[2]) + else: + # return endpoint and cgi params + return (parts[1], query_string) + + +def get_endpoint(scope: dict[str, Any]) -> Callable[[...], Any]: + path = scope["path"] + query_string = scope["query_string"].decode() + endpoint, params = parse_query(path, query_string) + try: + func = ENDPOINTS[endpoint] + except KeyError: + return -1, HTTPStatus.NOT_FOUND + + result = func(params) + if isinstance(result, HTTPStatus): + return -1, result + else: + return result, HTTPStatus.OK + + +async def read_request_body(receive: Callable[[], Awaitable[dict[str, Any]]]) -> bytes: + body = b"" + while True: + message = await receive() + if message["type"] != "http.request": + # Ignore non-http.request messages for robustness + continue + body += message.get("body", b"") + if not message.get("more_body"): + break + return body async def application( @@ -14,6 +119,64 @@ async def application( """ # TODO: Ваша реализация здесь + # Handle lifespan events + if scope["type"] == "lifespan": + while True: + message = await receive() + msg_type = message.get("type") + if msg_type == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + elif msg_type == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) + return + return + + # Only HTTP requests below + if scope["type"] != "http": + # Not supported scope + await send({"type": "http.response.start", "status": int(HTTPStatus.NOT_FOUND)}) + await send({"type": "http.response.body", "body": b""}) + return + + # Handle only correct methods + method = scope["method"] + if method != "GET": + await send( + { + "type": "http.response.start", + "status": int(HTTPStatus.NOT_FOUND), + "headers": [], + } + ) + await send( + { + "type": "http.response.body", + "body": b"", + "headers": [(b"content-type", b"application/json")], + } + ) + return + + result, status = get_endpoint(scope) + await send( + { + "type": "http.response.start", + "status": int(status), + "headers": [], + } + ) + if status != HTTPStatus.OK: + result = {} + await send( + { + "type": "http.response.body", + "body": json.dumps({"result": result}).encode(), + "headers": [(b"content-type", b"application/json")], + } + ) + + if __name__ == "__main__": import uvicorn + uvicorn.run("app:application", host="0.0.0.0", port=8000, reload=True) From 5f742e7caab6bf8c9a238495f6d8ba03ac3add71 Mon Sep 17 00:00:00 2001 From: Said Kamalov Date: Fri, 26 Sep 2025 17:34:30 +0300 Subject: [PATCH 2/3] all endpoints done --- hw1/app.py | 76 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/hw1/app.py b/hw1/app.py index 1172b94a..dc795cb1 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -1,6 +1,7 @@ from typing import Any, Awaitable, Callable from http import HTTPStatus import json +from urllib.parse import urlparse # Math backend @@ -31,9 +32,9 @@ def mean(numbers: list[float]) -> float: # Endpoints -def factorial_endpoint(n_raw) -> int | HTTPStatus: +def factorial_endpoint(params: str, data: Any) -> int | HTTPStatus: try: - n = int(n_raw[2:]) + n = int(params[2:]) if n < 0: return HTTPStatus.BAD_REQUEST except ValueError: @@ -41,9 +42,9 @@ def factorial_endpoint(n_raw) -> int | HTTPStatus: return factorial(n) -def fibonacci_endpoint(n_raw) -> int | HTTPStatus: +def fibonacci_endpoint(params: str, data: Any) -> int | HTTPStatus: try: - n = int(n_raw) + n = int(params) if n < 0: return HTTPStatus.BAD_REQUEST except ValueError: @@ -51,11 +52,27 @@ def fibonacci_endpoint(n_raw) -> int | HTTPStatus: return fibonacci(n) -def mean_endpoint(numbers_raw) -> float | HTTPStatus: +def mean_endpoint(params: str, data: bytes) -> float | HTTPStatus: + # Expect JSON array in the request body + if not data: + return HTTPStatus.UNPROCESSABLE_ENTITY try: - numbers = [float(number) for number in numbers_raw[8:].split(",")] - except ValueError: - return HTTPStatus.UNPROCESSABLE_CONTENT + payload = json.loads(data.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError): + return HTTPStatus.UNPROCESSABLE_ENTITY + + if payload is None: + return HTTPStatus.UNPROCESSABLE_ENTITY + if not isinstance(payload, list): + return HTTPStatus.UNPROCESSABLE_ENTITY + if len(payload) == 0: + return HTTPStatus.BAD_REQUEST + + try: + numbers = [float(value) for value in payload] + except (TypeError, ValueError): + return HTTPStatus.BAD_REQUEST + return mean(numbers) @@ -67,26 +84,42 @@ def mean_endpoint(numbers_raw) -> float | HTTPStatus: ### -def parse_query(path: str, query_string: str) -> tuple[str, str]: - parts = path.split("/") - if len(parts) == 3: - # return endpoint and params - return (parts[1], parts[2]) - else: - # return endpoint and cgi params - return (parts[1], query_string) +def get_endpoint_name(scope: dict[str, Any]) -> str: + """Return endpoint name (first path segment) using urlparse.""" + path = scope["path"] + parsed = urlparse(path) + clean_path = parsed.path.strip("/") + if not clean_path: + return "" + first, *_ = clean_path.split("/", 1) + return first -def get_endpoint(scope: dict[str, Any]) -> Callable[[...], Any]: +def get_param_from_url(scope: dict[str, Any]) -> str: + """Return the parameter part: path segment if present, else the raw query string. + + This preserves existing behavior where factorial expects a raw + "n=..." style string and fibonacci expects the second path segment. + """ path = scope["path"] - query_string = scope["query_string"].decode() - endpoint, params = parse_query(path, query_string) + parsed = urlparse(path) + clean_path = parsed.path.strip("/") + parts = clean_path.split("/") if clean_path else [] + if len(parts) >= 2 and parts[1]: + return parts[1] + # fallback to decoded query string (without '?') + return scope.get("query_string", b"").decode() + + +def execute_endpoint(scope: dict[str, Any], data: Any) -> Callable[[...], Any]: + endpoint = get_endpoint_name(scope) + params = get_param_from_url(scope) try: func = ENDPOINTS[endpoint] except KeyError: return -1, HTTPStatus.NOT_FOUND - result = func(params) + result = func(params, data) if isinstance(result, HTTPStatus): return -1, result else: @@ -157,7 +190,8 @@ async def application( ) return - result, status = get_endpoint(scope) + data = await read_request_body(receive) + result, status = execute_endpoint(scope, data) await send( { "type": "http.response.start", From b5f1497cdd1fb3f2639782dfc1facf61b386188a Mon Sep 17 00:00:00 2001 From: Said Kamalov Date: Fri, 26 Sep 2025 17:46:55 +0300 Subject: [PATCH 3/3] fix status codes --- hw1/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw1/app.py b/hw1/app.py index dc795cb1..9318bc4d 100644 --- a/hw1/app.py +++ b/hw1/app.py @@ -38,7 +38,7 @@ def factorial_endpoint(params: str, data: Any) -> int | HTTPStatus: if n < 0: return HTTPStatus.BAD_REQUEST except ValueError: - return HTTPStatus.UNPROCESSABLE_CONTENT + return HTTPStatus.UNPROCESSABLE_ENTITY return factorial(n) @@ -48,7 +48,7 @@ def fibonacci_endpoint(params: str, data: Any) -> int | HTTPStatus: if n < 0: return HTTPStatus.BAD_REQUEST except ValueError: - return HTTPStatus.UNPROCESSABLE_CONTENT + return HTTPStatus.UNPROCESSABLE_ENTITY return fibonacci(n)