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
182 changes: 180 additions & 2 deletions hw1/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
from typing import Any, Awaitable, Callable
import json


def fibonacci(n: int) -> int:
"""Вычисляет n-е число Фибоначчи"""
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 factorial(n: int) -> int:
"""Вычисляет факториал n"""
if n == 0:
return 1

result = 1
for i in range(1, n + 1):
result *= i
return result


def mean(numbers: list[float]) -> float:
"""Вычисляет среднее арифметическое"""
if not numbers:
return 0.0
return sum(numbers) / len(numbers)


async def application(
Expand All @@ -12,8 +44,154 @@ async def application(
receive: Корутина для получения сообщений от клиента
send: Корутина для отправки сообщений клиенту
"""
# TODO: Ваша реализация здесь
if scope['type'] == 'lifespan':
while True:
message = await receive()
if message['type'] == 'lifespan.startup':
await send({'type': 'lifespan.startup.complete'})
elif message['type'] == 'lifespan.shutdown':
await send({'type': 'lifespan.shutdown.complete'})
break
return

if scope['type'] != 'http':
return

method = scope['method']
path = scope['path']

if method != 'GET':
await send_response(send, 404, {'error': 'Not found'})
return

if path == '/mean':
await handle_mean(scope, receive, send)
elif path.startswith('/fibonacci/'):
await handle_fibonacci(path, send)
elif path == '/factorial':
await handle_factorial(scope, send)
else:
await send_response(send, 404, {'error': 'Not found'})


async def handle_fibonacci(path: str, send: Callable):
"""Обработчик для /fibonacci/{n}"""
try:
# Извлекаем n из пути
n_str = path.split('/fibonacci/')[-1].split('/')[0]
n = int(n_str)

if n < 0:
await send_response(send, 400, {'error': 'n must be non-negative'})
return

result = fibonacci(n)
await send_response(send, 200, {'result': result})

except (ValueError, IndexError):
await send_response(send, 422, {'error': 'Invalid parameter format'})
except Exception:
await send_response(send, 500, {'error': 'Internal server error'})


async def handle_mean(
scope: dict[str, Any], receive: Callable, send: Callable
):
"""Обработчик для /mean с JSON в теле запроса"""
# Получаем тело запроса
body = await get_request_body(receive)

if not body:
await send_response(send, 422, {'error': 'No data provided'})
return

try:
# Парсим JSON из тела запроса
data = json.loads(body)

if not isinstance(data, list):
await send_response(
send, 422, {'error': 'Expected list of numbers'}
)
return

if len(data) == 0:
await send_response(send, 400, {'error': 'Empty list'})
return

# Конвертируем все числа в float
numbers = [float(num) for num in data]
result = mean(numbers)

await send_response(send, 200, {'result': result})

except (ValueError, TypeError):
await send_response(send, 422, {'error': 'Invalid numbers format'})
except Exception:
await send_response(send, 500, {'error': 'Internal server error'})


async def handle_factorial(scope: dict[str, Any], send: Callable):
"""Обработчик для /factorial?n=5"""
query_string = scope.get('query_string', b'').decode()
params = parse_query_params(query_string)

if 'n' not in params:
await send_response(send, 422, {'error': 'Missing n parameter'})
return

try:
n = int(params['n'])
if n < 0:
await send_response(send, 400, {'error': 'n must be non-negative'})
return

result = factorial(n)
await send_response(send, 200, {'result': result})

except ValueError:
await send_response(send, 422, {'error': 'n must be an integer'})
except Exception:
await send_response(send, 500, {'error': 'Internal server error'})


async def get_request_body(receive: Callable) -> str:
"""Получает тело запроса"""
body = b''
more_body = True

while more_body:
message = await receive()
body += message.get('body', b'')
more_body = message.get('more_body', False)

return body.decode('utf-8')


def parse_query_params(query_string: str) -> dict:
"""Парсит query string в словарь параметров"""
params = {}
if query_string:
for param in query_string.split('&'):
if '=' in param:
key, value = param.split('=', 1)
params[key] = value
return params


async def send_response(send: Callable, status: int, data: dict):
"""Утилита для отправки JSON ответа"""
await send({
'type': 'http.response.start',
'status': status,
'headers': [[b'content-type', b'application/json']],
})
await send({
'type': 'http.response.body',
'body': json.dumps(data).encode('utf-8'),
})


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)
23 changes: 23 additions & 0 deletions hw2/hw/Dockerfile.txt
Original file line number Diff line number Diff line change
@@ -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_ROOT/src
COPY . ./

ENV VIRTUAL_ENV=$APP_ROOT/src/.venv \
PATH=$APP_ROOT/src/.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"]
36 changes: 36 additions & 0 deletions hw2/hw/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: "3"

services:

local:
build:
context: .
dockerfile: ./Dockerfile
target: local
restart: always
ports:
- 8080:8080

grafana:
image: grafana/grafana:latest
ports:
- 3000:3000
restart: always
volumes:
- grafana-storage:/var/lib/grafana

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

volumes:
grafana-storage: {}
2 changes: 2 additions & 0 deletions hw2/hw/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Основные зависимости для ASGI приложения
fastapi>=0.117.1
uvicorn>=0.24.0
prometheus-client
prometheus-fastapi-instrumentator

# Зависимости для тестирования
pytest>=7.4.0
Expand Down
10 changes: 10 additions & 0 deletions hw2/hw/settings/prometheus/prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
global:
scrape_interval: 10s
evaluation_interval: 10s

scrape_configs:
- job_name: shop-api-local
metrics_path: /metrics
static_configs:
- targets:
- local:8080
Loading