Skip to content

Commit

Permalink
chore: add additional lint rules
Browse files Browse the repository at this point in the history
  • Loading branch information
brownben committed Apr 10, 2024
1 parent 177f5cd commit 262078d
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 149 deletions.
20 changes: 16 additions & 4 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ source = ["src"]
omit = [".venv/*", "*/test*", "*/*database_conf.py"]
exclude_lines = ["coverage: ignore"]

[tool.isort]
profile = "black"

[tool.mypy]
strict = true
plugins = "pydantic.mypy"
Expand All @@ -54,5 +51,20 @@ files = '**/*.py'
explicit_package_bases = true

[tool.ruff.lint]
ignore = ["E501"]
ignore = [
"E501", # Line too long
"B904", # Raise inside except
]
per-file-ignores = { "./src/database/*"=["E712"] }

select = [
"F", # Pyflakes
"E", # pycodestyle (errors)
"W", # pycodestyle (warnings)
"UP", # pyupgrade
"B", # flake8-bugbear
"SIM", # flake8-simplify
"I", # isort
"PERF", # Performnce
"RUF", # Ruff
]
4 changes: 2 additions & 2 deletions backend/src/database/results.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Iterable, Optional
from typing import Iterable, Iterator, Optional

from ..schemas import EventResult, Result, ResultBeforeDatabase, ResultWithEventName
from .tables import Result as ResultTable
Expand Down Expand Up @@ -102,7 +102,7 @@ async def get_by_event_and_courses(
)

@staticmethod
async def get_by_event(event: str) -> Iterable[Result]:
async def get_by_event(event: str) -> Iterator[Result]:
return (
Result.parse_obj(result)
for result in await ResultTable.select(*results_fields)
Expand Down
16 changes: 9 additions & 7 deletions backend/src/routes/leagues.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
assign_position_based_on_time,
)
from ..utils.club import club_matches_filter
from ..utils.counting_results import find_counting_results
from ..utils.counting_results import counting_results_finder
from ..utils.dynamic_results import DYNAMIC_RESULT_TYPES, calculate_dynamic_results
from ..utils.points_calculators import get_matching_points_calculator
from .authentication import require_authentication
Expand Down Expand Up @@ -232,16 +232,18 @@ async def get_league_results(
type=result.type,
)

find_counting_results = counting_results_finder(
league, events_with_results, league_groups, league_class
)
for competitor in league_results.values():
competitor = calculate_dynamic_results(competitor)
counting_points = find_counting_results(
competitor.points, league, events_with_results, league_groups, league_class
)
for point in competitor.points:
league_result = calculate_dynamic_results(competitor)
counting_points = find_counting_results(league_result.points)

for point in league_result.points:
if point:
point.counting = point in counting_points
if point and point.counting:
competitor.total_points += point.score
league_result.total_points += point.score

league_points_calculator = get_matching_points_calculator(league.scoring_method)
results = [
Expand Down
4 changes: 2 additions & 2 deletions backend/src/routes/tests/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ async def test_result_created_competitor_exists(self) -> None:
response.json(), {"detail": "Result created for `Sophie Glider`"}
)

result = list(await Results.get_by_event("TestEvent-2022-02-03"))[0]
result = next(await Results.get_by_event("TestEvent-2022-02-03"))
self.assertEqual(result.time, 712)
self.assertEqual(result.incomplete, False)
self.assertEqual(result.course, "Long")
Expand Down Expand Up @@ -238,7 +238,7 @@ async def test_result_created_new_competitor(self) -> None:
self.assertEqual(new_competitor.competitor_pool, "Edinburgh Summer 2021")
self.assertEqual(new_competitor.age_class, "M21")

new_result = list(await Results.get_by_event("TestEvent-2022-02-03"))[0]
new_result = next(await Results.get_by_event("TestEvent-2022-02-03"))
self.assertEqual(new_result.time, 832)
self.assertEqual(new_result.incomplete, False)
self.assertEqual(new_result.course, "Long")
Expand Down
3 changes: 2 additions & 1 deletion backend/src/routes/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
UploadURLRequest,
)
from ..utils.get_results import get_document_from_url
from ..utils.import_file import ImportException, import_results_from_file
from ..utils.import_file import ImportException as ImportException
from ..utils.import_file import import_results_from_file
from ..utils.match_results import match_result_to_competitor
from ..utils.times import parse_time

Expand Down
30 changes: 15 additions & 15 deletions backend/src/types/asyncpg.pyi
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
from typing import Any, Iterator, List, Optional, Tuple, TypeVar, Union, overload
from typing import Any, Iterator, TypeVar, overload

_T = TypeVar("_T")

