Add linters config

This commit is contained in:
2022-01-01 20:48:39 +03:00
parent a7cfff3170
commit f062b41ce8
16 changed files with 179 additions and 60 deletions

35
.github/workflows/linters.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Linters
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
jobs:
Run-Pre-Commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 32
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install pre-commit
run: pip3 install pre-commit
- name: Pre-commit (Push)
env:
SETUPTOOLS_USE_DISTUTILS: stdlib
if: ${{ github.event_name == 'push' }}
run: pre-commit run --source ${{ github.event.before }} --origin ${{ github.event.after }} --show-diff-on-failure
- name: Pre-commit (Pull-Request)
env:
SETUPTOOLS_USE_DISTUTILS: stdlib
if: ${{ github.event_name == 'pull_request' }}
run: pre-commit run --source ${{ github.event.pull_request.base.sha }} --origin ${{ github.event.pull_request.head.sha }} --show-diff-on-failure

20
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,20 @@
exclude: 'docs|node_modules|migrations|.git|.tox'
repos:
- repo: https://github.com/ambv/black
rev: 21.12b0
hooks:
- id: black
language_version: python3.9
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/csachs/pyproject-flake8
rev: v0.0.1a2.post1
hooks:
- id: pyproject-flake8
additional_dependencies: [
'-e', 'git+https://github.com/pycqa/pyflakes@1911c20#egg=pyflakes',
'-e', 'git+https://github.com/pycqa/pycodestyle@d219c68#egg=pycodestyle',
]

View File

@@ -1 +1 @@
__version__ = '0.1.0' __version__ = "0.1.0"

View File

@@ -1,20 +1,22 @@
from logging.config import fileConfig from logging.config import fileConfig
import os
import sys
from alembic import context from alembic import context
import sys, os
from sqlalchemy.engine import create_engine from sqlalchemy.engine import create_engine
from core.db import DATABASE_URL from core.db import DATABASE_URL
myPath = os.path.dirname(os.path.abspath(__file__)) myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../../') sys.path.insert(0, myPath + "/../../")
config = context.config config = context.config
from app.models import BaseMeta from app.models import BaseMeta
target_metadata = BaseMeta.metadata target_metadata = BaseMeta.metadata
@@ -52,9 +54,7 @@ def run_migrations_online():
connectable = create_engine(DATABASE_URL) connectable = create_engine(DATABASE_URL)
with connectable.connect() as connection: with connectable.connect() as connection:
context.configure( context.configure(connection=connection, target_metadata=target_metadata)
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction(): with context.begin_transaction():
context.run_migrations() context.run_migrations()

View File

@@ -9,24 +9,31 @@ from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '3bbf7cb4eaa2' revision = "3bbf7cb4eaa2"
down_revision = '5a32159504fd' down_revision = "5a32159504fd"
branch_labels = None branch_labels = None
depends_on = None depends_on = None
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.alter_column('uploaded_files', 'upload_time', op.alter_column(
existing_type=postgresql.TIMESTAMP(timezone=True), "uploaded_files",
nullable=True) "upload_time",
existing_type=postgresql.TIMESTAMP(timezone=True),
nullable=True,
)
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.alter_column('uploaded_files', 'upload_time', op.alter_column(
existing_type=postgresql.TIMESTAMP(timezone=True), "uploaded_files",
nullable=False) "upload_time",
existing_type=postgresql.TIMESTAMP(timezone=True),
nullable=False,
)
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '5a32159504fd' revision = "5a32159504fd"
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@@ -18,17 +18,18 @@ depends_on = None
def upgrade(): def upgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('uploaded_files', op.create_table(
sa.Column('id', sa.BigInteger(), nullable=True), "uploaded_files",
sa.Column('backend', sa.String(length=16), nullable=False), sa.Column("id", sa.BigInteger(), nullable=True),
sa.Column('data', sa.JSON(), nullable=False), sa.Column("backend", sa.String(length=16), nullable=False),
sa.Column('upload_time', sa.DateTime(timezone=True), nullable=False), sa.Column("data", sa.JSON(), nullable=False),
sa.PrimaryKeyConstraint('id') sa.Column("upload_time", sa.DateTime(timezone=True), nullable=False),
sa.PrimaryKeyConstraint("id"),
) )
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('uploaded_files') op.drop_table("uploaded_files")
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -6,4 +6,6 @@ from core.config import env_config
async def check_token(api_key: str = Security(default_security)): async def check_token(api_key: str = Security(default_security)):
if api_key != env_config.API_KEY: if api_key != env_config.API_KEY:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Wrong api key!") raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Wrong api key!"
)

View File

@@ -1,5 +1,5 @@
from enum import Enum
from datetime import datetime from datetime import datetime
from enum import Enum
import ormar import ormar
@@ -12,8 +12,8 @@ class BaseMeta(ormar.ModelMeta):
class UploadBackends(str, Enum): class UploadBackends(str, Enum):
aiogram = 'aiogram' aiogram = "aiogram"
telethon = 'telethon' telethon = "telethon"
class UploadedFile(ormar.Model): class UploadedFile(ormar.Model):

