mirror of
https://github.com/evroon/bracket.git
synced 2026-01-05 20:48:05 -05:00
Add ruff check (#294)
This commit is contained in:
4
.github/workflows/backend.yml
vendored
4
.github/workflows/backend.yml
vendored
@@ -53,3 +53,7 @@ jobs:
|
||||
- name: Run black
|
||||
run: pipenv run black --check .
|
||||
working-directory: backend
|
||||
|
||||
- name: Run ruff
|
||||
run: pipenv run ruff check .
|
||||
working-directory: backend
|
||||
|
||||
@@ -5,39 +5,40 @@ name = "pypi"
|
||||
|
||||
[packages]
|
||||
aiopg = ">=1.4.0"
|
||||
alembic = ">=1.9.1"
|
||||
click = ">=8.1.3"
|
||||
databases = {extras = ["asyncpg"], version = ">=0.7.0"}
|
||||
fastapi = ">=0.88.0"
|
||||
fastapi-cache2 = ">=0.2.0"
|
||||
fastapi-sso = ">=0.6.4"
|
||||
gunicorn = ">=20.1.0"
|
||||
uvicorn = ">=0.20.0"
|
||||
starlette = ">=0.22.0"
|
||||
heliclockter = ">=1.0.4"
|
||||
parameterized = ">=0.8.1"
|
||||
passlib = ">=1.7.4"
|
||||
pydantic = "<2.0.0"
|
||||
pyjwt = ">=2.6.0"
|
||||
python-dotenv = ">=0.21.0"
|
||||
python-multipart = ">=0.0.5"
|
||||
sentry-sdk = ">=1.13.0"
|
||||
sqlalchemy = "<2.0"
|
||||
sqlalchemy-stubs = ">=0.4"
|
||||
pydantic = "<2.0.0"
|
||||
heliclockter = ">=1.0.4"
|
||||
alembic = ">=1.9.1"
|
||||
types-simplejson = ">=3.18.0"
|
||||
python-dotenv = ">=0.21.0"
|
||||
databases = {extras = ["asyncpg"], version = ">=0.7.0"}
|
||||
passlib = ">=1.7.4"
|
||||
starlette = ">=0.22.0"
|
||||
types-passlib = "*"
|
||||
pyjwt = ">=2.6.0"
|
||||
click = ">=8.1.3"
|
||||
python-multipart = ">=0.0.5"
|
||||
parameterized = ">=0.8.1"
|
||||
sentry-sdk = ">=1.13.0"
|
||||
fastapi-sso = ">=0.6.4"
|
||||
types-simplejson = ">=3.18.0"
|
||||
uvicorn = ">=0.20.0"
|
||||
|
||||
[dev-packages]
|
||||
mypy = ">=1.3.1"
|
||||
black = ">=22.12.0"
|
||||
isort = ">=5.11.4"
|
||||
pylint = ">=2.15.10"
|
||||
pytest = ">=7.2.0"
|
||||
pytest-cov = ">=4.0.0"
|
||||
pytest-asyncio = ">=0.20.3"
|
||||
pytest-xdist = ">=3.2.1"
|
||||
aiohttp = ">=3.8.3"
|
||||
aioresponses = ">=0.7.4"
|
||||
black = ">=22.12.0"
|
||||
isort = ">=5.11.4"
|
||||
mypy = ">=1.3.1"
|
||||
pylint = ">=2.15.10"
|
||||
pytest = ">=7.2.0"
|
||||
pytest-asyncio = ">=0.20.3"
|
||||
pytest-cov = ">=4.0.0"
|
||||
pytest-xdist = ">=3.2.1"
|
||||
ruff = ">=0.0.292"
|
||||
|
||||
[requires]
|
||||
python_version = "3.10"
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# ruff: noqa: E402. We first need to insert the path
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from alembic import context
|
||||
@@ -9,8 +10,8 @@ from alembic import context
|
||||
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
from bracket.config import config # pylint: disable=wrong-import-position
|
||||
from bracket.schema import Base # pylint: disable=wrong-import-position
|
||||
from bracket.config import config
|
||||
from bracket.schema import Base
|
||||
|
||||
ALEMBIC_CONFIG = context.config
|
||||
logger = logging.getLogger('alembic')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Add ON DELETE CASCADE to users_x_clubs
|
||||
|
||||
Revision ID: 274385f2a757
|
||||
Revises:
|
||||
Revises:
|
||||
Create Date: 2023-04-15 11:08:57.406407
|
||||
|
||||
"""
|
||||
|
||||
@@ -15,7 +15,7 @@ from bracket.utils.types import assert_some
|
||||
|
||||
|
||||
def player_already_scheduled(player: Player, draft_round: RoundWithMatches) -> bool:
|
||||
return any((player.id in match.player_ids for match in draft_round.matches))
|
||||
return any(player.id in match.player_ids for match in draft_round.matches)
|
||||
|
||||
|
||||
async def get_possible_upcoming_matches_for_players(
|
||||
@@ -40,11 +40,9 @@ async def get_possible_upcoming_matches_for_players(
|
||||
@lru_cache
|
||||
def team_already_scheduled_before(player1: Player, player2: Player) -> bool:
|
||||
return any(
|
||||
(
|
||||
player1 in match.team1.players and player2 in match.team2.players
|
||||
for round_ in other_rounds
|
||||
for match in round_.matches
|
||||
)
|
||||
player1 in match.team1.players and player2 in match.team2.players
|
||||
for round_ in other_rounds
|
||||
for match in round_.matches
|
||||
)
|
||||
|
||||
team_already_scheduled_before.cache_clear()
|
||||
|
||||
@@ -20,10 +20,8 @@ async def get_possible_upcoming_matches_for_teams(
|
||||
for i, team1 in enumerate(teams):
|
||||
for _, team2 in enumerate(teams[i + 1 :]):
|
||||
team_already_scheduled = any(
|
||||
(
|
||||
team1.id in match.team_ids or team2.id in match.team_ids
|
||||
for match in draft_round.matches
|
||||
)
|
||||
team1.id in match.team_ids or team2.id in match.team_ids
|
||||
for match in draft_round.matches
|
||||
)
|
||||
if team_already_scheduled:
|
||||
continue
|
||||
|
||||
@@ -9,17 +9,15 @@ async def get_possible_upcoming_matches_round_robin(
|
||||
) -> list[SuggestedMatch]:
|
||||
suggestions: list[SuggestedMatch] = []
|
||||
rounds = await get_rounds_for_stage(tournament_id, stage_id)
|
||||
draft_round = next((round_ for round_ in rounds if round_.id == round_id))
|
||||
draft_round = next(round_ for round_ in rounds if round_.id == round_id)
|
||||
|
||||
teams = await get_teams_with_members(tournament_id, only_active_teams=True)
|
||||
|
||||
for i, team1 in enumerate(teams):
|
||||
for _, team2 in enumerate(teams[i + 1 :]):
|
||||
team_already_scheduled = any(
|
||||
(
|
||||
team1.id in match.team_ids or team2.id in match.team_ids
|
||||
for match in draft_round.matches
|
||||
)
|
||||
team1.id in match.team_ids or team2.id in match.team_ids
|
||||
for match in draft_round.matches
|
||||
)
|
||||
if team_already_scheduled:
|
||||
continue
|
||||
|
||||
@@ -12,7 +12,15 @@ async def sql_delete_match(match_id: int) -> None:
|
||||
|
||||
async def sql_create_match(match: MatchCreateBody) -> Match:
|
||||
query = '''
|
||||
INSERT INTO matches (round_id, team1_id, team2_id, team1_score, team2_score, court_id, created)
|
||||
INSERT INTO matches (
|
||||
round_id,
|
||||
team1_id,
|
||||
team2_id,
|
||||
team1_score,
|
||||
team2_score,
|
||||
court_id,
|
||||
created
|
||||
)
|
||||
VALUES (:round_id, :team1_id, :team2_id, 0, 0, :court_id, NOW())
|
||||
RETURNING *
|
||||
'''
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
from typing import List
|
||||
|
||||
from bracket.database import database
|
||||
from bracket.models.db.round import RoundWithMatches
|
||||
from bracket.sql.stages import get_stages_with_rounds_and_matches
|
||||
|
||||
|
||||
async def get_rounds_for_stage(tournament_id: int, stage_id: int) -> List[RoundWithMatches]:
|
||||
async def get_rounds_for_stage(tournament_id: int, stage_id: int) -> list[RoundWithMatches]:
|
||||
stages = await get_stages_with_rounds_and_matches(tournament_id)
|
||||
result_stage = next((stage for stage in stages if stage.id == stage_id), None)
|
||||
if result_stage is None:
|
||||
|
||||
@@ -106,7 +106,7 @@ async def get_next_stage_in_tournament(
|
||||
SELECT id FROM stages AS t
|
||||
WHERE is_active IS TRUE
|
||||
AND stages.tournament_id = :tournament_id
|
||||
ORDER BY id ASC
|
||||
ORDER BY id ASC
|
||||
LIMIT 1
|
||||
),
|
||||
10000000000000
|
||||
@@ -118,7 +118,7 @@ async def get_next_stage_in_tournament(
|
||||
SELECT id FROM stages AS t
|
||||
WHERE is_active IS TRUE
|
||||
AND stages.tournament_id = :tournament_id
|
||||
ORDER BY id DESC
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
),
|
||||
-1
|
||||
@@ -142,7 +142,7 @@ async def sql_activate_next_stage(new_active_stage_id: int, tournament_id: int)
|
||||
UPDATE stages
|
||||
SET is_active = (stages.id = :new_active_stage_id)
|
||||
WHERE stages.tournament_id = :tournament_id
|
||||
|
||||
|
||||
'''
|
||||
await database.execute(
|
||||
query=update_query,
|
||||
|
||||
@@ -12,7 +12,7 @@ async def get_user_access_to_tournament(tournament_id: int, user_id: int) -> boo
|
||||
SELECT DISTINCT t.id
|
||||
FROM users_x_clubs
|
||||
JOIN tournaments t ON t.club_id = users_x_clubs.club_id
|
||||
WHERE user_id = :user_id
|
||||
WHERE user_id = :user_id
|
||||
'''
|
||||
result = await database.fetch_all(query=query, values={'user_id': user_id})
|
||||
return tournament_id in {tournament.id for tournament in result} # type: ignore[attr-defined]
|
||||
@@ -22,7 +22,7 @@ async def get_which_clubs_has_user_access_to(user_id: int) -> set[int]:
|
||||
query = '''
|
||||
SELECT club_id
|
||||
FROM users_x_clubs
|
||||
WHERE user_id = :user_id
|
||||
WHERE user_id = :user_id
|
||||
'''
|
||||
result = await database.fetch_all(query=query, values={'user_id': user_id})
|
||||
return {club.club_id for club in result} # type: ignore[attr-defined]
|
||||
@@ -36,7 +36,7 @@ async def update_user(user_id: int, user: UserToUpdate) -> None:
|
||||
query = '''
|
||||
UPDATE users
|
||||
SET name = :name, email = :email
|
||||
WHERE id = :user_id
|
||||
WHERE id = :user_id
|
||||
'''
|
||||
await database.execute(
|
||||
query=query, values={'user_id': user_id, 'name': user.name, 'email': user.email}
|
||||
@@ -47,7 +47,7 @@ async def update_user_password(user_id: int, password_hash: str) -> None:
|
||||
query = '''
|
||||
UPDATE users
|
||||
SET password_hash = :password_hash
|
||||
WHERE id = :user_id
|
||||
WHERE id = :user_id
|
||||
'''
|
||||
await database.execute(query=query, values={'user_id': user_id, 'password_hash': password_hash})
|
||||
|
||||
@@ -56,7 +56,7 @@ async def get_user_by_id(user_id: int) -> UserPublic | None:
|
||||
query = '''
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE id = :user_id
|
||||
WHERE id = :user_id
|
||||
'''
|
||||
result = await database.fetch_one(query=query, values={'user_id': user_id})
|
||||
return UserPublic.parse_obj(result._mapping) if result is not None else None
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, Mapping
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Type
|
||||
|
||||
from databases import Database
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy.sql import Select
|
||||
@@ -10,27 +8,27 @@ from bracket.utils.types import BaseModelT, assert_some
|
||||
|
||||
|
||||
async def fetch_one_parsed(
|
||||
database: Database, model: Type[BaseModelT], query: Select
|
||||
database: Database, model: type[BaseModelT], query: Select
|
||||
) -> BaseModelT | None:
|
||||
record = await database.fetch_one(query)
|
||||
return model.parse_obj(record._mapping) if record is not None else None
|
||||
|
||||
|
||||
async def fetch_one_parsed_certain(
|
||||
database: Database, model: Type[BaseModelT], query: Select
|
||||
database: Database, model: type[BaseModelT], query: Select
|
||||
) -> BaseModelT:
|
||||
return assert_some(await fetch_one_parsed(database, model, query))
|
||||
|
||||
|
||||
async def fetch_all_parsed(
|
||||
database: Database, model: Type[BaseModelT], query: Select
|
||||
database: Database, model: type[BaseModelT], query: Select
|
||||
) -> list[BaseModelT]:
|
||||
records = await database.fetch_all(query)
|
||||
return [model.parse_obj(record._mapping) for record in records]
|
||||
|
||||
|
||||
async def insert_generic(
|
||||
database: Database, data_model: BaseModelT, table: Table, return_type: Type[BaseModelT]
|
||||
database: Database, data_model: BaseModelT, table: Table, return_type: type[BaseModelT]
|
||||
) -> tuple[int, BaseModelT]:
|
||||
try:
|
||||
last_record_id: int = await database.execute(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from heliclockter import datetime_utc
|
||||
from sqlalchemy import Table
|
||||
|
||||
from bracket.config import Environment, config, environment
|
||||
from bracket.database import database, engine
|
||||
@@ -65,6 +66,9 @@ from bracket.utils.logging import logger
|
||||
from bracket.utils.security import pwd_context
|
||||
from bracket.utils.types import BaseModelT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy import Table
|
||||
|
||||
|
||||
async def create_admin_user() -> int:
|
||||
assert config.admin_email
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, NewType, Sequence, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, NewType, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
BaseModelT = TypeVar('BaseModelT', bound=BaseModel)
|
||||
T = TypeVar('T')
|
||||
JsonDict = dict[str, Any]
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import signal
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
from uvicorn.workers import UvicornWorker
|
||||
|
||||
@@ -21,7 +21,7 @@ class ReloaderThread(threading.Thread):
|
||||
|
||||
|
||||
class RestartableUvicornWorker(UvicornWorker):
|
||||
def __init__(self, *args: List[Any], **kwargs: Dict[str, Any]):
|
||||
def __init__(self, *args: list[Any], **kwargs: dict[str, Any]):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._reloader_thread = ReloaderThread(self)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -evo pipefail
|
||||
|
||||
black .
|
||||
ruff --fix .
|
||||
dmypy run -- --follow-imports=normal --junit-xml= .
|
||||
ENVIRONMENT=CI pytest --cov --cov-report=xml . -vvv
|
||||
pylint alembic bracket tests
|
||||
isort .
|
||||
|
||||
@@ -76,6 +76,7 @@ disable = [
|
||||
'logging-fstring-interpolation',
|
||||
'too-many-arguments',
|
||||
'unspecified-encoding',
|
||||
'wrong-import-position',
|
||||
]
|
||||
|
||||
[tool.bandit]
|
||||
@@ -84,3 +85,34 @@ skips = [
|
||||
'B106',
|
||||
'B108'
|
||||
]
|
||||
|
||||
|
||||
[tool.ruff]
|
||||
select = [
|
||||
"E",
|
||||
"EXE",
|
||||
# "ERA", TODO
|
||||
"F",
|
||||
"FA",
|
||||
"FIX",
|
||||
"I",
|
||||
"ISC",
|
||||
"PGH",
|
||||
"PIE",
|
||||
"PLE",
|
||||
"PLW",
|
||||
"RUF100",
|
||||
"T20",
|
||||
"TCH",
|
||||
"TD",
|
||||
"TID",
|
||||
"UP",
|
||||
"W",
|
||||
]
|
||||
ignore = []
|
||||
line-length = 100
|
||||
respect-gitignore = false
|
||||
show-fixes = true
|
||||
show-source = true
|
||||
target-version = "py311"
|
||||
exclude = [".mypy_cache", ".pytest_cache", "__pycache__"]
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from typing import Generator
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import jwt
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import asyncio
|
||||
import os
|
||||
from asyncio import AbstractEventLoop
|
||||
from collections.abc import AsyncIterator
|
||||
from time import sleep
|
||||
from typing import AsyncIterator
|
||||
|
||||
import pytest
|
||||
from databases import Database
|
||||
|
||||
@@ -210,7 +210,6 @@ async def test_upcoming_matches_endpoint(
|
||||
json_response = await send_tournament_request(
|
||||
HTTPMethod.GET, f'rounds/{round_inserted.id}/upcoming_matches', auth_context, {}
|
||||
)
|
||||
print(json_response)
|
||||
assert json_response == {
|
||||
'data': [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import asyncio
|
||||
import socket
|
||||
from collections.abc import Sequence
|
||||
from contextlib import closing
|
||||
from typing import Final, Optional, Sequence
|
||||
from typing import Final
|
||||
|
||||
import aiohttp
|
||||
import uvicorn
|
||||
@@ -45,11 +46,11 @@ class UvicornTestServer(uvicorn.Server):
|
||||
|
||||
def __init__(self, _app: FastAPI = app, host: str = TEST_HOST, port: int = TEST_PORT):
|
||||
self._startup_done = asyncio.Event()
|
||||
self._serve_task: Optional[asyncio.Task[None]] = None
|
||||
self._serve_task: asyncio.Task[None] | None = None
|
||||
self.should_exit: bool = False
|
||||
super().__init__(config=uvicorn.Config(_app, host=host, port=port))
|
||||
|
||||
async def startup(self, sockets: Optional[Sequence[socket.socket]] = None) -> None:
|
||||
async def startup(self, sockets: Sequence[socket.socket] | None = None) -> None:
|
||||
sockets_list = list(sockets) if sockets is not None else sockets
|
||||
await super().startup(sockets=sockets_list)
|
||||
self.config.setup_event_loop()
|
||||
|
||||
@@ -42,7 +42,6 @@ async def test_update_user(
|
||||
response = await send_auth_request(
|
||||
HTTPMethod.PATCH, f'users/{auth_context.user.id}', auth_context, None, body
|
||||
)
|
||||
print(response)
|
||||
patched_user = await fetch_one_parsed_certain(
|
||||
database, User, query=users.select().where(users.c.id == auth_context.user.id)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncIterator, Type, cast
|
||||
from typing import cast
|
||||
|
||||
from sqlalchemy import Table
|
||||
|
||||
@@ -42,7 +43,7 @@ async def assert_row_count_and_clear(table: Table, expected_rows: int) -> None:
|
||||
|
||||
@asynccontextmanager
|
||||
async def inserted_generic(
|
||||
data_model: BaseModelT, table: Table, return_type: Type[BaseModelT]
|
||||
data_model: BaseModelT, table: Table, return_type: type[BaseModelT]
|
||||
) -> AsyncIterator[BaseModelT]:
|
||||
last_record_id, row_inserted = await insert_generic(database, data_model, table, return_type)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user