class Record:
@overload
def get(self, key: str) -> Optional[Any]: ...
def get(self, key: str) -> Any | None: ...
@overload
def get(self, key: str, default: _T) -> Union[Any, _T]: ...
def items(self) -> Iterator[Tuple[str, Any]]: ...
def get(self, key: str, default: _T) -> Any | _T: ...
def items(self) -> Iterator[tuple[str, Any]]: ...
def keys(self) -> Iterator[str]: ...
def values(self) -> Iterator[Any]: ...
@overload
def __getitem__(self, index: str) -> Any: ...
@overload
def __getitem__(self, index: int) -> Any: ...
@overload
def __getitem__(self, index: slice) -> Tuple[Any, ...]: ...
def __getitem__(self, index: slice) -> tuple[Any, ...]: ...
def __iter__(self) -> Iterator[Any]: ...
def __contains__(self, x: object) -> bool: ...
def __len__(self) -> int: ...
Expand All @@ -26,25 +26,25 @@ class ConnectionMeta(type):
class Connection(metaclass=ConnectionMeta):
def __del__(self) -> None: ...
async def execute(
self, query: str, *args: Any, timeout: Optional[float] = ...
self, query: str, *args: Any, timeout: float | None = ...
) -> str: ...
async def fetch(
self,
query: str,
*args: Any,
timeout: Optional[float] = ...,
record_class: Optional[Record] = ...,
) -> List[Record]: ...
timeout: float | None = ...,
record_class: Record | None = ...,
) -> list[Record]: ...
async def fetchval(
self, query: str, *args: Any, column: int = ..., timeout: Optional[float] = ...
) -> Optional[Any]: ...
self, query: str, *args: Any, column: int = ..., timeout: float | None = ...
) -> Any | None: ...
async def fetchrow(
self,
query: str,
*args: Any,
timeout: Optional[float] = ...,
record_class: Optional[Record] = ...,
) -> Optional[Record]: ...
async def close(self, *, timeout: Optional[float] = ...) -> None: ...
timeout: float | None = ...,
record_class: Record | None = ...,
) -> Record | None: ...
async def close(self, *, timeout: float | None = ...) -> None: ...

async def connect(connection_string: str) -> Connection: ...
6 changes: 3 additions & 3 deletions backend/src/types/google/auth/transport/requests.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Mapping, Optional
from typing import Any, Mapping

class _Response:
"""Requests transport response adapter.
Expand Down Expand Up @@ -42,8 +42,8 @@ class Request:
self,
url: str,
method: str = "GET",
body: Optional[bytes] = None,
headers: Optional[Mapping[str, str]] = None,
body: bytes | None = None,
headers: Mapping[str, str] | None = None,
timeout: int = 120,
**kwargs: Any,
) -> _Response:
Expand Down
4 changes: 2 additions & 2 deletions backend/src/types/google/oauth2/id_token.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any, Mapping, Optional, Union
from typing import Any, Mapping

from google.auth.transport.requests import Request

def verify_firebase_token(
id_token: Union[str, bytes], request: Request, audience: Optional[str] = None
id_token: str | bytes, request: Request, audience: str | None = None
) -> Mapping[str, Any]:
"""Verifies an ID Token issued by Firebase Authentication.
Expand Down
14 changes: 4 additions & 10 deletions backend/src/utils/age_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ def is_age_eligible() -> bool:
return age <= specified_age

def is_gender_eligible() -> bool:
if gender == specified_gender:
return True

# Women are also eligible for the Mens/Open
elif gender == "W":
# either gender matches, or is a women and is eligible for mens/open
if gender in (specified_gender, "W"):
return True

return False
Expand Down Expand Up @@ -122,11 +119,8 @@ def is_age_eligible() -> bool:
return age <= specified_age

def is_gender_eligible() -> bool:
if gender == specified_gender:
return True

# Women are also eligible for the Mens/Open
elif gender == "W":
# either gender matches, or is a women and is eligible for mens/open
if gender in (specified_gender, "W"):
return True

return False
Expand Down
105 changes: 52 additions & 53 deletions backend/src/utils/counting_results.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,74 @@
from itertools import groupby
from typing import Iterable, Optional, Set
from typing import Callable, Iterable, Optional, Set

from ..schemas import League, LeagueClass, LeagueEvent
from ..schemas import LeagueResultScore as Result
from ..utils.points_calculators import get_matching_points_calculator


def find_counting_results(
results: Iterable[Optional[Result]],
def counting_results_finder(
league: League,
events: list[LeagueEvent] = [],
league_groups: dict[int, tuple[int, int]] = {},
league_class: Optional[LeagueClass] = None,
) -> Set[Result]:
"""Returns the results that are used as their counting scores when calculating their total points"""

if league_class is not None:
max_number_of_counting_results = (
league_class.number_of_counting_events or league.number_of_counting_events
)
else:
max_number_of_counting_results = league.number_of_counting_events

events: list[LeagueEvent],
league_groups: dict[int, tuple[int, int]],
league_class: LeagueClass,
) -> Callable[[Iterable[Optional[Result]]], Set[Result]]:
number_of_counting_events = (
league_class.number_of_counting_events or league.number_of_counting_events
)
league_points_calculator = get_matching_points_calculator(league.scoring_method)

