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
55 changes: 55 additions & 0 deletions .github/workflows/hw4_5-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Run tests HW4 and HW5

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: hw2_db
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U user -d hw2_db"
--health-interval=5s
--health-timeout=5s
--health-retries=10

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r hw2/hw/requirements.txt

- name: Wait for PostgreSQL
run: |
until pg_isready -h localhost -p 5432 -U user; do
echo "Waiting for Postgres..."
sleep 2
done

- name: Run tests with coverage
working-directory: hw2/hw
env:
PYTHONPATH: .
DATABASE_URL: postgresql+psycopg2://user:password@localhost:5432/hw2_db
run: |
pytest --cov=shop_api --cov-report=term-missing --cov-fail-under=98
108 changes: 108 additions & 0 deletions hw1/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,84 @@
from typing import Any, Awaitable, Callable
import math
import json
from urllib.parse import parse_qs

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

def mean(nums):
return sum(nums) / len(nums)

async def send_response(send, status, data):
body = json.dumps(data).encode()
await send({
"type": "http.response.start",
"status": status,
"headers": [(b"content-type", b"application/json")],
})
await send({"type": "http.response.body", "body": body})

async def factorial_endpoint(send, query_string):
q = parse_qs(query_string)
nums = q.get("n")
if not nums:
await send_response(send, 422, {})
return

try:
n = int(nums[0])
except ValueError:
await send_response(send, 422, {})
return

if n < 0:
await send_response(send, 400, {})
return

await send_response(send, 200, {"result": math.factorial(n)})

async def fibonacci_endpoint(send, n_str):
try:
n = int(n_str)
except ValueError:
await send_response(send, 422, {})
return

if n < 0:
await send_response(send, 400, {"error": "n must be >= 0"})
return
await send_response(send, 200, {"result": fibonacci(n)})

async def mean_endpoint(send, receive):
body = b""
while True:
message = await receive()
if message["type"] == "http.request":
body += message.get("body", b"")
if not message.get("more_body", False):
break
if not body:
await send_response(send, 422, {})
return
try:
data = json.loads(body.decode())
except (json.JSONDecodeError, UnicodeDecodeError):
await send_response(send, 422, {})
return
if not isinstance(data, list):
await send_response(send, 422, {})
return
if len(data) == 0:
await send_response(send, 400, {})
return
try:
await send_response(send, 200, {"result": mean(data)})
except (TypeError, ValueError):
await send_response(send, 422, {})
return

async def application(
scope: dict[str, Any],
Expand All @@ -13,6 +92,35 @@ async def application(
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']
query_string = scope.get('query_string', b'').decode()

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

if path == '/factorial':
await factorial_endpoint(send, query_string)
elif path.startswith('/fibonacci/'):
n_str = path[len('/fibonacci/'):]
await fibonacci_endpoint(send, n_str)
elif path == '/mean':
await mean_endpoint(send, receive)
else:
await send_response(send, 404, {"error": "Not found"})

if __name__ == "__main__":
import uvicorn
Expand Down
23 changes: 23 additions & 0 deletions hw2/hw/Dockerfile
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"]
47 changes: 47 additions & 0 deletions hw2/hw/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
services:
db:
image: postgres:16
restart: always
environment:
POSTGRES_DB: hw2_db
POSTGRES_USER: user
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d hw2_db"]
interval: 2s
timeout: 2s
retries: 30

local:
build:
context: .
dockerfile: ./Dockerfile
target: local
restart: always
# зависит от готовности БД, чтобы скрипты не ловили гонку
depends_on:
db:
condition: service_healthy
ports:
- "8080:8080"

grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
restart: always

prometheus:
image: prom/prometheus
volumes:
- ./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
Loading
Loading