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
48 changes: 48 additions & 0 deletions .github/workflows/test-postgresql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Tests with PostgreSQL

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

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: shop_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install uv
run: |
pip install uv

- name: Install dependencies
run: |
cd hw2/hw
uv sync

- name: Run all tests with SQLite
run: |
cd hw2/hw
uv run -m pytest -v
115 changes: 114 additions & 1 deletion hw1/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
import json
from math import factorial
from typing import Any, Awaitable, Callable


def fibonacci(n: int):
if n < 0:
raise ValueError(f"Expected parameter n must be non-negative. Got n={n}")

if n == 0:
return 0

first_value, second_value = 0, 1

for i in range(n - 1):
first_value, second_value = second_value, first_value + second_value

return second_value


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


async def application(
scope: dict[str, Any],
receive: Callable[[], Awaitable[dict[str, Any]]],
Expand All @@ -12,8 +47,86 @@ 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

elif scope["type"] == "http":
path = scope["path"]

if path == "/" or path == "/not_found":
await send_response(404, b"text/plain", b"not found", send)

elif path == "/factorial":
try:
n = int(scope["query_string"].decode().replace("n=", ""))
if n < 0:
await send_response(
400,
b"text/plain",
b"Invalid value for n, must be non-negative",
send,
)
else:
result = factorial(n)
response_body = bytes(
json.dumps({"result": result}), encoding="utf-8"
)
await send_response(200, b"application/json", response_body, send)
except ValueError:
await send_response(422, b"text/plain", b"unprocessible entity", send)

elif path.startswith("/fibonacci/"):
try:
n = int(path.split("/")[2])
if n < 0:
await send_response(
400,
b"text/plain",
b"Invalid value for n, must be non-negative",
send,
)
else:
result = fibonacci(n)
response_body = bytes(
json.dumps({"result": result}), encoding="utf-8"
)
await send_response(200, b"application/json", response_body, send)
except ValueError:
await send_response(422, b"text/plain", b"unprocessible entity", send)

elif path == "/mean":
body = b""
event = await receive()
if event["type"] == "http.request":
body = event.get("body", b"")
inp_arr = json.loads(body.decode())
if inp_arr is None:
await send_response(422, b"text/plain", b"unprocessible entity", send)
else:
if len(inp_arr) == 0:
await send_response(
400,
b"text/plain",
b"Invalid value for body, must be non-empty array of floats",
send,
)
else:
result = sum(inp_arr) / len(inp_arr)
response_body = bytes(
json.dumps({"result": result}), encoding="utf-8"
)
await send_response(200, b"application/json", response_body, send)
else:
await send_response(422, b"text/plain", b"unprocessible entity", send)


if __name__ == "__main__":
import uvicorn

uvicorn.run("app:application", host="0.0.0.0", port=8000, reload=True)
48 changes: 48 additions & 0 deletions hw2/hw/.github/workflows/test-postgresql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Tests with PostgreSQL

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

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: shop_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'

- name: Install uv
run: |
pip intall uv

- name: Install dependencies
run: |
cd hw2/hw
uv sync

- name: Run all tests with SQLite
run: |
cd hw2/hw
uv run -m pytest -v
24 changes: 24 additions & 0 deletions hw2/hw/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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
RUN python -m pip install uv

WORKDIR $APP_ROOT/src
COPY . ./

ENV VIRTUAL_ENV=$APP_ROOT/src/.venv \
PATH=$APP_ROOT/src/.venv/bin:$PATH

RUN uv sync

FROM base as local

CMD ["uv", "run", "uvicorn", "shop_api.main:app", "--port", "8080", "--host", "0.0.0.0"]
Empty file removed hw2/hw/__init__.py
Empty file.
49 changes: 49 additions & 0 deletions hw2/hw/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
services:
prometheus:
image: prom/prometheus
volumes:
- ./settings/prometheus/:/etc/prometheus/
command:
- "--config.file=/etc/prometheus/prometheus.yaml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
ports:
- 9090:9090
restart: always

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

local:
build:
context: .
dockerfile: ./Dockerfile
target: local
restart: always
environment:
- DATABASE_URL=postgresql://shop:shop@postgres:5432/shopdb
ports:
- 8080:8080
depends_on:
- prometheus
- grafana
- postgres

postgres:
image: postgres:15
environment:
POSTGRES_USER: shop
POSTGRES_PASSWORD: shop
POSTGRES_DB: shopdb
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- 5432:5432
restart: always

volumes:
postgres_data:
Binary file added hw2/hw/prometheus.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions hw2/hw/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[project]
name = "hw"
version = "0.1.0"
description = "Homework 2 - Shop API"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"faker>=37.8.0",
"fastapi>=0.117.1",
"httpx>=0.27.2",
"prometheus-fastapi-instrumentator==7.1.0",
"psycopg2-binary>=2.9",
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.1.0",
"sqlalchemy[asyncio]>=2.0",
"uvicorn>=0.24.0",
"asyncpg>=0.30.0",
"aiosqlite>=0.21.0",
]

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "-v --cov=shop_api --cov-report=term-missing"

[tool.coverage.run]
source = ["shop_api"]
omit = ["tests/*", "**/__init__.py", "shop_api/main.py"]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if __name__ == .__main__.:",
"raise NotImplementedError",
"pass",
]
9 changes: 0 additions & 9 deletions hw2/hw/requirements.txt

This file was deleted.

Loading
Loading