diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml new file mode 100644 index 0000000..3b3fb20 --- /dev/null +++ b/.github/workflows/linters.yaml @@ -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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5cf5615 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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', + ] diff --git a/fastapi_file_server/__init__.py b/fastapi_file_server/__init__.py index b794fd4..3dc1f76 100644 --- a/fastapi_file_server/__init__.py +++ b/fastapi_file_server/__init__.py @@ -1 +1 @@ -__version__ = '0.1.0' +__version__ = "0.1.0" diff --git a/fastapi_file_server/app/alembic/env.py b/fastapi_file_server/app/alembic/env.py index 9844b85..194f060 100644 --- a/fastapi_file_server/app/alembic/env.py +++ b/fastapi_file_server/app/alembic/env.py @@ -1,20 +1,22 @@ from logging.config import fileConfig +import os +import sys from alembic import context -import sys, os - from sqlalchemy.engine import create_engine from core.db import DATABASE_URL myPath = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, myPath + '/../../') +sys.path.insert(0, myPath + "/../../") config = context.config from app.models import BaseMeta + + target_metadata = BaseMeta.metadata @@ -52,9 +54,7 @@ def run_migrations_online(): connectable = create_engine(DATABASE_URL) with connectable.connect() as connection: - context.configure( - connection=connection, target_metadata=target_metadata - ) + context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): context.run_migrations() diff --git a/fastapi_file_server/app/alembic/versions/3bbf7cb4eaa2_.py b/fastapi_file_server/app/alembic/versions/3bbf7cb4eaa2_.py index be4d34f..aa6cdbd 100644 --- a/fastapi_file_server/app/alembic/versions/3bbf7cb4eaa2_.py +++ b/fastapi_file_server/app/alembic/versions/3bbf7cb4eaa2_.py @@ -9,24 +9,31 @@ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql + # revision identifiers, used by Alembic. -revision = '3bbf7cb4eaa2' -down_revision = '5a32159504fd' +revision = "3bbf7cb4eaa2" +down_revision = "5a32159504fd" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('uploaded_files', 'upload_time', - existing_type=postgresql.TIMESTAMP(timezone=True), - nullable=True) + op.alter_column( + "uploaded_files", + "upload_time", + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=True, + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('uploaded_files', 'upload_time', - existing_type=postgresql.TIMESTAMP(timezone=True), - nullable=False) + op.alter_column( + "uploaded_files", + "upload_time", + existing_type=postgresql.TIMESTAMP(timezone=True), + nullable=False, + ) # ### end Alembic commands ### diff --git a/fastapi_file_server/app/alembic/versions/5a32159504fd_.py b/fastapi_file_server/app/alembic/versions/5a32159504fd_.py index ab29cd7..9baa3e9 100644 --- a/fastapi_file_server/app/alembic/versions/5a32159504fd_.py +++ b/fastapi_file_server/app/alembic/versions/5a32159504fd_.py @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '5a32159504fd' +revision = "5a32159504fd" down_revision = None branch_labels = None depends_on = None @@ -18,17 +18,18 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('uploaded_files', - sa.Column('id', sa.BigInteger(), nullable=True), - sa.Column('backend', sa.String(length=16), nullable=False), - sa.Column('data', sa.JSON(), nullable=False), - sa.Column('upload_time', sa.DateTime(timezone=True), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "uploaded_files", + sa.Column("id", sa.BigInteger(), nullable=True), + sa.Column("backend", sa.String(length=16), nullable=False), + sa.Column("data", sa.JSON(), nullable=False), + sa.Column("upload_time", sa.DateTime(timezone=True), nullable=False), + sa.PrimaryKeyConstraint("id"), ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('uploaded_files') + op.drop_table("uploaded_files") # ### end Alembic commands ### diff --git a/fastapi_file_server/app/depends.py b/fastapi_file_server/app/depends.py index b99768e..39e7e32 100644 --- a/fastapi_file_server/app/depends.py +++ b/fastapi_file_server/app/depends.py @@ -6,4 +6,6 @@ from core.config import env_config async def check_token(api_key: str = Security(default_security)): 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!" + ) diff --git a/fastapi_file_server/app/models.py b/fastapi_file_server/app/models.py index 88c102a..e8cb030 100644 --- a/fastapi_file_server/app/models.py +++ b/fastapi_file_server/app/models.py @@ -1,5 +1,5 @@ -from enum import Enum from datetime import datetime +from enum import Enum import ormar @@ -12,8 +12,8 @@ class BaseMeta(ormar.ModelMeta): class UploadBackends(str, Enum): - aiogram = 'aiogram' - telethon = 'telethon' + aiogram = "aiogram" + telethon = "telethon" class UploadedFile(ormar.Model): diff --git a/fastapi_file_server/app/services/file_uploader.py b/fastapi_file_server/app/services/file_uploader.py index 313b911..8f7d542 100644 --- a/fastapi_file_server/app/services/file_uploader.py +++ b/fastapi_file_server/app/services/file_uploader.py @@ -1,11 +1,12 @@ -from typing import Optional from io import BytesIO +from typing import Optional from fastapi import UploadFile + from telegram_files_storage import AiogramFilesStorage, TelethonFilesStorage -from core.config import env_config from app.models import UploadedFile, UploadBackends +from core.config import env_config class FileUploader: @@ -67,7 +68,9 @@ class FileUploader: 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 return True @@ -82,12 +85,18 @@ class FileUploader: async def prepare(cls): if env_config.BOT_TOKENS: 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: 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 ] @@ -99,7 +108,9 @@ class FileUploader: if not cls.AIOGRAM_STORAGES: 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] @@ -108,12 +119,16 @@ class FileUploader: if not cls.TELETHON_STORAGES: 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] @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) upload_result = await uploader._upload() diff --git a/fastapi_file_server/app/views.py b/fastapi_file_server/app/views.py index cf69e5a..440668b 100644 --- a/fastapi_file_server/app/views.py +++ b/fastapi_file_server/app/views.py @@ -1,19 +1,17 @@ from typing import Optional -from fastapi import File, UploadFile, Depends, Form -from starlette import status -from fastapi import APIRouter, HTTPException +from fastapi import File, UploadFile, Depends, Form, APIRouter, HTTPException +from starlette import status + +from app.depends import check_token from app.models import UploadedFile as UploadedFileDB from app.serializers import UploadedFile, CreateUploadedFile from app.services.file_uploader import FileUploader -from app.depends import check_token router = APIRouter( - prefix="/api/v1/files", - dependencies=[Depends(check_token)], - tags=["files"] + prefix="/api/v1/files", dependencies=[Depends(check_token)], tags=["files"] ) @@ -22,9 +20,13 @@ async def get_files(): return await UploadedFileDB.objects.all() -@router.get("/{file_id}", response_model=UploadedFile, responses={ - 404: {}, -}) +@router.get( + "/{file_id}", + response_model=UploadedFile, + responses={ + 404: {}, + }, +) async def get_file(file_id: int): 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) async def create_file(data: CreateUploadedFile): - return await UploadedFileDB.objects.create( - **data.dict() - ) + return await UploadedFileDB.objects.create(**data.dict()) @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) -@router.delete("/{file_id}", response_model=UploadedFile, responses={ - 400: {} -}) +@router.delete("/{file_id}", response_model=UploadedFile, responses={400: {}}) async def delete_file(file_id: int): uploaded_file = await UploadedFileDB.objects.get_or_none(id=file_id) diff --git a/fastapi_file_server/core/app.py b/fastapi_file_server/core/app.py index 2c4d780..34533d3 100644 --- a/fastapi_file_server/core/app.py +++ b/fastapi_file_server/core/app.py @@ -1,8 +1,8 @@ from fastapi import FastAPI -from core.db import database from app.on_start import on_start from app.views import router +from core.db import database def start_app() -> FastAPI: @@ -12,7 +12,7 @@ def start_app() -> FastAPI: app.include_router(router) - @app.on_event('startup') + @app.on_event("startup") async def startup() -> None: database_ = app.state.database if not database_.is_connected: @@ -20,7 +20,7 @@ def start_app() -> FastAPI: await on_start() - @app.on_event('shutdown') + @app.on_event("shutdown") async def shutdown() -> None: database_ = app.state.database if database_.is_connected: diff --git a/fastapi_file_server/core/auth.py b/fastapi_file_server/core/auth.py index 18ea52d..7cc07b5 100644 --- a/fastapi_file_server/core/auth.py +++ b/fastapi_file_server/core/auth.py @@ -1,5 +1,4 @@ from fastapi.security import APIKeyHeader -from fastapi.security.utils import get_authorization_scheme_param default_security = APIKeyHeader(name="Authorization") diff --git a/fastapi_file_server/core/config.py b/fastapi_file_server/core/config.py index 1f37f0f..7871208 100644 --- a/fastapi_file_server/core/config.py +++ b/fastapi_file_server/core/config.py @@ -1,9 +1,11 @@ from typing import Optional + from pydantic import BaseModel, BaseSettings BotToken = str -TelethonSessionName= str +TelethonSessionName = str + class TelethonConfig(BaseModel): APP_ID: int @@ -27,8 +29,8 @@ class EnvConfig(BaseSettings): TELETHON_SESSIONS: Optional[list[TelethonSessionName]] class Config: - env_file = '.env' - env_file_encoding = 'utf-8' + env_file = ".env" + env_file_encoding = "utf-8" env_config = EnvConfig() diff --git a/fastapi_file_server/core/db.py b/fastapi_file_server/core/db.py index 6037ba3..0879e69 100644 --- a/fastapi_file_server/core/db.py +++ b/fastapi_file_server/core/db.py @@ -1,6 +1,6 @@ from urllib.parse import quote -from databases import Database +from databases import Database from sqlalchemy import MetaData from core.config import env_config diff --git a/pyproject.toml b/pyproject.toml index 11a70ae..a21ed32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,3 +24,43 @@ mypy = "^0.910" [build-system] requires = ["poetry-core>=1.0.0"] 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"] diff --git a/tests/test_fastapi_file_server.py b/tests/test_fastapi_file_server.py index 4b8aafa..c2fdf45 100644 --- a/tests/test_fastapi_file_server.py +++ b/tests/test_fastapi_file_server.py @@ -2,4 +2,4 @@ from fastapi_file_server import __version__ def test_version(): - assert __version__ == '0.1.0' + assert __version__ == "0.1.0"