View File

@@ -1,11 +1,12 @@
from typing import Optional
from io import BytesIO from io import BytesIO
from typing import Optional
from fastapi import UploadFile from fastapi import UploadFile
from telegram_files_storage import AiogramFilesStorage, TelethonFilesStorage from telegram_files_storage import AiogramFilesStorage, TelethonFilesStorage
from core.config import env_config
from app.models import UploadedFile, UploadBackends from app.models import UploadedFile, UploadBackends
from core.config import env_config
class FileUploader: class FileUploader:
@@ -67,7 +68,9 @@ class FileUploader:
storage = self.get_telethon_storage() storage = self.get_telethon_storage()
self.upload_data = await storage.upload(bytes_io, caption=self.caption) # type: ignore self.upload_data = await storage.upload(
bytes_io, caption=self.caption
) # type: ignore
self.upload_backend = UploadBackends.telethon self.upload_backend = UploadBackends.telethon
return True return True
@@ -82,12 +85,18 @@ class FileUploader:
async def prepare(cls): async def prepare(cls):
if env_config.BOT_TOKENS: if env_config.BOT_TOKENS:
cls.AIOGRAM_STORAGES: list[AiogramFilesStorage] = [ cls.AIOGRAM_STORAGES: list[AiogramFilesStorage] = [
AiogramFilesStorage(env_config.TELEGRAM_CHAT_ID, token) for token in env_config.BOT_TOKENS AiogramFilesStorage(env_config.TELEGRAM_CHAT_ID, token)
for token in env_config.BOT_TOKENS
] ]
if env_config.TELETHON_APP_CONFIG and env_config.TELETHON_SESSIONS: if env_config.TELETHON_APP_CONFIG and env_config.TELETHON_SESSIONS:
cls.TELETHON_STORAGES: list[TelethonFilesStorage] = [ cls.TELETHON_STORAGES: list[TelethonFilesStorage] = [
TelethonFilesStorage(env_config.TELEGRAM_CHAT_ID, env_config.TELETHON_APP_CONFIG.APP_ID, env_config.TELETHON_APP_CONFIG.API_HASH, session) TelethonFilesStorage(
env_config.TELEGRAM_CHAT_ID,
env_config.TELETHON_APP_CONFIG.APP_ID,
env_config.TELETHON_APP_CONFIG.API_HASH,
session,
)
for session in env_config.TELETHON_SESSIONS for session in env_config.TELETHON_SESSIONS
] ]
@@ -99,7 +108,9 @@ class FileUploader:
if not cls.AIOGRAM_STORAGES: if not cls.AIOGRAM_STORAGES:
raise ValueError("Aiogram storage not exist!") raise ValueError("Aiogram storage not exist!")
cls._aiogram_storage_index = (cls._aiogram_storage_index + 1) % len(cls.AIOGRAM_STORAGES) cls._aiogram_storage_index = (cls._aiogram_storage_index + 1) % len(
cls.AIOGRAM_STORAGES
)
return cls.AIOGRAM_STORAGES[cls._aiogram_storage_index] return cls.AIOGRAM_STORAGES[cls._aiogram_storage_index]
@@ -108,12 +119,16 @@ class FileUploader:
if not cls.TELETHON_STORAGES: if not cls.TELETHON_STORAGES:
raise ValueError("Telethon storage not exists!") raise ValueError("Telethon storage not exists!")
cls._telethon_storage_index = (cls._telethon_storage_index + 1) % len(cls.TELETHON_STORAGES) cls._telethon_storage_index = (cls._telethon_storage_index + 1) % len(
cls.TELETHON_STORAGES
)
return cls.TELETHON_STORAGES[cls._telethon_storage_index] return cls.TELETHON_STORAGES[cls._telethon_storage_index]
@classmethod @classmethod
async def upload(cls, file: UploadFile, caption: Optional[str] = None) -> Optional[UploadedFile]: async def upload(
cls, file: UploadFile, caption: Optional[str] = None
) -> Optional[UploadedFile]:
uploader = cls(file, caption) uploader = cls(file, caption)
upload_result = await uploader._upload() upload_result = await uploader._upload()

View File

