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
125 changes: 124 additions & 1 deletion hw1/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,109 @@
from typing import Any, Awaitable, Callable
from operations import mean_number, fibonacci, factorial

import asyncio
import ast
import json


async def handle_query(
send: Callable[[dict[str, Any]], Awaitable[None]],
body: bytes | None,
query_path: str | None,
query_params: bytes | None
):

if not query_path:
op_type = ''
else:
op_type = query_path.split('/')[1]

try:
if op_type not in {'mean', 'fibonacci', 'factorial'}:
raise ValueError("Unknown or wrong operation type")

elif op_type == 'mean':
if query_params:
raise ValueError("No query params supported for this operation")
if not body:
raise ValueError("Parameters should be in the body")
numbers = body.decode()
if numbers == 'null':
raise ValueError("Body should consist of the list only")
numbers = ast.literal_eval(numbers)
if not isinstance(numbers, list):
raise ValueError("Body should consist of the list only")
result = str(await asyncio.to_thread(mean_number, numbers))

elif op_type == 'fibonacci':
if query_params:
raise ValueError("No query params supported for this operation")
if body:
raise ValueError("Body should be empty for this operation")

n = query_path.split('/')[2]
if not n.lstrip('-').isnumeric():
raise ValueError("Parameter is not a number")
n = int(n)
result = str(await asyncio.to_thread(fibonacci, n))

elif op_type == 'factorial':
if not query_params:
raise ValueError("Arguments should be in query params")
if body:
raise ValueError("Body should be empty for this operation")

params = query_params.decode().split('=')
if len(params) != 2:
raise ValueError("Specify only one parameter")
if params[0] != 'n':
raise ValueError("Parameter name should be `n`")
if params[1] == '':
raise ValueError("Empty parameter value")
if not params[1].lstrip('-').isnumeric():
raise ValueError("Parameter is not a number")
n = int(params[1])
result = str(await asyncio.to_thread(factorial, n))

status_code = 200
body = json.dumps({"result": result})

except ValueError as e:
body = str(e)
if body == "Unknown or wrong operation type":
status_code = 404
elif body in {"No query params supported for this operation",
"Arguments should be in query params",
"Parameters should be in body",
"Body should be empty for this operations",
"Body should consist of the list only",
"Specify only one parameter",
"Parameter name should be `n`",
"Empty parameter value", "Parameter is not a number"}:
status_code = 422
else:
status_code = 400
except ZeroDivisionError:
body = "Empty list for mean calculation"
status_code = 400
except IndexError:
body = "No number for Fibonacci calculation"
status_code = 422
except TypeError:
body = "Operation can be performed only with numbers"
status_code = 400

finally:
await send(
{
"type": "http.response.start",
"status": status_code,
"headers": [
[b"content-type", b"text/plain"],
],
}
)
await send({"type": "http.response.body", "body": body.encode('utf-8')})


async def application(
Expand All @@ -12,7 +117,25 @@ async def application(
receive: Корутина для получения сообщений от клиента
send: Корутина для отправки сообщений клиенту
"""
# TODO: Ваша реализация здесь

if scope["type"] == 'lifespan':
while True:
body_dict = await receive()
if body_dict['type'] == 'lifespan.startup':
await send({'type': 'lifespan.startup.complete'})
elif body_dict['type'] == 'lifespan.shutdown':
await send({'type': 'lifespan.shutdown.complete'})
return

if scope["type"] == 'http':
body_dict = await receive()
body = body_dict['body']

query_path = scope['path']
query_params = scope['query_string']

await handle_query(send, body, query_path, query_params)


if __name__ == "__main__":
import uvicorn
Expand Down
53 changes: 53 additions & 0 deletions hw1/operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import math


def factorial(n: int) -> int:
try:
return math.factorial(n)
except ValueError:
raise


def mean_number(numbers: list[int]) -> int | float:
try:
return sum(numbers) / len(numbers)
except Exception:
raise


def pow(x, n, I, mult):
"""
Возвращает x в степени n. Предполагает, что I – это единичная матрица, которая
перемножается с mult, а n – положительное целое
"""
if n == 0:
return I
elif n == 1:
return x
else:
y = pow(x, n // 2, I, mult)
y = mult(y, y)
if n % 2:
y = mult(x, y)
return y


def identity_matrix(n):
"""Возвращает единичную матрицу n на n"""
r = list(range(n))
return [[1 if i == j else 0 for i in r] for j in r]


def matrix_multiply(A, B):
BT = list(zip(*B))
return [[sum(a * b
for a, b in zip(row_a, col_b))
for col_b in BT]
for row_a in A]


def fibonacci(n):
if n < 0:
raise ValueError('Number for Fibonacci should be >= 0')
F = pow([[1, 1], [1, 0]], n, identity_matrix(2), matrix_multiply)
return F[0][1]
9 changes: 9 additions & 0 deletions hw2/hw/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY shop_api/ ./shop_api/
EXPOSE 8088

CMD ["uvicorn", "shop_api.main:app", "--host", "0.0.0.0", "--port", "8088"]
10 changes: 10 additions & 0 deletions hw2/hw/README_hw3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Домашнее задание №3

Сделано на основе ДЗ2 (API магазина). Создан общий docker-compose.yaml и Dockerfile отдельно для приложения.

Реализован мониторинг следующих метрик с помощью Prometheus и Grafana: количество успешных запросов, количество неуспешных запросов, количество запросов на замену товара (PUT, PATCH), количество имеющихся в базе товаров (с учетом удаленных).

На основе файла с тестами написан скрипт **load_test.py**, который отправляет указанное количество случайных запросов к API на различные эндпоинты.

Пример дашборда:
![пример дашборда](./res/hw_3.png)
55 changes: 55 additions & 0 deletions hw2/hw/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
version: '3.8'

services:
shop-api:
build: .
ports:
- "8088:8088"
environment:
- PYTHONPATH=/app
networks:
- monitoring
restart: unless-stopped

prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
networks:
- monitoring
restart: unless-stopped

grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/dashboards:/var/lib/grafana/dashboards
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
networks:
- monitoring
restart: unless-stopped
depends_on:
- prometheus

volumes:
prometheus_data:
grafana_data:

networks:
monitoring:
driver: bridge
Loading