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
2 changes: 1 addition & 1 deletion hw1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### 1. Установите зависимости
```bash
pip install -r requirements.txt
pip3 install --break-system-packages -r requirements.txt
```

### 2. Реализуйте ASGI приложение
Expand Down
128 changes: 126 additions & 2 deletions hw1/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
from typing import Any, Awaitable, Callable
import math
import json
from urllib.parse import parse_qs
from http import HTTPStatus


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


async def send_response(
send: Callable[[dict[str, Any]], Awaitable[None]],
status: int,
body: bytes = b"",
content_type: bytes = b"text/plain",
):
await send(
{
"type": "http.response.start",
"status": status,
"headers": [[b"content-type", content_type]],
}
)
await send({"type": "http.response.body", "body": body})


async def send_json(
send: Callable[[dict[str, Any]], Awaitable[None]],
data: dict[str, Any],
status: int = HTTPStatus.OK,
):
body = json.dumps(data).encode("utf-8")
await send_response(send, status, body, b"application/json")


async def application(
Expand All @@ -12,8 +50,94 @@ 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"})
return
elif scope["type"] == "http":
method = scope["method"]
path = scope["path"]

if path == "/factorial":
if method != "GET":
await send_response(send, HTTPStatus.METHOD_NOT_ALLOWED, b"Method Not Allowed")
return
query_string = scope["query_string"].decode()
query = parse_qs(query_string)
n_str = query.get("n", [None])[0]
if n_str is None:
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Missing parameter 'n'")
return
try:
n = int(n_str)
except ValueError:
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Invalid parameter 'n'")
return
if n < 0:
await send_response(send, HTTPStatus.BAD_REQUEST, b"Parameter 'n' must be non-negative")
return
result = math.factorial(n)
await send_json(send, {"result": result})

elif path.startswith("/fibonacci/"):
if method != "GET":
await send_response(send, HTTPStatus.METHOD_NOT_ALLOWED, b"Method Not Allowed")
return
n_str = path[len("/fibonacci/"):]
if not n_str:
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Missing number in path")
return
try:
n = int(n_str)
except ValueError:
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Invalid number in path")
return
if n < 0:
await send_response(send, HTTPStatus.BAD_REQUEST, b"Number must be non-negative")
return
result = compute_fibonacci(n)
await send_json(send, {"result": result})

elif path == "/mean":
if method != "GET":
await send_response(send, HTTPStatus.METHOD_NOT_ALLOWED, b"Method Not Allowed")
return
message = await receive()
if message["type"] != "http.request":
await send_response(send, HTTPStatus.INTERNAL_SERVER_ERROR, b"Invalid request")
return
body = message.get("body", b"")
if not body:
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Empty body")
return
try:
data = json.loads(body)
if not isinstance(data, list):
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Body must be a list")
return
if not data:
await send_response(send, HTTPStatus.BAD_REQUEST, b"Empty list")
return
numbers = []
for item in data:
if not isinstance(item, (int, float)):
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"All elements must be numbers")
return
numbers.append(float(item))
mean_value = sum(numbers) / len(numbers)
await send_json(send, {"result": mean_value})
except json.JSONDecodeError:
await send_response(send, HTTPStatus.UNPROCESSABLE_ENTITY, b"Invalid JSON")
return
else:
await send_response(send, HTTPStatus.NOT_FOUND, b"Not Found")
else:
return

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)
10 changes: 10 additions & 0 deletions hw2/hw/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY shop_api/ ./shop_api/

CMD ["uvicorn", "shop_api.main:app", "--host", "0.0.0.0", "--port", "8000"]
64 changes: 64 additions & 0 deletions hw2/hw/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
version: '3.8'

services:
app:
build: .
restart: unless-stopped
ports:
- "8000:8000"
networks:
- monitoring
- db_network
depends_on:
- db
- prometheus
environment:
- DATABASE_URL=postgresql://shop:shop@db:5432/shop

db:
image: postgres:15
restart: unless-stopped
environment:
POSTGRES_USER: shop
POSTGRES_PASSWORD: shop
POSTGRES_DB: shop
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- db_network
ports:
- "5432:5432"

prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
networks:
- monitoring

grafana:
image: grafana/grafana:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
networks:
- monitoring

networks:
monitoring:
db_network:

volumes:
grafana_data:
postgres_data: {}
Binary file added hw2/hw/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions hw2/hw/prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
global:
scrape_interval: 15s

scrape_configs:
- job_name: 'shop-api'
static_configs:
- targets: ['app:8000']
metrics_path: '/metrics'
9 changes: 7 additions & 2 deletions hw2/hw/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Основные зависимости для ASGI приложения
fastapi>=0.117.1
uvicorn>=0.24.0
fastapi==0.104.1
uvicorn[standard]==0.24.0
prometheus-fastapi-instrumentator==6.1.0
pydantic==2.5.3
pydantic-core==2.14.6
sqlalchemy==2.0.23
psycopg2-binary==2.9.9

# Зависимости для тестирования
pytest>=7.4.0
Expand Down
5 changes: 5 additions & 0 deletions hw2/hw/run_scripts.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
docker-compose up --build -d

sleep 20

python3 scripts/isolation_demo.py
30 changes: 30 additions & 0 deletions hw2/hw/scripts/dirty_read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
import time

engine = create_engine("postgresql://shop:shop@localhost:5432/shop")
Session = sessionmaker(bind=engine)

s1 = Session()
s1.connection(execution_options={"isolation_level": "READ UNCOMMITTED"})
s2 = Session()
s2.connection(execution_options={"isolation_level": "READ UNCOMMITTED"})

s1.execute(text("DELETE FROM items WHERE name LIKE 'dirty%'"))
s1.commit()

print("DIRTY READ (READ UNCOMMITTED):")
s1.execute(text("BEGIN"))
s1.execute(text("INSERT INTO items (name, price) VALUES ('dirty_item', 999)"))
print("S1: inserted dirty_item")

print("S2: sees uncommitted data?")
res = s2.execute(text("SELECT name FROM items WHERE name = 'dirty_item'")).fetchone()
print("S2 sees:", res)

s1.rollback()
print("S1 rolled back")
res = s2.execute(text("SELECT name FROM items WHERE name = 'dirty_item'")).fetchone()
print("S2 after rollback sees:", res)

s1.close(); s2.close()
Loading
Loading