@@ -1,19 +1,17 @@
from typing import Optional from typing import Optional
from fastapi import File, UploadFile, Depends, Form from fastapi import File, UploadFile, Depends, Form, APIRouter, HTTPException
from starlette import status
from fastapi import APIRouter, HTTPException
from starlette import status
from app.depends import check_token
from app.models import UploadedFile as UploadedFileDB from app.models import UploadedFile as UploadedFileDB
from app.serializers import UploadedFile, CreateUploadedFile from app.serializers import UploadedFile, CreateUploadedFile
from app.services.file_uploader import FileUploader from app.services.file_uploader import FileUploader
from app.depends import check_token
router = APIRouter( router = APIRouter(
prefix="/api/v1/files", prefix="/api/v1/files", dependencies=[Depends(check_token)], tags=["files"]
dependencies=[Depends(check_token)],
tags=["files"]
) )
@@ -22,9 +20,13 @@ async def get_files():
return await UploadedFileDB.objects.all() return await UploadedFileDB.objects.all()
@router.get("/{file_id}", response_model=UploadedFile, responses={ @router.get(
404: {}, "/{file_id}",
}) response_model=UploadedFile,
responses={
404: {},
},
)
async def get_file(file_id: int): async def get_file(file_id: int):
uploaded_file = await UploadedFileDB.objects.get_or_none(id=file_id) uploaded_file = await UploadedFileDB.objects.get_or_none(id=file_id)
@@ -36,9 +38,7 @@ async def get_file(file_id: int):
@router.post("/", response_model=UploadedFile) @router.post("/", response_model=UploadedFile)
async def create_file(data: CreateUploadedFile): async def create_file(data: CreateUploadedFile):
return await UploadedFileDB.objects.create( return await UploadedFileDB.objects.create(**data.dict())
**data.dict()
)
@router.post("/upload/", response_model=UploadedFile) @router.post("/upload/", response_model=UploadedFile)
@@ -46,9 +46,7 @@ async def upload_file(file: UploadFile = File({}), caption: Optional[str] = Form
return await FileUploader.upload(file, caption=caption) return await FileUploader.upload(file, caption=caption)
@router.delete("/{file_id}", response_model=UploadedFile, responses={ @router.delete("/{file_id}", response_model=UploadedFile, responses={400: {}})
400: {}
})
async def delete_file(file_id: int): async def delete_file(file_id: int):
uploaded_file = await UploadedFileDB.objects.get_or_none(id=file_id) uploaded_file = await UploadedFileDB.objects.get_or_none(id=file_id)

View File

@@ -1,8 +1,8 @@
from fastapi import FastAPI from fastapi import FastAPI
from core.db import database
from app.on_start import on_start from app.on_start import on_start
from app.views import router from app.views import router
from core.db import database
def start_app() -> FastAPI: def start_app() -> FastAPI:
@@ -12,7 +12,7 @@ def start_app() -> FastAPI:
app.include_router(router) app.include_router(router)
@app.on_event('startup') @app.on_event("startup")
async def startup() -> None: async def startup() -> None:
database_ = app.state.database database_ = app.state.database
if not database_.is_connected: if not database_.is_connected:
@@ -20,7 +20,7 @@ def start_app() -> FastAPI:
await on_start() await on_start()
@app.on_event('shutdown') @app.on_event("shutdown")
async def shutdown() -> None: async def shutdown() -> None:
database_ = app.state.database database_ = app.state.database
if database_.is_connected: if database_.is_connected:

View File

@@ -1,5 +1,4 @@
from fastapi.security import APIKeyHeader from fastapi.security import APIKeyHeader
from fastapi.security.utils import get_authorization_scheme_param
default_security = APIKeyHeader(name="Authorization") default_security = APIKeyHeader(name="Authorization")

View File

@@ -1,9 +1,11 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel, BaseSettings from pydantic import BaseModel, BaseSettings
BotToken = str BotToken = str
TelethonSessionName= str TelethonSessionName = str
class TelethonConfig(BaseModel): class TelethonConfig(BaseModel):
APP_ID: int APP_ID: int
@@ -27,8 +29,8 @@ class EnvConfig(BaseSettings):
TELETHON_SESSIONS: Optional[list[TelethonSessionName]] TELETHON_SESSIONS: Optional[list[TelethonSessionName]]
class Config: class Config:
env_file = '.env' env_file = ".env"
env_file_encoding = 'utf-8' env_file_encoding = "utf-8"
env_config = EnvConfig() env_config = EnvConfig()

View File

@@ -1,6 +1,6 @@
from urllib.parse import quote from urllib.parse import quote
from databases import Database
from databases import Database
from sqlalchemy import MetaData from sqlalchemy import MetaData
from core.config import env_config from core.config import env_config

View File

@@ -24,3 +24,43 @@ mypy = "^0.910"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.black]
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.vscode
| \venv
| alembic
)/
'''
[tool.flake8]
ignore = [
# Whitespace before ':' ( https://www.flake8rules.com/rules/E203.html )
"E203"
]
max-line-length=88
max-complexity = 15
select = "B,C,E,F,W,T4,B9"
exclude = [
# No need to traverse our git directory
".git",
# There's no value in checking cache directories
"__pycache__",
# The conf file is mostly autogenerated, ignore it
"fastapi_file_server/app/alembic/*",
# The old directory contains Flake8 2.0
]
[tool.isort]
profile = "black"
only_sections = true
force_sort_within_sections = true
lines_after_imports = 2
lexicographical = true
sections = ["FUTURE", "STDLIB", "BASEFRAMEWORK", "FRAMEWORKEXT", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
known_baseframework = ["fastapi",]
known_frameworkext = ["starlette",]
src_paths = ["fastapi_file_server"]

View File

@@ -2,4 +2,4 @@ from fastapi_file_server import __version__
def test_version(): def test_version():
assert __version__ == '0.1.0' assert __version__ == "0.1.0"