Skip to content

Commit c8cab54

Browse files
authored
Add pyrefly type checking (#1434)
Prefly is much faster than mypy and a bit stricter Mypy will also still be checked on CI for now
1 parent acf3e31 commit c8cab54

File tree

27 files changed

+178
-192
lines changed

27 files changed

+178
-192
lines changed

.github/workflows/backend.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ jobs:
5656
run: uv run mypy --version && uv run mypy .
5757
working-directory: backend
5858

59+
- name: Run pyrefly
60+
run: uv run pyrefly --version && uv run pyrefly check
61+
working-directory: backend
62+
5963
- name: Run pylint
6064
run: uv run pylint bracket tests cli.py
6165
working-directory: backend

backend/alembic/env.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def run_migrations_offline() -> None:
2727

2828
def run_migrations_online() -> None:
2929
config_ini_section = ALEMBIC_CONFIG.get_section(ALEMBIC_CONFIG.config_ini_section)
30-
config_ini_section["sqlalchemy.url"] = str(config.pg_dsn) # type: ignore[index]
30+
assert config_ini_section is not None, "No section 'alembic' found in config.ini"
31+
config_ini_section["sqlalchemy.url"] = str(config.pg_dsn)
3132

3233
engine = engine_from_config(config_ini_section, prefix="sqlalchemy.", poolclass=pool.NullPool)
3334

backend/bracket/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Config(BaseSettings):
3838
cors_origins: str = "*"
3939
jwt_secret: str
4040
auto_run_migrations: bool = True
41-
pg_dsn: PostgresDsn = "postgresql://user:pass@localhost:5432/db" # type: ignore[assignment]
41+
pg_dsn: PostgresDsn = PostgresDsn("postgresql://user:pass@localhost:5432/db")
4242
sentry_dsn: str | None = None
4343

4444
def is_cors_enabled(self) -> bool:
@@ -50,8 +50,8 @@ class CIConfig(Config):
5050

5151

5252
class DevelopmentConfig(Config):
53-
admin_email: Annotated[str, Field("[email protected]")]
54-
admin_password: Annotated[str, Field("aeGhoe1ahng2Aezai0Dei6Aih6dieHoo")]
53+
admin_email: Annotated[str | None, Field("[email protected]")]
54+
admin_password: Annotated[str | None, Field("aeGhoe1ahng2Aezai0Dei6Aih6dieHoo")]
5555
allow_insecure_http_sso: Annotated[bool, Field(True)]
5656
jwt_secret: Annotated[
5757
str, Field("7495204c062787f257b12d03b88d80da1d338796a6449666eb634c9efbbf5fa7")

backend/bracket/logic/ranking/calculation.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import math
22
from collections import defaultdict
33
from decimal import Decimal
4-
from typing import TypeVar
54

65
from bracket.logic.ranking.statistics import START_ELO, TeamStatistics
76
from bracket.models.db.match import MatchWithDetailsDefinitive
@@ -10,15 +9,12 @@
109
from bracket.models.db.util import StageItemWithRounds
1110
from bracket.sql.rankings import get_ranking_for_stage_item
1211
from bracket.sql.teams import update_team_stats
13-
from bracket.utils.id_types import PlayerId, StageItemInputId, TeamId, TournamentId
12+
from bracket.utils.id_types import StageItemInputId, TournamentId
1413

1514
K = 32
1615
D = 400
1716

1817

19-
TeamIdOrPlayerId = TypeVar("TeamIdOrPlayerId", bound=PlayerId | TeamId)
20-
21-
2218
def set_statistics_for_stage_item_input(
2319
team_index: int,
2420
stats: defaultdict[StageItemInputId, TeamStatistics],

backend/bracket/logic/ranking/elimination.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,19 @@ def get_inputs_to_update_in_subsequent_elimination_rounds(
3838
]
3939
original_inputs = updated_inputs.copy()
4040

41-
if subsequent_match.stage_item_input1_winner_from_match_id in affected_matches:
42-
updated_inputs[0] = affected_matches[
41+
if subsequent_match.stage_item_input1_winner_from_match_id is not None and (
42+
affected_match1 := affected_matches.get(
4343
subsequent_match.stage_item_input1_winner_from_match_id
44-
].get_winner()
44+
)
45+
):
46+
updated_inputs[0] = affected_match1.get_winner()
4547

46-
if subsequent_match.stage_item_input2_winner_from_match_id in affected_matches:
47-
updated_inputs[1] = affected_matches[
48+
if subsequent_match.stage_item_input2_winner_from_match_id is not None and (
49+
affected_match2 := affected_matches.get(
4850
subsequent_match.stage_item_input2_winner_from_match_id
49-
].get_winner()
51+
)
52+
):
53+
updated_inputs[1] = affected_match2.get_winner()
5054

5155
if original_inputs != updated_inputs:
5256
input_ids = [input_.id if input_ else None for input_ in updated_inputs]

backend/bracket/models/db/match.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def get_match_hash(
6767

6868

6969
class MatchWithDetailsDefinitive(Match):
70-
stage_item_input1: StageItemInput
71-
stage_item_input2: StageItemInput
70+
stage_item_input1: StageItemInput # pyrefly: ignore [bad-override]
71+
stage_item_input2: StageItemInput # pyrefly: ignore [bad-override]
7272
court: Court | None = None
7373

7474
@property

backend/bracket/models/db/stage_item.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,18 @@ class StageItemCreateBody(BaseModelORM):
5050
ranking_id: RankingId | None = None
5151

5252
def get_name_or_default_name(self) -> str:
53-
return self.name if self.name is not None else self.type.value.replace("_", " ").title()
53+
return (
54+
self.name if self.name is not None else str(self.type.value).replace("_", " ").title()
55+
)
5456

5557

5658
class StageItemWithInputsCreate(StageItemCreateBody):
5759
inputs: list[StageItemInputCreateBody]
5860

5961
def get_name_or_default_name(self) -> str:
60-
return self.name if self.name is not None else self.type.value.replace("_", " ").title()
62+
return (
63+
self.name if self.name is not None else str(self.type.value).replace("_", " ").title()
64+
)
6165

6266
@model_validator(mode="before")
6367
def handle_inputs_length(cls, values: Any) -> Any:

backend/bracket/models/db/stage_item_inputs.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
from decimal import Decimal
24

35
from pydantic import BaseModel, Field
@@ -15,9 +17,6 @@ class StageItemInputBase(BaseModelORM):
1517

1618

1719
class StageItemInputGeneric(BaseModel):
18-
team_id: TeamId | None = None
19-
winner_from_stage_item_id: StageItemId | None = None
20-
winner_position: int | None = None
2120
points: Decimal = Decimal("0.0")
2221
wins: int = 0
2322
draws: int = 0
@@ -30,12 +29,15 @@ def elo(self) -> Decimal:
3029
"""
3130
return self.points
3231

33-
def __hash__(self) -> int:
34-
return (
35-
self.team_id,
36-
self.winner_from_stage_item_id,
37-
self.winner_position,
38-
).__hash__()
32+
33+
def hash_stage_item_input(
34+
self: StageItemInputTentative | StageItemInputFinal | StageItemInputEmpty,
35+
) -> int:
36+
return (
37+
self.team_id,
38+
self.winner_from_stage_item_id,
39+
self.winner_position,
40+
).__hash__()
3941

4042

4143
class StageItemInputTentative(StageItemInputBase, StageItemInputGeneric):
@@ -46,17 +48,25 @@ class StageItemInputTentative(StageItemInputBase, StageItemInputGeneric):
4648
def get_lookup_key(self) -> tuple[StageItemId, int]:
4749
return self.winner_from_stage_item_id, self.winner_position
4850

51+
__hash__ = hash_stage_item_input
52+
4953

5054
class StageItemInputFinal(StageItemInputBase, StageItemInputGeneric):
5155
team_id: TeamId
56+
winner_from_stage_item_id: StageItemId | None = None
57+
winner_position: int | None = None
5258
team: Team
5359

60+
__hash__ = hash_stage_item_input
61+
5462

5563
class StageItemInputEmpty(StageItemInputBase, StageItemInputGeneric):
5664
team_id: None = None
5765
winner_from_stage_item_id: None = None
5866
winner_position: None = None
5967

68+
__hash__ = hash_stage_item_input
69+
6070

6171
StageItemInput = StageItemInputTentative | StageItemInputFinal | StageItemInputEmpty
6272

backend/bracket/models/db/team.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def player_ids(self) -> list[PlayerId]:
4646
return [player.id for player in self.players]
4747

4848
@field_validator("players", mode="before")
49-
def handle_players(values: list[Player]) -> list[Player]: # type: ignore[misc]
49+
@staticmethod
50+
def handle_players(values: list[Player]) -> list[Player]:
5051
if isinstance(values, str):
5152
values_json = json.loads(values)
5253
if values_json == [None]:

backend/bracket/models/db/user.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Annotated
44

55
from heliclockter import datetime_utc
6-
from pydantic import BaseModel, constr
6+
from pydantic import BaseModel, StringConstraints
77

88
from bracket.models.db.account import UserAccountType
99
from bracket.models.db.shared import BaseModelORM
@@ -30,8 +30,9 @@ class UserInsertable(UserBase):
3030
password_hash: str | None = None
3131

3232

33-
class User(UserInsertable):
33+
class User(UserBase):
3434
id: UserId
35+
password_hash: str | None = None
3536

3637

3738
class UserPublic(UserBase):
@@ -44,7 +45,7 @@ class UserToUpdate(BaseModel):
4445

4546

4647
class UserPasswordToUpdate(BaseModel):
47-
password: constr(min_length=8, max_length=48) # type: ignore[valid-type]
48+
password: Annotated[str, StringConstraints(min_length=8, max_length=48)]
4849

4950

5051
class DemoUserToRegister(BaseModelORM):
@@ -58,6 +59,6 @@ class UserToRegister(BaseModelORM):
5859
captcha_token: str
5960

6061

61-
class UserInDB(User):
62+
class UserInDB(UserBase):
6263
id: UserId
6364
password_hash: str

0 commit comments

Comments
 (0)