Initial commit

This commit is contained in:
2026-07-01 01:07:25 +02:00
commit b8c7eaddbf
33 changed files with 2876 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
### Python template
deploy/
.idea/
.vscode/
.git/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
+31
View File
@@ -0,0 +1,31 @@
root = true
[*]
tab_width = 4
end_of_line = lf
max_line_length = 88
ij_visual_guides = 88
insert_final_newline = true
trim_trailing_whitespace = true
[*.{js,py,html}]
charset = utf-8
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
[.flake8]
indent_style = space
indent_size = 2
[*.py]
indent_style = space
indent_size = 4
ij_python_from_import_parentheses_force_if_multiline = true
+41
View File
@@ -0,0 +1,41 @@
name: Testing zangramru
on: push
jobs:
lint:
strategy:
matrix:
cmd:
- ruff-format
- ruff
- mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Set up UV
uses: astral-sh/setup-uv@v6
- name: Install deps
run: uv sync
- name: Run lint check
run: uv run pre-commit run -a ${{ matrix.cmd }}
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create .env
run: touch .env
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Update docker-compose
uses: KengoTODA/actions-setup-docker-compose@v1
with:
version: "2.28.0"
- name: run tests
run: docker-compose run --rm api pytest -vv
+142
View File
@@ -0,0 +1,142 @@
### Python template
.idea/
.vscode/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
*.sqlite3
*.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
+31
View File
@@ -0,0 +1,31 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: local
hooks:
- id: ruff-format
name: Format with Ruff
entry: uv run ruff format
language: system
pass_filenames: false
always_run: true
- id: ruff
name: Check with Ruff
entry: uv run ruff
language: system
pass_filenames: false
always_run: true
args: ["check", "zangramru", "tests", "--fix"]
- id: mypy
name: Validate types with MyPy
entry: uv run mypy
language: system
types: [python]
pass_filenames: false
args:
- "zangramru"
- "tests"
+43
View File
@@ -0,0 +1,43 @@
FROM ghcr.io/astral-sh/uv:0.11.7-python3.13-trixie AS uv
# -----------------------------------
# STAGE 1: prod stage
# Only install main dependencies
# -----------------------------------
FROM python:3.13-slim-trixie AS prod
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV UV_PROJECT_ENVIRONMENT=/usr/local
ENV UV_PYTHON_DOWNLOADS=never
ENV UV_NO_MANAGED_PYTHON=1
ENV PICCOLO_CONF="zangramru.piccolo_conf"
WORKDIR /app/src
RUN --mount=from=uv,source=/usr/local/bin/uv,target=/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project --no-dev
COPY . .
RUN --mount=from=uv,source=/usr/local/bin/uv,target=/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev
CMD ["/usr/local/bin/python", "-m", "zangramru"]
# -----------------------------------
# STAGE 3: development build
# Includes dev dependencies
# -----------------------------------
FROM prod AS dev
RUN --mount=from=uv,source=/usr/local/bin/uv,target=/bin/uv \
--mount=type=cache,target=/root/.cache/uv \
uv sync --locked --all-groups
+145
View File
@@ -0,0 +1,145 @@
# zangramru
This project was generated using fastapi_template.
## UV
This project uses uv. It's a modern dependency management
tool.
To run the project use this set of commands:
```bash
uv sync --locked
uv run -m zangramru
```
This will start the server on the configured host.
You can find swagger documentation at `/api/docs`.
You can read more about uv here: https://docs.astral.sh/ruff/
## Docker
You can start the project with docker using this command:
```bash
docker compose up --build
```
This command exposes the web application on port 8000, mounts current directory and enables autoreload.
But you have to rebuild image every time you modify `uv.lock` or `pyproject.toml` with this command:
```bash
docker compose build
```
## Project structure
```bash
$ tree "zangramru"
zangramru
├── conftest.py # Fixtures for all tests.
├── db # module contains db configurations
│   ├── dao # Data Access Objects. Contains different classes to interact with database.
│   └── models # Package contains different models for ORMs.
├── __main__.py # Startup script. Starts uvicorn.
├── services # Package for different external services such as rabbit or redis etc.
├── settings.py # Main configuration settings for project.
├── static # Static content.
├── tests # Tests for project.
└── web # Package contains web server. Handlers, startup config.
├── api # Package with all handlers.
│   └── router.py # Main router.
├── application.py # FastAPI application configuration.
└── lifespan.py # Contains actions to perform on startup and shutdown.
```
## Configuration
This application can be configured with environment variables.
You can create `.env` file in the root directory and place all
environment variables here.
All environment variables should start with "ZANGRAMRU_" prefix.
For example if you see in your "zangramru/settings.py" a variable named like
`random_parameter`, you should provide the "ZANGRAMRU_RANDOM_PARAMETER"
variable to configure the value. This behaviour can be changed by overriding `env_prefix` property
in `zangramru.settings.Settings.Config`.
An example of .env file:
```bash
ZANGRAMRU_RELOAD="True"
ZANGRAMRU_PORT="8000"
ZANGRAMRU_ENVIRONMENT="dev"
```
You can read more about BaseSettings class here: https://pydantic-docs.helpmanual.io/usage/settings/
## Pre-commit
To install pre-commit simply run inside the shell:
```bash
pre-commit install
```
pre-commit is very useful to check your code before publishing it.
It's configured using .pre-commit-config.yaml file.
By default it runs:
* mypy (validates types);
* ruff (spots possible bugs);
You can read more about pre-commit here: https://pre-commit.com/
## Migrations
If you want to migrate your database, you should run following commands:
```bash
# You have to set a PICCOLO_CONF variable
export PICCOLO_CONF="zangramru.piccolo_conf"
# Now you can easily run migrations using
piccolo migrations forwards all
```
### Reverting migrations
If you want to revert migrations, you should run:
```bash
```
### Migration generation
To generate migrations you should run:
```bash
```
## Running tests
If you want to run it in docker, simply run:
```bash
docker compose run --build --rm api pytest -vv .
docker compose down
```
For running tests on your local machine.
1. you need to start all aux services.
We can do so by using our `docker-compose.yaml` configuration. It already has everything we need.
```bash
docker compose up -d --wait db
```
2. Run tests.
```bash
pytest -vv .
```
+61
View File
@@ -0,0 +1,61 @@
services:
api: &main_app
build:
context: .
target: dev
dockerfile: ./Dockerfile
image: zangramru:${ZANGRAMRU_VERSION:-latest}
restart: always
ports:
# Exposes application port.
- "8000:8000"
env_file:
- path: .env
required: false
depends_on:
db:
condition: service_healthy
migrator:
condition: service_completed_successfully
environment:
ZANGRAMRU_HOST: 0.0.0.0
ZANGRAMRU_RELOAD: "True"
ZANGRAMRU_DB_HOST: zangramru-db
ZANGRAMRU_DB_PORT: 5432
ZANGRAMRU_DB_USER: zangramru
ZANGRAMRU_DB_PASS: zangramru
ZANGRAMRU_DB_BASE: zangramru
volumes:
- .:/app/src/
db:
image: postgres:18.3-trixie
hostname: zangramru-db
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: "zangramru"
POSTGRES_USER: "zangramru"
POSTGRES_DB: "zangramru"
volumes:
- zangramru-db-data:/var/lib/postgresql
restart: always
healthcheck:
test: pg_isready -U zangramru
interval: 2s
timeout: 3s
retries: 40
migrator:
<<: *main_app
restart: "no"
command: piccolo migrations forwards all
depends_on:
db:
condition: service_healthy
volumes:
zangramru-db-data:
name: zangramru-db-data
+164
View File
@@ -0,0 +1,164 @@
[project]
name = "zangramru"
version = "0.1.0"
requires-python = ">=3.12,<4.0"
description = ""
authors = []
maintainers = []
readme = "README.md"
dependencies = [
"fastapi >=0.122.0,<1",
"uvicorn[standard] >=0.38.0,<1",
"pydantic >=2.12.5,<3",
"pydantic-settings >=2.12.0,<3",
"yarl >=1.22.0,<2",
"ujson >=5.11.0,<6",
"piccolo[postgres] >=1.30.0,<2",
"httptools >=0.7.1,<1",
"pymongo >=4.15.4,<5",
]
[dependency-groups]
dev = [
"pytest >=9.0.1,<10",
"ruff >=0.14.6,<1",
"mypy >=1.19.0,<2",
"pre-commit >=4.5.0,<5",
"pytest-cov >=7.0.0,<8",
"anyio >=4.11.0,<5",
"pytest-env >=1.2.0,<2",
"httpx >=0.28.1,<1",
]
[tool.uv]
default-groups = ["dev"]
[tool.isort]
profile = "black"
multi_line_output = 3
src_paths = ["zangramru",]
[tool.mypy]
python_version = "3.13"
strict = true
ignore_missing_imports = true
allow_subclassing_any = true
allow_untyped_calls = true
pretty = true
show_error_codes = true
implicit_reexport = true
allow_untyped_decorators = true
warn_unused_ignores = false
warn_return_any = false
namespace_packages = true
[tool.pytest.ini_options]
addopts = "-p no:unraisableexception"
filterwarnings = [
"error",
"ignore::DeprecationWarning",
"ignore:.*unclosed.*:ResourceWarning",
]
anyio_mode = "auto"
env = [
"ZANGRAMRU_ENVIRONMENT=pytest",
"ZANGRAMRU_DB_BASE=zangramru_test",
"PICCOLO_CONF=zangramru.piccolo_conf",
]
[tool.ruff]
target-version = "py313"
# List of enabled rulsets.
# See https://docs.astral.sh/ruff/rules/ for more information.
lint.select = [ "E", # Error
"F", # Pyflakes
"W", # Pycodestyle
"C90", # McCabe complexity
"I", # Isort
"N", # pep8-naming
"D", # Pydocstyle
"ANN", # Pytype annotations
"S", # Bandit
"B", # Bugbear
"C4", # Comprehensions
"ISC", # Implicit string concat
"PIE", # Unnecessary code
"T20", # Catch prints
"PYI", # validate pyi files
"Q", # Checks for quotes
"RSE", # Checks raise statements
"RET", # Checks return statements
"SLF", # Self checks
"SIM", # Simplificator
"PTH", # Pathlib checks
"ERA", # Checks for commented out code
"PL", # PyLint checks
"RUF", # Specific to Ruff checks
"FA102", # Future annotations
"UP", # Python updates
]
lint.ignore = [
"D105", # Missing docstring in magic method
"D107", # Missing docstring in __init__
"B008", # Do not perform function calls in argument defaults
"D211", # No blank lines allowed before class docstring
"D212", # Multi-line docstring summary should start at the first line
"D401", # First line should be in imperative mood
"D104", # Missing docstring in public package
"D100", # Missing docstring in public module
"D202", # No blank lines allowed after function docstring
"ANN401", # typing.Any are disallowed in `**kwargs
"PLR0913", # Too many arguments for function call
"D106", # Missing docstring in public nested class
"UP043", # Unnecessary default argument specified (conflicts with mypy).
]
exclude = [
"zangramru/db/migrations",
".venv/"
]
lint.mccabe = { max-complexity = 10 }
line-length = 88
[tool.ruff.lint.per-file-ignores]
"tests/*" = [
"S101", # Use of assert detected
"PLC0415", # Import outside toplevel
]
[tool.ruff.lint.pydocstyle]
convention = "pep257"
ignore-decorators = ["typing.overload"]
[tool.ruff.lint.pylint]
allow-magic-value-types = ["int", "str", "float", "bytes"]
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = [
"fastapi.Depends",
"fastapi.Query",
]
[fastapi-template.options]
project_name = "zangramru"
api_type = "rest"
enable_redis = "None"
enable_rmq = "None"
ci_type = "github"
enable_migrations = "True"
enable_taskiq = "None"
enable_routers = "True"
enable_kafka = "None"
enable_nats = "None"
enable_loguru = "None"
traefik_labels = "None"
add_dummy = "None"
orm = "piccolo"
self_hosted_swagger = "None"
prometheus_enabled = "None"
sentry_enabled = "None"
otlp_enabled = "None"
gunicorn = "None"
add_users = "None"
cookie_auth = "None"
jwt_auth = "None"
+1
View File
@@ -0,0 +1 @@
"""Tests for zangramru."""
+99
View File
@@ -0,0 +1,99 @@
from collections.abc import AsyncGenerator
from typing import Any
import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from piccolo.conf.apps import Finder
from piccolo.engine.postgres import PostgresEngine
from piccolo.table import create_tables, drop_tables
from zangramru.settings import settings
from zangramru.web.application import get_app
@pytest.fixture(scope="session")
def anyio_backend() -> str:
"""
Backend for anyio pytest plugin.
:return: backend name.
"""
return "asyncio"
async def drop_database(engine: PostgresEngine) -> None:
"""
Drops test database.
:param engine: engine connected to postgres database.
"""
await engine.run_ddl(
"SELECT pg_terminate_backend(pg_stat_activity.pid) " # noqa: S608
"FROM pg_stat_activity "
f"WHERE pg_stat_activity.datname = '{settings.db_base}' "
"AND pid <> pg_backend_pid();",
)
await engine.run_ddl(
f"DROP DATABASE {settings.db_base};",
)
@pytest.fixture(autouse=True)
async def setup_db() -> AsyncGenerator[None, None]:
"""
Fixture to create all tables before test and drop them after.
:yield: nothing.
"""
engine = PostgresEngine(
config={
"database": "postgres",
"user": settings.db_user,
"password": settings.db_pass,
"host": settings.db_host,
"port": settings.db_port,
},
)
await engine.start_connection_pool()
db_exists = await engine.run_ddl(
f"SELECT 1 FROM pg_database WHERE datname='{settings.db_base}'" # noqa: S608
)
if db_exists:
await drop_database(engine)
await engine.run_ddl(f"CREATE DATABASE {settings.db_base}")
tables = Finder().get_table_classes()
create_tables(*tables, if_not_exists=True)
yield
drop_tables(*tables)
await drop_database(engine)
@pytest.fixture
def fastapi_app() -> FastAPI:
"""
Fixture for creating FastAPI app.
:return: fastapi app with mocked dependencies.
"""
application = get_app()
return application # noqa: RET504
@pytest.fixture
async def client(
fastapi_app: FastAPI, anyio_backend: Any
) -> AsyncGenerator[AsyncClient, None]:
"""
Fixture that creates client for requesting server.
:param fastapi_app: the application.
:yield: client for the app.
"""
async with AsyncClient(
transport=ASGITransport(fastapi_app), base_url="http://test", timeout=2.0
) as ac:
yield ac
+19
View File
@@ -0,0 +1,19 @@
import uuid
from fastapi import FastAPI
from httpx import AsyncClient
from starlette import status
async def test_echo(fastapi_app: FastAPI, client: AsyncClient) -> None:
"""
Tests that echo route works.
:param fastapi_app: current application.
:param client: client for the app.
"""
url = fastapi_app.url_path_for("send_echo_message")
message = uuid.uuid4().hex
response = await client.post(url, json={"message": message})
assert response.status_code == status.HTTP_200_OK
assert response.json()["message"] == message
+15
View File
@@ -0,0 +1,15 @@
from fastapi import FastAPI
from httpx import AsyncClient
from starlette import status
async def test_health(client: AsyncClient, fastapi_app: FastAPI) -> None:
"""
Checks the health endpoint.
:param client: client for the app.
:param fastapi_app: current FastAPI application.
"""
url = fastapi_app.url_path_for("health_check")
response = await client.get(url)
assert response.status_code == status.HTTP_200_OK
Generated
+1642
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
"""zangramru package."""
+24
View File
@@ -0,0 +1,24 @@
import os
import uvicorn
from zangramru.settings import settings
def main() -> None:
"""Entrypoint of the application."""
os.environ["PICCOLO_CONF"] = "zangramru.piccolo_conf"
uvicorn.run(
"zangramru.web.application:get_app",
workers=settings.workers_count,
host=settings.host,
port=settings.port,
reload=settings.reload,
log_level=settings.log_level.value.lower(),
access_log=True,
factory=True,
)
if __name__ == "__main__":
main()
+14
View File
@@ -0,0 +1,14 @@
from pathlib import Path
from piccolo.conf.apps import AppConfig, table_finder
CURRENT_DIRECTORY = Path(__file__).parent
APP_CONFIG = AppConfig(
app_name="zangramru_db",
migrations_folder_path=str(CURRENT_DIRECTORY / "migrations"),
table_classes=table_finder(modules=[]),
migration_dependencies=[],
commands=[],
)
View File
+1
View File
@@ -0,0 +1 @@
"""zangramru models."""
+62
View File
@@ -0,0 +1,62 @@
import logging
import sys
from loguru import logger
from zangramru.settings import settings
class InterceptHandler(logging.Handler):
"""
Default handler from examples in loguru documentation.
This handler intercepts all log requests and
passes them to loguru.
For more info see:
https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
"""
def emit(self, record: logging.LogRecord) -> None: # pragma: no cover
"""
Propagates logs to loguru.
:param record: record to log.
"""
try:
level: str | int = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back # type: ignore
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
level,
record.getMessage(),
)
def configure_logging() -> None: # pragma: no cover
"""Configures logging."""
intercept_handler = InterceptHandler()
logging.basicConfig(handlers=[intercept_handler], level=logging.NOTSET)
for logger_name in logging.root.manager.loggerDict:
if logger_name.startswith("uvicorn."):
logging.getLogger(logger_name).handlers = []
# change handler for default uvicorn logger
logging.getLogger("uvicorn").handlers = [intercept_handler]
logging.getLogger("uvicorn.access").handlers = [intercept_handler]
# set logs output, level and format
logger.remove()
logger.add(
sys.stdout,
level=settings.log_level.value,
)
+17
View File
@@ -0,0 +1,17 @@
from piccolo.conf.apps import AppRegistry
from piccolo.engine.postgres import PostgresEngine
from zangramru.settings import settings
DB = PostgresEngine(
config={
"database": settings.db_base,
"user": settings.db_user,
"password": settings.db_pass,
"host": settings.db_host,
"port": settings.db_port,
}
)
APP_REGISTRY = AppRegistry(apps=["zangramru.db.app_conf"])
+1
View File
@@ -0,0 +1 @@
"""Services for zangramru."""
+72
View File
@@ -0,0 +1,72 @@
import enum
from pathlib import Path
from tempfile import gettempdir
from pydantic_settings import BaseSettings, SettingsConfigDict
from yarl import URL
TEMP_DIR = Path(gettempdir())
class LogLevel(enum.StrEnum):
"""Possible log levels."""
NOTSET = "NOTSET"
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
FATAL = "FATAL"
class Settings(BaseSettings):
"""
Application settings.
These parameters can be configured
with environment variables.
"""
host: str = "127.0.0.1"
port: int = 8000
# quantity of workers for uvicorn
workers_count: int = 1
# Enable uvicorn reloading
reload: bool = False
# Current environment
environment: str = "dev"
log_level: LogLevel = LogLevel.INFO
# Variables for the database
db_host: str = "localhost"
db_port: int = 5432
db_user: str = "zangramru"
db_pass: str = "zangramru" # noqa: S105
db_base: str = "zangramru"
db_echo: bool = False
@property
def db_url(self) -> URL:
"""
Assemble database URL from settings.
:return: database URL.
"""
return URL.build(
scheme="postgresql",
host=self.db_host,
port=self.db_port,
user=self.db_user,
password=self.db_pass,
path=f"/{self.db_base}",
)
model_config = SettingsConfigDict(
env_file=".env",
env_prefix="ZANGRAMRU_",
env_file_encoding="utf-8",
)
settings = Settings()
+1
View File
@@ -0,0 +1 @@
"""WEB API for zangramru."""
+1
View File
@@ -0,0 +1 @@
"""zangramru API package."""
+5
View File
@@ -0,0 +1,5 @@
"""Echo API."""
from .views import router
__all__ = ["router"]
+7
View File
@@ -0,0 +1,7 @@
from pydantic import BaseModel
class Message(BaseModel):
"""Simple message model."""
message: str
+18
View File
@@ -0,0 +1,18 @@
from fastapi import APIRouter
from zangramru.web.api.echo.schema import Message
router = APIRouter()
@router.post("/", response_model=Message)
async def send_echo_message(
incoming_message: Message,
) -> Message:
"""
Sends echo back to user.
:param incoming_message: incoming message.
:returns: message same as the incoming.
"""
return incoming_message
+5
View File
@@ -0,0 +1,5 @@
"""API for checking project status."""
from zangramru.web.api.monitoring.views import router
__all__ = ["router"]
+12
View File
@@ -0,0 +1,12 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
def health_check() -> None:
"""
Checks the health of a project.
It returns 200 if the project is healthy.
"""
+7
View File
@@ -0,0 +1,7 @@
from fastapi.routing import APIRouter
from zangramru.web.api import echo, monitoring
api_router = APIRouter()
api_router.include_router(monitoring.router)
api_router.include_router(echo.router, prefix="/echo", tags=["echo"])
+26
View File
@@ -0,0 +1,26 @@
from fastapi import FastAPI
from zangramru.web.api.router import api_router
from zangramru.web.lifespan import lifespan_setup
def get_app() -> FastAPI:
"""
Get FastAPI application.
This is the main constructor of an application.
:return: application.
"""
app = FastAPI(
title="zangramru",
lifespan=lifespan_setup,
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
)
# Main router for the API.
app.include_router(router=api_router, prefix="/api")
return app
+24
View File
@@ -0,0 +1,24 @@
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan_setup(
app: FastAPI,
) -> AsyncGenerator[None, None]: # pragma: no cover
"""
Actions to run on application startup.
This function uses fastAPI app to store data
in the state, such as db_engine.
:param app: the fastAPI application.
:return: function that actually performs actions.
"""
app.middleware_stack = None
app.middleware_stack = app.build_middleware_stack()
yield