From 0964f2e7e98bbdfe17d5e71f219fdcf902bc5c68 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon, 20 Apr 2026 01:13:06 +0200 Subject: [PATCH 1/5] ruff==0.15.10 --- pyproject.toml | 1 - requirements-dev.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e5fd1bb0a5..16d5d0aeb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ minimal-titan-version-code = 197 # Same as Black. line-length = 88 indent-width = 4 -target-version = "py314" [tool.ruff.lint] select = [ diff --git a/requirements-dev.txt b/requirements-dev.txt index 1e386820af..7a23e09373 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ pytest-asyncio==1.3.0 pytest-cov==7.1.0 pytest-mock==3.15.1 pytest==9.0.3 -ruff==0.11.8 +ruff==0.15.10 types-Authlib==1.6.11.20260418 types-fpdf2==2.8.4.20260408 types-psutil==7.2.2.20260408 From 2b0dff695085eabd5e2fdbeb0a3ecc0c0924b680 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon, 20 Apr 2026 01:13:30 +0200 Subject: [PATCH 2/5] Format --- app/core/checkout/endpoints_checkout.py | 8 +++-- app/core/checkout/payment_tool.py | 2 +- app/core/users/factory_users.py | 36 ++++++++++------------ app/core/utils/config.py | 2 +- app/modules/booking/endpoints_booking.py | 4 +-- app/modules/calendar/endpoints_calendar.py | 8 ++--- app/modules/raid/cruds_raid.py | 8 +++-- app/utils/auth/auth_utils.py | 2 +- tests/core/test_users.py | 6 ++-- tests_script.py | 2 +- 10 files changed, 38 insertions(+), 40 deletions(-) diff --git a/app/core/checkout/endpoints_checkout.py b/app/core/checkout/endpoints_checkout.py index b14b0e1daa..84af7002b2 100644 --- a/app/core/checkout/endpoints_checkout.py +++ b/app/core/checkout/endpoints_checkout.py @@ -73,9 +73,11 @@ async def webhook( ): # We may receive the webhook multiple times, we only want to save a CheckoutPayment # in the database the first time - existing_checkout_payment_model = await cruds_checkout.get_checkout_payment_by_hello_asso_payment_id( - hello_asso_payment_id=content.data.id, # ty:ignore[unresolved-attribute] - db=db, + existing_checkout_payment_model = ( + await cruds_checkout.get_checkout_payment_by_hello_asso_payment_id( + hello_asso_payment_id=content.data.id, # ty:ignore[unresolved-attribute] + db=db, + ) ) if existing_checkout_payment_model is not None: hyperion_error_logger.debug( diff --git a/app/core/checkout/payment_tool.py b/app/core/checkout/payment_tool.py index c75629f9d4..fb033e8e85 100644 --- a/app/core/checkout/payment_tool.py +++ b/app/core/checkout/payment_tool.py @@ -203,7 +203,7 @@ async def init_checkout( self._helloasso_slug, init_checkout_body, ) - except (UnauthorizedException, BadRequestException): + except UnauthorizedException, BadRequestException: # We know that HelloAsso may refuse some payer infos, like using the firstname "test" # Even when prefilling the payer infos,the user will be able to edit them on the payment page, # so we can safely retry without the payer infos diff --git a/app/core/users/factory_users.py b/app/core/users/factory_users.py index 1c97a46cb2..db97dd28b4 100644 --- a/app/core/users/factory_users.py +++ b/app/core/users/factory_users.py @@ -32,26 +32,22 @@ class CoreUsersFactory(Factory): @classmethod def init_demo_users(cls, settings: Settings) -> None: - cls.demo_users = ( - settings.FACTORIES_DEMO_USERS - if settings.FACTORIES_DEMO_USERS - else [ - UserDemoFactoryConfig( - firstname="Alice", - name="Dupont", - nickname="alice", - email="demo1@test.fr", - password=Faker().password(16, True, True, True, True), - ), - UserDemoFactoryConfig( - firstname="Bob", - name="Martin", - nickname="bob", - email="demo2@test.fr", - password=Faker().password(16, True, True, True, True), - ), - ] - ) + cls.demo_users = settings.FACTORIES_DEMO_USERS or [ + UserDemoFactoryConfig( + firstname="Alice", + name="Dupont", + nickname="alice", + email="demo1@test.fr", + password=Faker().password(16, True, True, True, True), + ), + UserDemoFactoryConfig( + firstname="Bob", + name="Martin", + nickname="bob", + email="demo2@test.fr", + password=Faker().password(16, True, True, True, True), + ), + ] cls.demo_users_id = [str(uuid.uuid4()) for _ in cls.demo_users] @classmethod diff --git a/app/core/utils/config.py b/app/core/utils/config.py index 6010717b73..6f65a16f2d 100644 --- a/app/core/utils/config.py +++ b/app/core/utils/config.py @@ -1,10 +1,10 @@ import pathlib +import tomllib from functools import cached_property from re import Pattern from typing import Any, ClassVar import jwt -import tomllib from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.serialization import load_pem_private_key from pydantic import BaseModel, computed_field, model_validator diff --git a/app/modules/booking/endpoints_booking.py b/app/modules/booking/endpoints_booking.py index 5db723bfba..3f6b4279a7 100644 --- a/app/modules/booking/endpoints_booking.py +++ b/app/modules/booking/endpoints_booking.py @@ -1,10 +1,10 @@ import logging import uuid from datetime import UTC, datetime +from zoneinfo import ZoneInfo from fastapi import Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession -from zoneinfo import ZoneInfo from app.core.groups import cruds_groups from app.core.groups.groups_type import AccountType @@ -302,7 +302,7 @@ async def create_booking( group = await cruds_groups.get_group_by_id(group_id=manager.group_id, db=db) local_start = result.start.astimezone(ZoneInfo("Europe/Paris")) - applicant_nickname = user.nickname if user.nickname else user.firstname + applicant_nickname = user.nickname or user.firstname content = f"{applicant_nickname} - {result.room.name} {local_start.strftime('%m/%d/%Y, %H:%M')} - {result.reason}" # Setting time to Paris timezone in order to have the correct time in the notification diff --git a/app/modules/calendar/endpoints_calendar.py b/app/modules/calendar/endpoints_calendar.py index 3509318cc7..e61a74a797 100644 --- a/app/modules/calendar/endpoints_calendar.py +++ b/app/modules/calendar/endpoints_calendar.py @@ -365,9 +365,7 @@ async def add_event( if decision == Decision.approved: feed_module = "tickets" if event.ticket_event_id else utils_calendar.root - feed_module_object_id = ( - event.ticket_event_id if event.ticket_event_id else event_id - ) + feed_module_object_id = event.ticket_event_id or event_id await utils_calendar.add_event_to_feed( event=created_event, @@ -524,9 +522,7 @@ async def confirm_event( if decision == Decision.approved: feed_module = "tickets" if event.ticket_event_id else utils_calendar.root - feed_module_object_id = ( - event.ticket_event_id if event.ticket_event_id else event.id - ) + feed_module_object_id = event.ticket_event_id or event.id await utils_calendar.add_event_to_feed( event=event, diff --git a/app/modules/raid/cruds_raid.py b/app/modules/raid/cruds_raid.py index 8a2c8ea40d..7a464befd4 100644 --- a/app/modules/raid/cruds_raid.py +++ b/app/modules/raid/cruds_raid.py @@ -563,9 +563,11 @@ async def get_number_of_team_by_difficulty( team_numbers = [ team.number if team.number is not None and team.number >= 0 else 0 for team in filter( - lambda team: team.validation_progress == 100 - and team.number is not None - and team.number >= 0, + lambda team: ( + team.validation_progress == 100 + and team.number is not None + and team.number >= 0 + ), teams_found, ) ] diff --git a/app/utils/auth/auth_utils.py b/app/utils/auth/auth_utils.py index 3948f70e32..532a4a439f 100644 --- a/app/utils/auth/auth_utils.py +++ b/app/utils/auth/auth_utils.py @@ -31,7 +31,7 @@ def get_token_data( hyperion_access_logger.info( f"Get_token_data: Decoded a token for user {token_data.sub} ({request_id})", ) - except (InvalidTokenError, ValidationError): + except InvalidTokenError, ValidationError: hyperion_access_logger.exception( f"Get_token_data: Failed to decode a token ({request_id})", ) diff --git a/tests/core/test_users.py b/tests/core/test_users.py index c7b039b512..fce332d8ec 100644 --- a/tests/core/test_users.py +++ b/tests/core/test_users.py @@ -235,7 +235,7 @@ def test_create_and_activate_user( "activation_token": UNIQUE_TOKEN, "password": "password", "firstname": "firstname", - "name": email.split("@")[0], + "name": email.split("@", maxsplit=1)[0], "nickname": "nickname", "floor": "X1", }, @@ -247,7 +247,9 @@ def test_create_and_activate_user( "/users/", headers={"Authorization": f"Bearer {token_admin_user}"}, ) - user = next(user for user in users.json() if user["name"] == email.split("@")[0]) + user = next( + user for user in users.json() if user["name"] == email.split("@", maxsplit=1)[0] + ) assert user is not None assert user["account_type"] == expected_account_type.value diff --git a/tests_script.py b/tests_script.py index f911da8e99..3ce1a394ec 100644 --- a/tests_script.py +++ b/tests_script.py @@ -22,7 +22,7 @@ def get_changed_files(): """Enumerate files changed compared to main branch.""" # We use git diff to get the list of changed files with three dots (...) to compare with the base commit of the PR in the main branch - diff = subprocess.check_output( # noqa: S603 + diff = subprocess.check_output( ["git", "diff", "--name-only", "origin/main..."], # noqa: S607 text=True, ).strip() From 15752fa131fdb28e3136d01023eb8fd8bbc22446 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon, 20 Apr 2026 01:15:42 +0200 Subject: [PATCH 3/5] Lint --- app/core/feed/types_feed.py | 4 ++-- app/core/google_api/google_api.py | 3 ++- app/core/permissions/endpoints_permissions.py | 6 +----- app/core/permissions/type_permissions.py | 4 ++-- app/core/tickets/types_tickets.py | 4 ++-- app/core/utils/log.py | 4 ++-- app/modules/amap/types_amap.py | 6 +++--- app/modules/booking/types_booking.py | 4 ++-- app/modules/calendar/types_calendar.py | 4 ++-- app/modules/campaign/types_campaign.py | 6 +++--- app/modules/cdr/types_cdr.py | 10 +++++----- app/modules/raffle/types_raffle.py | 4 ++-- app/modules/raid/raid_type.py | 14 +++++++------- app/types/content_type.py | 6 +++--- app/types/scopes_type.py | 4 ++-- app/types/websocket.py | 6 +++--- app/utils/initialization.py | 4 ++-- app/utils/tools.py | 4 ++-- migrations/versions/21-fix_phonebook.py | 2 +- migrations/versions/28-booking_type.py | 4 ++-- migrations/versions/29-hardcode-BDS-group.py | 4 ++-- migrations/versions/30-membership.py | 6 +++--- migrations/versions/32-MyECLPay.py | 14 +++++++------- migrations/versions/35-topic-membership.py | 4 ++-- migrations/versions/41-feed.py | 4 ++-- migrations/versions/46-remove_hardcoded_floor.py | 4 ++-- migrations/versions/61-permissions.py | 4 ++-- tests/core/test_utils.py | 2 +- tests/modules/test_raid.py | 6 +----- 29 files changed, 72 insertions(+), 79 deletions(-) diff --git a/app/core/feed/types_feed.py b/app/core/feed/types_feed.py index 5a0ba13088..51f60835ac 100644 --- a/app/core/feed/types_feed.py +++ b/app/core/feed/types_feed.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class NewsStatus(str, Enum): +class NewsStatus(StrEnum): WAITING_APPROVAL = "waiting_approval" REJECTED = "rejected" PUBLISHED = "published" diff --git a/app/core/google_api/google_api.py b/app/core/google_api/google_api.py index 2d3517f1b9..0575ef9e95 100644 --- a/app/core/google_api/google_api.py +++ b/app/core/google_api/google_api.py @@ -1,5 +1,6 @@ import logging from types import TracebackType +from typing import Self from fastapi import HTTPException, Request from google.auth.transport import requests @@ -217,7 +218,7 @@ def __init__(self, db: AsyncSession, settings: Settings): self._db = db self._settings = settings - async def __aenter__(self) -> "DriveGoogleAPI": + async def __aenter__(self) -> Self: google_api = GoogleAPI() creds = await google_api.get_credentials(self._db, self._settings) self._drive: Resource = build("drive", "v3", credentials=creds) diff --git a/app/core/permissions/endpoints_permissions.py b/app/core/permissions/endpoints_permissions.py index a1181e0619..ea2708f9b3 100644 --- a/app/core/permissions/endpoints_permissions.py +++ b/app/core/permissions/endpoints_permissions.py @@ -16,6 +16,7 @@ is_user, is_user_super_admin, ) +from app.module import full_name_permissions_list, permissions_list from app.types.module import CoreModule from app.utils.tools import is_group_id_valid @@ -45,7 +46,6 @@ async def read_permissions_list( """ Return all permissions from database """ - from app.module import full_name_permissions_list return full_name_permissions_list @@ -62,7 +62,6 @@ async def read_permissions( """ Return all permissions from database """ - from app.module import permissions_list return await cruds_permissions.get_permissions(permissions_list, db) @@ -80,7 +79,6 @@ async def read_permission( """ Return permission with name from database """ - from app.module import permissions_list if permission_name not in permissions_list: raise HTTPException( @@ -111,7 +109,6 @@ async def create_permission( """ Create a new permission in database """ - from app.module import permissions_list if permission.permission_name not in permissions_list: raise HTTPException( @@ -143,7 +140,6 @@ async def delete_permission( """ Delete a permission from database by name """ - from app.module import permissions_list if permission.permission_name not in permissions_list: raise HTTPException( diff --git a/app/core/permissions/type_permissions.py b/app/core/permissions/type_permissions.py index 3dc5e83dd6..e138c75ce7 100644 --- a/app/core/permissions/type_permissions.py +++ b/app/core/permissions/type_permissions.py @@ -1,5 +1,5 @@ -from enum import Enum +from enum import StrEnum -class ModulePermissions(str, Enum): +class ModulePermissions(StrEnum): pass diff --git a/app/core/tickets/types_tickets.py b/app/core/tickets/types_tickets.py index 69f3e55229..19eeb7465d 100644 --- a/app/core/tickets/types_tickets.py +++ b/app/core/tickets/types_tickets.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class AnswerType(str, Enum): +class AnswerType(StrEnum): TEXT = "text" NUMBER = "number" BOOLEAN = "boolean" diff --git a/app/core/utils/log.py b/app/core/utils/log.py index 90c347da43..aa23ad8751 100644 --- a/app/core/utils/log.py +++ b/app/core/utils/log.py @@ -1,7 +1,7 @@ import logging import logging.config import queue -from enum import Enum +from enum import StrEnum from logging.handlers import QueueHandler, QueueListener from pathlib import Path from typing import Any @@ -12,7 +12,7 @@ class ColoredConsoleFormatter(uvicorn.logging.DefaultFormatter): - class ConsoleColors(str, Enum): + class ConsoleColors(StrEnum): """Colors can be found here: https://talyian.github.io/ansicolors/""" DEBUG = "\033[38;5;12m" diff --git a/app/modules/amap/types_amap.py b/app/modules/amap/types_amap.py index e08f6d901d..5265133a8f 100644 --- a/app/modules/amap/types_amap.py +++ b/app/modules/amap/types_amap.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class AmapSlotType(str, Enum): +class AmapSlotType(StrEnum): midi = "midi" soir = "soir" @@ -9,7 +9,7 @@ def __str__(self) -> str: return f"{self.name}<{self.value}" -class DeliveryStatusType(str, Enum): +class DeliveryStatusType(StrEnum): creation = "creation" # Can edit date, add and remove products, no order possible orderable = "orderable" # Ordering is possible, no edition possible locked = "locked" # Can't order diff --git a/app/modules/booking/types_booking.py b/app/modules/booking/types_booking.py index e8f2187c17..aab919c13b 100644 --- a/app/modules/booking/types_booking.py +++ b/app/modules/booking/types_booking.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class Decision(str, Enum): +class Decision(StrEnum): approved = "approved" declined = "declined" pending = "pending" diff --git a/app/modules/calendar/types_calendar.py b/app/modules/calendar/types_calendar.py index e8f2187c17..aab919c13b 100644 --- a/app/modules/calendar/types_calendar.py +++ b/app/modules/calendar/types_calendar.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class Decision(str, Enum): +class Decision(StrEnum): approved = "approved" declined = "declined" pending = "pending" diff --git a/app/modules/campaign/types_campaign.py b/app/modules/campaign/types_campaign.py index 825c79b374..4ade76ffac 100644 --- a/app/modules/campaign/types_campaign.py +++ b/app/modules/campaign/types_campaign.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class ListType(str, Enum): +class ListType(StrEnum): """ A list can be "Serios" or "Pipo". There will also be one "Blank" list by section that will be automatically added when the vote is open. """ @@ -11,7 +11,7 @@ class ListType(str, Enum): blank = "Blank" -class StatusType(str, Enum): +class StatusType(StrEnum): """ Status of the voting """ diff --git a/app/modules/cdr/types_cdr.py b/app/modules/cdr/types_cdr.py index dabe756973..f0dc4bc028 100644 --- a/app/modules/cdr/types_cdr.py +++ b/app/modules/cdr/types_cdr.py @@ -1,12 +1,12 @@ -from enum import Enum +from enum import StrEnum -class DocumentSignatureType(str, Enum): +class DocumentSignatureType(StrEnum): material = "material" numeric = "numeric" -class PaymentType(str, Enum): +class PaymentType(StrEnum): cash = "cash" check = "check" helloasso = "HelloAsso" @@ -14,14 +14,14 @@ class PaymentType(str, Enum): archived = "archived" -class CdrStatus(str, Enum): +class CdrStatus(StrEnum): pending = "pending" online = "online" onsite = "onsite" closed = "closed" -class CdrLogActionType(str, Enum): +class CdrLogActionType(StrEnum): purchase_add = "purchase_add" purchase_delete = "purchase_delete" payment_add = "payment_add" diff --git a/app/modules/raffle/types_raffle.py b/app/modules/raffle/types_raffle.py index 0349f67d44..35e5357996 100644 --- a/app/modules/raffle/types_raffle.py +++ b/app/modules/raffle/types_raffle.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class RaffleStatusType(str, Enum): +class RaffleStatusType(StrEnum): creation = "creation" # Can edit every parameter open = "open" # Ordering is possible, no edition possible lock = "lock" # Can't order diff --git a/app/modules/raid/raid_type.py b/app/modules/raid/raid_type.py index d15b19ff36..d611ada07b 100644 --- a/app/modules/raid/raid_type.py +++ b/app/modules/raid/raid_type.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class DocumentType(str, Enum): +class DocumentType(StrEnum): idCard = "idCard" # the id card of the participant medicalCertificate = ( "medicalCertificate" # the medical certificate of the participant @@ -11,7 +11,7 @@ class DocumentType(str, Enum): parentAuthorization = "parentAuthorization" # the parent authorization -class Size(str, Enum): # for the T-shirt and the bike +class Size(StrEnum): # for the T-shirt and the bike XS = "XS" S = "S" M = "M" @@ -20,26 +20,26 @@ class Size(str, Enum): # for the T-shirt and the bike None_ = "None" -class MeetingPlace(str, Enum): # place of meeting for the raid +class MeetingPlace(StrEnum): # place of meeting for the raid centrale = "centrale" bellecour = "bellecour" anyway = "anyway" -class Difficulty(str, Enum): # the difficulty of the raid +class Difficulty(StrEnum): # the difficulty of the raid discovery = "discovery" sports = "sports" expert = "expert" -class Situation(str, Enum): # the situation of the participant +class Situation(StrEnum): # the situation of the participant centrale = "centrale" otherSchool = "otherSchool" corporatePartner = "corporatePartner" other = "other" -class DocumentValidation(str, Enum): +class DocumentValidation(StrEnum): pending = "pending" accepted = "accepted" refused = "refused" diff --git a/app/types/content_type.py b/app/types/content_type.py index 18c5230b45..d415d18c6d 100644 --- a/app/types/content_type.py +++ b/app/types/content_type.py @@ -1,9 +1,9 @@ -from enum import Enum +from enum import StrEnum from app.types.exceptions import UnknownContentTypeExtensionError -class ContentType(str, Enum): +class ContentType(StrEnum): """ Accepted `content_type` for files """ @@ -32,7 +32,7 @@ def __str__(self): return self.extension -class PillowImageFormat(str, Enum): +class PillowImageFormat(StrEnum): """ Accepted image formats for Pillow """ diff --git a/app/types/scopes_type.py b/app/types/scopes_type.py index 3997da57ad..5885110c84 100644 --- a/app/types/scopes_type.py +++ b/app/types/scopes_type.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class ScopeType(str, Enum): +class ScopeType(StrEnum): """ Various scopes that can be included in JWT token """ diff --git a/app/types/websocket.py b/app/types/websocket.py index 73e16b2ee4..26eac804ae 100644 --- a/app/types/websocket.py +++ b/app/types/websocket.py @@ -1,7 +1,7 @@ import asyncio import logging import os -from enum import Enum +from enum import StrEnum from typing import Any, Literal from broadcaster import Broadcast @@ -15,7 +15,7 @@ from app.utils.auth import auth_utils -class HyperionWebsocketsRoom(str, Enum): +class HyperionWebsocketsRoom(StrEnum): CDR = "5a816d32-8b5d-4c44-8a8d-18fd830ec5a8" @@ -27,7 +27,7 @@ class WSMessageModel(BaseModel): data: Any -class ConnectionWSMessageModelStatus(str, Enum): +class ConnectionWSMessageModelStatus(StrEnum): connected = "connected" invalid = "invalid_token" diff --git a/app/utils/initialization.py b/app/utils/initialization.py index 74da6ba112..b21528ff03 100644 --- a/app/utils/initialization.py +++ b/app/utils/initialization.py @@ -205,7 +205,7 @@ def delete_core_data_crud_sync(schema: str, db: Session) -> None: CoreDataClass = TypeVar("CoreDataClass", bound=core_data.BaseCoreData) -def get_core_data_sync( +def get_core_data_sync[CoreDataClass: core_data.BaseCoreData]( core_data_class: type[CoreDataClass], db: Session, ) -> CoreDataClass: @@ -296,7 +296,7 @@ def drop_db_sync(conn: Connection): R = TypeVar("R") -async def use_lock_for_workers( +async def use_lock_for_workers[**P, R]( job_function: Callable[P, R], key: str, redis_client: redis.Redis | None, diff --git a/app/utils/tools.py b/app/utils/tools.py index b1f070cdc2..a71732d236 100644 --- a/app/utils/tools.py +++ b/app/utils/tools.py @@ -458,7 +458,7 @@ async def generate_pdf_from_template( You should only provide thrusted templates to this function. See [WeasyPrint security consideration](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#security) """ - from weasyprint import CSS, HTML + from weasyprint import CSS, HTML # noqa: PLC0415 templates = Environment( loader=FileSystemLoader("assets/templates"), @@ -676,7 +676,7 @@ def get_random_string(length: int = 5) -> str: CoreDataClass = TypeVar("CoreDataClass", bound=core_data.BaseCoreData) -async def get_core_data( +async def get_core_data[CoreDataClass: core_data.BaseCoreData]( core_data_class: type[CoreDataClass], db: AsyncSession, ) -> CoreDataClass: diff --git a/migrations/versions/21-fix_phonebook.py b/migrations/versions/21-fix_phonebook.py index 6149c304d7..afc365be6d 100644 --- a/migrations/versions/21-fix_phonebook.py +++ b/migrations/versions/21-fix_phonebook.py @@ -50,7 +50,7 @@ def define_order_of_memberships(memberships: list[sa.Row[Any]]) -> list[list]: for membership in memberships: if membership[2]: tags = membership[2].split(";") - tags.sort(key=lambda x: member_order.index(x)) + tags.sort(key=member_order.index) else: tags = ["Default"] memberships2.append( diff --git a/migrations/versions/28-booking_type.py b/migrations/versions/28-booking_type.py index fe7555b19b..3b14ff894d 100644 --- a/migrations/versions/28-booking_type.py +++ b/migrations/versions/28-booking_type.py @@ -5,7 +5,7 @@ from collections.abc import Sequence from datetime import datetime -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -21,7 +21,7 @@ depends_on: str | Sequence[str] | None = None -class Decision(str, Enum): +class Decision(StrEnum): approved = "approved" declined = "declined" pending = "pending" diff --git a/migrations/versions/29-hardcode-BDS-group.py b/migrations/versions/29-hardcode-BDS-group.py index 081c2b4a1a..e1d9dd0692 100644 --- a/migrations/versions/29-hardcode-BDS-group.py +++ b/migrations/versions/29-hardcode-BDS-group.py @@ -4,7 +4,7 @@ """ from collections.abc import Sequence -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING from uuid import uuid4 @@ -25,7 +25,7 @@ BDS_ID = "61af3e52-7ef9-4608-823a-39d51e83d1db" -class GroupType(str, Enum): +class GroupType(StrEnum): # Core groups admin = "0a25cb76-4b63-4fd3-b939-da6d9feabf28" AE = "45649735-866a-49df-b04b-a13c74fd5886" diff --git a/migrations/versions/30-membership.py b/migrations/versions/30-membership.py index d873b5f4cd..4d391f090d 100644 --- a/migrations/versions/30-membership.py +++ b/migrations/versions/30-membership.py @@ -6,7 +6,7 @@ import uuid from collections.abc import Sequence from datetime import date -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING from app.core.schools.schools_type import SchoolType @@ -27,7 +27,7 @@ BDS_ID = "61af3e52-7ef9-4608-823a-39d51e83d1db" -class AvailableAssociationMembership(str, Enum): +class AvailableAssociationMembership(StrEnum): aeecl = "AEECL" useecl = "USEECL" @@ -84,7 +84,7 @@ class AvailableAssociationMembership(str, Enum): USEECL_ID = uuid.uuid4() -class GroupType(str, Enum): +class GroupType(StrEnum): # Core groups admin = "0a25cb76-4b63-4fd3-b939-da6d9feabf28" AE = "45649735-866a-49df-b04b-a13c74fd5886" diff --git a/migrations/versions/32-MyECLPay.py b/migrations/versions/32-MyECLPay.py index 3b4ac1cdae..8026e20706 100644 --- a/migrations/versions/32-MyECLPay.py +++ b/migrations/versions/32-MyECLPay.py @@ -4,7 +4,7 @@ """ from collections.abc import Sequence -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -22,37 +22,37 @@ depends_on: str | Sequence[str] | None = None -class TransactionType(str, Enum): +class TransactionType(StrEnum): DIRECT = "direct" REQUEST = "request" REFUND = "refund" -class WalletType(str, Enum): +class WalletType(StrEnum): USER = "user" STORE = "store" -class TransferType(str, Enum): +class TransferType(StrEnum): HELLO_ASSO = "hello_asso" CHECK = "check" CASH = "cash" BANK_TRANSFER = "bank_transfer" -class WalletDeviceStatus(str, Enum): +class WalletDeviceStatus(StrEnum): INACTIVE = "inactive" ACTIVE = "active" REVOKED = "revoked" -class TransactionStatus(str, Enum): +class TransactionStatus(StrEnum): CONFIRMED = "confirmed" CANCELED = "canceled" REFUNDED = "refunded" -class RequestStatus(str, Enum): +class RequestStatus(StrEnum): PROPOSED = "proposed" ACCEPTED = "accepted" REFUSED = "refused" diff --git a/migrations/versions/35-topic-membership.py b/migrations/versions/35-topic-membership.py index 6a124433ac..2aaccfa0db 100644 --- a/migrations/versions/35-topic-membership.py +++ b/migrations/versions/35-topic-membership.py @@ -5,7 +5,7 @@ import uuid from collections.abc import Sequence -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING import sqlalchemy as sa @@ -24,7 +24,7 @@ depends_on: str | Sequence[str] | None = None -class Topic(str, Enum): +class Topic(StrEnum): cinema = "cinema" advert = "advert" amap = "amap" diff --git a/migrations/versions/41-feed.py b/migrations/versions/41-feed.py index fe5d0a89b8..381eeaa7ff 100644 --- a/migrations/versions/41-feed.py +++ b/migrations/versions/41-feed.py @@ -4,7 +4,7 @@ """ from collections.abc import Sequence -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -22,7 +22,7 @@ depends_on: str | Sequence[str] | None = None -class Status(str, Enum): +class Status(StrEnum): WAITING_APPROVAL = "WAITING_APPROVAL" REJECTED = "REJECTED" PUBLISHED = "PUBLISHED" diff --git a/migrations/versions/46-remove_hardcoded_floor.py b/migrations/versions/46-remove_hardcoded_floor.py index befa009af6..c06397faa6 100644 --- a/migrations/versions/46-remove_hardcoded_floor.py +++ b/migrations/versions/46-remove_hardcoded_floor.py @@ -4,7 +4,7 @@ """ from collections.abc import Sequence -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -20,7 +20,7 @@ depends_on: str | Sequence[str] | None = None -class FloorsType(str, Enum): +class FloorsType(StrEnum): # WARNING: the key is used in the database. Use the same key and value. Autre = "Autre" Adoma = "Adoma" diff --git a/migrations/versions/61-permissions.py b/migrations/versions/61-permissions.py index 4fbf89ae12..ac888a11a5 100644 --- a/migrations/versions/61-permissions.py +++ b/migrations/versions/61-permissions.py @@ -4,7 +4,7 @@ """ from collections.abc import Sequence -from enum import Enum +from enum import StrEnum from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -21,7 +21,7 @@ depends_on: str | Sequence[str] | None = None -class AccountType(str, Enum): +class AccountType(StrEnum): """ Various account types that can be created in Hyperion. Each account type is associated with a set of permissions. diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index fd607d8db4..b60c66c543 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -88,7 +88,7 @@ async def test_save_file() -> None: async def test_save_file_with_invalid_content_type() -> None: valid_uuid = str(uuid.uuid4()) with ( - pytest.raises(HTTPException, match="400: Invalid file format, supported*"), + pytest.raises(HTTPException, match=r"400: Invalid file format, supported*"), pathlib.Path("assets/images/default_profile_picture.png").open("rb") as file, ): await save_file_as_data( diff --git a/tests/modules/test_raid.py b/tests/modules/test_raid.py index d566718e83..6582df9009 100644 --- a/tests/modules/test_raid.py +++ b/tests/modules/test_raid.py @@ -25,7 +25,7 @@ MeetingPlace, Size, ) -from app.modules.raid.utils.utils_raid import calculate_raid_payment +from app.modules.raid.utils.utils_raid import calculate_raid_payment, set_team_number from tests.commons import ( add_coredata_to_db, add_object_to_db, @@ -747,7 +747,6 @@ async def test_set_team_number_utility_empty_database( mock_update_team = mocker.patch("app.modules.raid.cruds_raid.update_team") # Call the function - from app.modules.raid.utils.utils_raid import set_team_number await set_team_number(mock_team, mock_db) @@ -780,7 +779,6 @@ async def test_set_team_number_utility_existing_teams( mock_update_team = mocker.patch("app.modules.raid.cruds_raid.update_team") # Call the function - from app.modules.raid.utils.utils_raid import set_team_number await set_team_number(mock_team, mock_db) @@ -807,7 +805,6 @@ async def test_set_team_number_utility_no_difficulty( mock_update_team = mocker.patch("app.modules.raid.cruds_raid.update_team") # Call the function - from app.modules.raid.utils.utils_raid import set_team_number await set_team_number(mock_team, mock_db) @@ -836,7 +833,6 @@ async def test_set_team_number_utility_discovery_difficulty( mock_update_team = mocker.patch("app.modules.raid.cruds_raid.update_team") # Call the function - from app.modules.raid.utils.utils_raid import set_team_number await set_team_number(mock_team, mock_db) From 2fa88f464b896515ba745933dd83a85768236657 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:28:11 +0200 Subject: [PATCH 4/5] Return content type in ensure_file_properties --- app/utils/tools.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/utils/tools.py b/app/utils/tools.py index a71732d236..42c3f02918 100644 --- a/app/utils/tools.py +++ b/app/utils/tools.py @@ -191,7 +191,7 @@ async def ensure_file_properties( upload_file: UploadFile, accepted_content_types: list[ContentType] | None = None, max_file_size: int = 1024 * 1024 * 5, # 5 MB -) -> None: +) -> ContentType: """ Ensure that the provided file respects the properties: - Maximum size is 5 MB by default, it can be changed using `max_file_size` (in bytes) parameter. @@ -229,6 +229,8 @@ async def ensure_file_properties( # We go back to the beginning of the file to save it on the disk await upload_file.seek(0) + return ContentType(upload_file.content_type) + async def save_file_as_data( upload_file: UploadFile, @@ -265,13 +267,14 @@ async def save_file_as_data( ) raise FileNameIsNotAnUUIDError() - await ensure_file_properties( + content_type = await ensure_file_properties( upload_file=upload_file, accepted_content_types=accepted_content_types, max_file_size=max_file_size, ) - extension = ContentType(upload_file.content_type).name + extension = content_type.extension + # Remove the existing file if any and create the new one # If the directory does not exist, we want to create it @@ -631,7 +634,7 @@ async def compress_and_save_image_file( WARNING: **NEVER** trust user input when calling this function. Always check that parameters are valid. """ - await ensure_file_properties( + content_type = await ensure_file_properties( upload_file=upload_file, accepted_content_types=accepted_content_types, max_file_size=max_file_size, @@ -647,7 +650,7 @@ async def compress_and_save_image_file( file_bytes=original_file_bytes, directory=original_directory, filename=filename, - extension=ContentType(upload_file.content_type), + extension=content_type, ) file_bytes = compress_image( From 6067d46f1b346fc0704b4794be6bd61ce655bfb0 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:42:31 +0200 Subject: [PATCH 5/5] Circular --- app/module.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/module.py b/app/module.py index ed0f9d305f..a2260a0d54 100644 --- a/app/module.py +++ b/app/module.py @@ -11,6 +11,9 @@ core_module_list: list[CoreModule] = [] all_modules: list[CoreModule] = [] +permissions_list: list[str] = [] +full_name_permissions_list: list[str] = [] + for endpoints_file in Path().glob("app/modules/*/endpoints_*.py"): endpoint_module = importlib.import_module( ".".join(endpoints_file.with_suffix("").parts), @@ -39,10 +42,6 @@ ) -permissions_list: list[str] = [] -full_name_permissions_list: list[str] = [] - - class DuplicatePermissionsError(Exception): def __init__(self, permissions: list[list[str]]): arranged_permissions = [