makeup readme and clean unnecessary files
This commit is contained in:
parent
03c4941af0
commit
9b096057c0
100
.gitlab-ci.yml
100
.gitlab-ci.yml
@ -1,100 +0,0 @@
|
|||||||
stages:
|
|
||||||
- lint
|
|
||||||
- build
|
|
||||||
- backup
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
.configure_ssh:
|
|
||||||
before_script:
|
|
||||||
# Run ssh-agent for keys management
|
|
||||||
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
|
|
||||||
- eval $(ssh-agent -s)
|
|
||||||
# Add place for ssh related files
|
|
||||||
- mkdir -p ~/.ssh
|
|
||||||
- chmod 700 ~/.ssh
|
|
||||||
# Initialize token
|
|
||||||
- chmod 400 "$SSH_PRIVATE_KEY"
|
|
||||||
- ssh-add "$SSH_PRIVATE_KEY"
|
|
||||||
# Add server fingerprint to known hosts
|
|
||||||
- ssh-keyscan "$SSH_HOST" >> ~/.ssh/known_hosts
|
|
||||||
- chmod 644 ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
.on_merge_request:
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- when: never
|
|
||||||
|
|
||||||
lint-ruff:
|
|
||||||
stage: lint
|
|
||||||
image: registry.gitlab.com/pipeline-components/ruff:latest
|
|
||||||
rules:
|
|
||||||
- !reference [.on_merge_request, rules]
|
|
||||||
script:
|
|
||||||
- echo "☕ Linting with ruff"
|
|
||||||
- ruff check --output-format=gitlab src/
|
|
||||||
- echo "✅ Passed"
|
|
||||||
|
|
||||||
lint-mypy:
|
|
||||||
stage: lint
|
|
||||||
image: python:3.12
|
|
||||||
rules:
|
|
||||||
- !reference [.on_merge_request, rules]
|
|
||||||
before_script:
|
|
||||||
- pip install mypy
|
|
||||||
- apt install make
|
|
||||||
- make deps
|
|
||||||
script:
|
|
||||||
- echo "🐍 Typechecking with mypy"
|
|
||||||
- mypy src
|
|
||||||
- echo "✅ Passed"
|
|
||||||
|
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
image: docker:latest
|
|
||||||
services:
|
|
||||||
- docker:dind
|
|
||||||
before_script:
|
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
||||||
script:
|
|
||||||
- docker pull $CI_REGISTRY_IMAGE:latest || true
|
|
||||||
- docker build --target prod --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:latest
|
|
||||||
|
|
||||||
database-backup:
|
|
||||||
stage: backup
|
|
||||||
image: ubuntu:latest
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
||||||
before_script:
|
|
||||||
- !reference [.configure_ssh, before_script]
|
|
||||||
script:
|
|
||||||
- echo "💾 backuping database"
|
|
||||||
- ssh $SSH_USER@$SSH_HOST "docker exec database pg_dump --column-inserts udom >> pre_deploy.sql"
|
|
||||||
- echo "✅ Passed"
|
|
||||||
|
|
||||||
deploy-dev:
|
|
||||||
stage: deploy
|
|
||||||
image: ubuntu:latest
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_BRANCH == "dev"'
|
|
||||||
before_script:
|
|
||||||
- !reference [.configure_ssh, before_script]
|
|
||||||
script:
|
|
||||||
- echo "🚀🧨 Deploing dev changes"
|
|
||||||
- ssh $SSH_USER@$SSH_HOST "cd /root/udom_dev/ && git pull && docker compose -f compose-dev.yaml up -d --build --remove-orphans"
|
|
||||||
- echo "✅ Passed"
|
|
||||||
|
|
||||||
deploy-main:
|
|
||||||
stage: deploy
|
|
||||||
image: ubuntu:latest
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
||||||
before_script:
|
|
||||||
- !reference [.configure_ssh, before_script]
|
|
||||||
script:
|
|
||||||
- echo "🚀 Deploing changes"
|
|
||||||
- ssh $SSH_USER@$SSH_HOST "cd /root/udom/ && git pull && echo $SERVER_TOKEN | docker login registry.gitlab.com -u 'Server' --password-stdin && docker compose pull && docker compose up -d --build --remove-orphans"
|
|
||||||
- echo "✅ Passed"
|
|
39
README.md
39
README.md
@ -1,15 +1,50 @@
|
|||||||
# python_dev
|
# python_dev
|
||||||
|
|
||||||
# launch
|
# Запуск
|
||||||
|
```cp .env.example .env```
|
||||||
```docker compose up -d```
|
```docker compose up -d```
|
||||||
|
|
||||||
# migrations
|
# миграции БД
|
||||||
|
```cp .env.example .env```
|
||||||
```cd alembic/db1 && alembic revision --autogenerate && alembic upgrade head```
|
```cd alembic/db1 && alembic revision --autogenerate && alembic upgrade head```
|
||||||
```cd alembic/db2 && alembic revision --autogenerate && alembic upgrade head```
|
```cd alembic/db2 && alembic revision --autogenerate && alembic upgrade head```
|
||||||
|
|
||||||
|
# режим разработки
|
||||||
|
```cp .env.example .env```
|
||||||
|
```rye sync```
|
||||||
|
```granian src/app.py --reload```
|
||||||
|
|
||||||
|
# структура проекта
|
||||||
|
📦python_dev_farpost
|
||||||
|
┣ 📂alembic
|
||||||
|
┃ ┣ 📂db1 - вспомогательные инструменты alembic для миграций первой БД
|
||||||
|
┃ ┗ 📂db2 - вспомогательные инструменты alembic для миграций второй БД
|
||||||
|
┣ 📂database
|
||||||
|
┃ ┣ 📂dumps - дампы БД
|
||||||
|
┃ ┗ 📜....sh - скрипт, для автоматизации создания и импорта дампов в multiDB
|
||||||
|
┣ 📂src
|
||||||
|
┃ ┣ 📂adapters
|
||||||
|
┃ ┃ ┗ 📂database - модели sqlAlchemy и инструменты для подключения к БД
|
||||||
|
┃ ┣ 📂api - эндпоинты fastapi + формирование датасета (не делил ввиду отстутствия необходимости)
|
||||||
|
┃ ┣ 📂schemas - схемы для обработки входных и выходных данных (в тч валидации данных для БД)
|
||||||
|
┃ ┣ 📜app.py - приложение для запуска
|
||||||
|
┃ ┣ 📜settings.py - валидация настроек из .env, которые в дальнейшем удобно брать
|
||||||
|
┣ 📜.env.example -
|
||||||
|
┣ 📜.gitignore
|
||||||
|
┣ 📜compose.yaml
|
||||||
|
┣ 📜Dockerfile
|
||||||
|
┣ 📜Makefile
|
||||||
|
┣ 📜pyproject.toml - данные проекта для запуска rye
|
||||||
|
┣ 📜README.md
|
||||||
|
┣ 📜requirements-dev.lock
|
||||||
|
┗ 📜requirements.lock
|
||||||
|
|
||||||
|
|
||||||
# что не по ТЗ
|
# что не по ТЗ
|
||||||
- мне не очень понравилось, что space_type и event_type сделаны через отдельные таблицы
|
- мне не очень понравилось, что space_type и event_type сделаны через отдельные таблицы
|
||||||
- потому что работать с этой таблицей будет бэкенд, и у нас есть различные API хэндлеры, которым, чтобы создать запись в БД нужно
|
- потому что работать с этой таблицей будет бэкенд, и у нас есть различные API хэндлеры, которым, чтобы создать запись в БД нужно
|
||||||
сходить на дочерние таблицы, найти нужный тип (например event_type - login), взять от него id, прийти назад и создать запись с нужным id, при этом это ещё будет не надёжным (кто-то удалит тип, поменяет название, всё поляжет) + а зачем нам отдельная таблица? (я в том смысле, что над этими типа у нас есть все операции круд, но без изменения кода бэкенда - это либо бесполезно, либо опасно)
|
сходить на дочерние таблицы, найти нужный тип (например event_type - login), взять от него id, прийти назад и создать запись с нужным id, при этом это ещё будет не надёжным (кто-то удалит тип, поменяет название, всё поляжет) + а зачем нам отдельная таблица? (я в том смысле, что над этими типа у нас есть все операции круд, но без изменения кода бэкенда - это либо бесполезно, либо опасно)
|
||||||
- я заменил на более удобные Enum
|
- я заменил на более удобные Enum
|
||||||
|
|
||||||
|
- (не знаю успею ли я закончить формирование датасета comments после ответа на вопрос)
|
||||||
|
- в ТЗ было сказано, что мне нужно сделать датасет в котором должна быть информация о том, кто кому какой коммент оставил, хотя упоминание комментов было только в логах. Если бы мне такое попалось как реальное задание, то я бы пошёл к аналитикам, и стал настаивать на создании отдельной таблицы комментариев, где было бы указано - от кого, на какой пост, текст комментария
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
class RepositoryException(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class AccessDenied(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class ResultNotFound(RepositoryException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationException(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class WrongCredentials(RepositoryException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class ForeignKeyError(RepositoryException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class JwtException(AuthorizationException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class JwtExpired(JwtException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class JwtInvalid(JwtException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshException(AuthorizationException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshExpired(RefreshException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshInvalid(RefreshException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class VerificationException(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class RefreshClientInfoIncorrect(RefreshException): ...
|
|
||||||
|
|
||||||
|
|
||||||
class FileNotFound(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class UserNotRegistered(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class PlaceOrderForeignKeyError(Exception): ...
|
|
||||||
|
|
||||||
|
|
||||||
class UserAlreadyExist(Exception): ...
|
|
@ -1,142 +0,0 @@
|
|||||||
from abc import abstractmethod
|
|
||||||
from typing import Any, Optional, Protocol
|
|
||||||
|
|
||||||
from sqlalchemy import func, insert, select, update
|
|
||||||
from sqlalchemy.exc import IntegrityError, NoResultFound
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
from sqlalchemy.sql.base import ExecutableOption
|
|
||||||
|
|
||||||
from src.utils.exceptions import ForeignKeyError, ResultNotFound
|
|
||||||
|
|
||||||
_sentinel: Any = object()
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractRepository(Protocol):
|
|
||||||
@abstractmethod
|
|
||||||
async def add_one(self, **data):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemyRepository(AbstractRepository):
|
|
||||||
model = _sentinel
|
|
||||||
|
|
||||||
def __init__(self, session: AsyncSession):
|
|
||||||
self.session = session
|
|
||||||
|
|
||||||
async def add_one(self, **data):
|
|
||||||
stmt = insert(self.model).values(**data).returning(self.model)
|
|
||||||
try:
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.scalar_one()
|
|
||||||
except IntegrityError:
|
|
||||||
raise ForeignKeyError
|
|
||||||
|
|
||||||
async def edit_one(self, id: int, **data):
|
|
||||||
stmt = update(self.model).values(**data).filter_by(id=id).returning(self.model)
|
|
||||||
try:
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalar_one()
|
|
||||||
except NoResultFound:
|
|
||||||
raise ResultNotFound
|
|
||||||
except IntegrityError:
|
|
||||||
raise ForeignKeyError
|
|
||||||
|
|
||||||
async def find_all(self):
|
|
||||||
stmt = select(self.model).options(*self.get_select_options())
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalars().fetchall()
|
|
||||||
|
|
||||||
async def find_filtered(self, sort_by: str = "", **filter_by):
|
|
||||||
stmt = (
|
|
||||||
select(self.model)
|
|
||||||
.options(*self.get_select_options())
|
|
||||||
.filter_by(**filter_by)
|
|
||||||
.order_by(getattr(self.model, sort_by, None))
|
|
||||||
)
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalars().fetchall()
|
|
||||||
|
|
||||||
async def find_filtered_and_paginated(self, page: int, limit: int, **filter_by):
|
|
||||||
stmt = (
|
|
||||||
select(self.model)
|
|
||||||
.options(*self.get_select_options())
|
|
||||||
.filter_by(**filter_by)
|
|
||||||
.offset((page - 1) * limit)
|
|
||||||
.limit(limit)
|
|
||||||
)
|
|
||||||
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalars().fetchall()
|
|
||||||
|
|
||||||
async def find_one(self, **filter_by):
|
|
||||||
stmt = (
|
|
||||||
select(self.model)
|
|
||||||
.options(*self.get_select_options())
|
|
||||||
.filter_by(**filter_by)
|
|
||||||
)
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
try:
|
|
||||||
return res.scalar_one()
|
|
||||||
except NoResultFound:
|
|
||||||
raise ResultNotFound
|
|
||||||
|
|
||||||
async def count_filtered(self, **filter_by):
|
|
||||||
stmt = (
|
|
||||||
select(func.count())
|
|
||||||
.select_from(self.model)
|
|
||||||
.options(*self.get_select_options())
|
|
||||||
.filter_by(**filter_by)
|
|
||||||
)
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalar_one()
|
|
||||||
|
|
||||||
async def find_filtered_in(self, column_name: str, values: list):
|
|
||||||
stmt = (
|
|
||||||
select(self.model)
|
|
||||||
.options(*self.get_select_options())
|
|
||||||
.filter(getattr(self.model, column_name).in_(values))
|
|
||||||
)
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalars().fetchall()
|
|
||||||
|
|
||||||
# this find operation in delete methods help us to raise 404 error instead of 50x
|
|
||||||
async def delete_one(self, id: int) -> None:
|
|
||||||
await self.session.delete((await self.find_one(id=id)))
|
|
||||||
|
|
||||||
async def delete_filtered(self, **filter_by) -> None:
|
|
||||||
for cart_item in await self.find_filtered(**filter_by):
|
|
||||||
await self.session.delete(cart_item)
|
|
||||||
|
|
||||||
def get_select_options(self) -> list[ExecutableOption]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def count_filtered_by_fastadmin(
|
|
||||||
self,
|
|
||||||
joins: list[Any],
|
|
||||||
filters: list[Any],
|
|
||||||
):
|
|
||||||
stmt = select(func.count()).select_from(self.model).filter(*filters)
|
|
||||||
for join in joins:
|
|
||||||
stmt = stmt.join(join)
|
|
||||||
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.unique().scalar_one()
|
|
||||||
|
|
||||||
async def find_filtered_by_fastadmin(
|
|
||||||
self,
|
|
||||||
options: list[Any],
|
|
||||||
joins: list[Any],
|
|
||||||
filters: list[Any],
|
|
||||||
sort_by: Optional[Any],
|
|
||||||
offset: int,
|
|
||||||
limit: int,
|
|
||||||
):
|
|
||||||
stmt = select(self.model).filter(*filters).offset(offset).limit(limit)
|
|
||||||
if sort_by is not None:
|
|
||||||
stmt = stmt.order_by(sort_by)
|
|
||||||
for join in joins:
|
|
||||||
stmt = stmt.join(join)
|
|
||||||
stmt = stmt.options(*options)
|
|
||||||
|
|
||||||
res = await self.session.execute(stmt)
|
|
||||||
return res.scalars()
|
|
Loading…
x
Reference in New Issue
Block a user