remaining_results: Set[Result] = set(result for result in results if result)
counting_results: Set[Result] = set()

# If any results are marked as compulsory they will always be included in the total
compulsory_events = {event.event for event in events if event.compulsory}
for result in remaining_results:
if result.event in compulsory_events:
counting_results.add(result)
remaining_results.difference_update(counting_results)

# If they haven't got a score for a compulsory event, they can't use another result
if len(counting_results) < len(compulsory_events):
max_number_of_counting_results -= len(compulsory_events) - len(counting_results)

# If a subset of a league (a group) has a required minimum/ maximum number of events counting
groups: dict[int, list[str]] = {
group_name: [event.event for event in events_in_group]
for group_name, events_in_group in groupby(events, lambda x: x.league_group)
if group_name
}
for group_name, group_events in groups.items():
group_min, group_max = league_groups[group_name]

results_in_group = {
result for result in remaining_results if result.event in group_events
}
sorted_results_in_group = sorted(
results_in_group, key=lambda x: x.score, reverse=True
)
def find_counting_results(results: Iterable[Optional[Result]]) -> Set[Result]:
"""Returns the results that are used as their counting scores when calculating their total points"""

# Add the top results up to the minimum counting
counting_results.update(sorted_results_in_group[:group_min])
remaining_results: Set[Result] = set(result for result in results if result)
counting_results: Set[Result] = set()
number_of_counting_results = number_of_counting_events

# If any results are marked as compulsory they will always be included in the total
for result in remaining_results:
if result.event in compulsory_events:
counting_results.add(result)
remaining_results.difference_update(counting_results)

# Remove the bottom results which included would take the number over the maximum
if group_max and group_max > 0:
remaining_results.difference_update(sorted_results_in_group[group_max:])
# If they haven't got a score for a compulsory event, they can't use another result
if len(counting_results) < len(compulsory_events):
number_of_counting_results -= len(compulsory_events) - len(counting_results)

# Then add the largest results left until the max number is reached
number_of_results_left = max_number_of_counting_results - len(counting_results)
sorted_results = sorted(
remaining_results,
key=lambda x: x.score,
reverse=league_points_calculator.best_points_is_max,
)
# If a subset of a league (a group) has a required minimum/ maximum number of events counting
for group_name, group_events in groups.items():
group_min, group_max = league_groups[group_name]

results_in_group = {
result for result in remaining_results if result.event in group_events
}
sorted_results_in_group = sorted(
results_in_group, key=lambda x: x.score, reverse=True
)

# Add the top results up to the minimum counting
counting_results.update(sorted_results_in_group[:group_min])
remaining_results.difference_update(counting_results)

# Remove the bottom results which included would take the number over the maximum
if group_max and group_max > 0:
remaining_results.difference_update(sorted_results_in_group[group_max:])

# Then add the largest results left until the max number is reached
number_of_results_left = number_of_counting_results - len(counting_results)
sorted_results = sorted(
remaining_results,
key=lambda x: x.score,
reverse=league_points_calculator.best_points_is_max,
)

counting_results.update(sorted_results[:number_of_results_left])

counting_results.update(sorted_results[:number_of_results_left])
return counting_results

return counting_results
return find_counting_results
4 changes: 2 additions & 2 deletions backend/src/utils/import_file/import_sitiming_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ def process_sitiming_html_file(file: str) -> Iterator[ImportedRecord]:
raise ImportException("Expected at least 1 course")

data_section = re.search(
"function getData\(tableNumber\) \{(.*);\}<\/script>", file
"function getData\\(tableNumber\\) \\{(.*);\\}<\\/script>", file
)

if not data_section:
raise ImportException("Expected file to contain results data")

data = data_section.group(1)
course_sections = re.split("if \(tableNumber == [0-9]+\) return ", data)
course_sections = re.split("if \\(tableNumber == [0-9]+\\) return ", data)

course_results = (
json.loads(
Expand Down
6 changes: 2 additions & 4 deletions backend/src/utils/import_file/tests/test_import_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,8 @@ def test_one_partial_result(self) -> None:
def test_one_full_result(self) -> None:
self.assertEqual(
process_csv_file(
(
"firstname; surname; club; ageclass; status; nonCompetitive; course; time; position; points\n"
"John; Doe; HAT; M16; 1; N; Long; 14:54; 1st; 1000"
)
"firstname; surname; club; ageclass; status; nonCompetitive; course; time; position; points\n"
"John; Doe; HAT; M16; 1; N; Long; 14:54; 1st; 1000"
),
[
{
Expand Down
Loading

0 comments on commit 262078d

Please sign in to comment.