From fa49fe07266e71c5c9970e63e0c24a72e829ab3a Mon Sep 17 00:00:00 2001 From: AtomHare <29772841+AtomHare@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:43:53 +0100 Subject: [PATCH 01/16] feat: support Pydantic V2 --- app/core/config.py | 102 +++++++++++----------- app/core/security.py | 4 +- app/cruds/cruds_advert.py | 4 +- app/cruds/cruds_amap.py | 14 +-- app/cruds/cruds_booking.py | 6 +- app/cruds/cruds_calendar.py | 2 +- app/cruds/cruds_campaign.py | 4 +- app/cruds/cruds_cinema.py | 4 +- app/cruds/cruds_groups.py | 2 +- app/cruds/cruds_loan.py | 6 +- app/cruds/cruds_raffle.py | 6 +- app/cruds/cruds_users.py | 2 +- app/endpoints/advert.py | 2 +- app/endpoints/amap.py | 4 +- app/endpoints/booking.py | 4 +- app/endpoints/calendar.py | 2 +- app/endpoints/campaign.py | 2 +- app/endpoints/cinema.py | 2 +- app/endpoints/loan.py | 2 +- app/endpoints/raffle.py | 8 +- app/schemas/schemas_advert.py | 14 +-- app/schemas/schemas_amap.py | 42 +++------ app/schemas/schemas_auth.py | 6 +- app/schemas/schemas_booking.py | 16 ++-- app/schemas/schemas_calendar.py | 28 ++---- app/schemas/schemas_campaign.py | 46 +++------- app/schemas/schemas_cinema.py | 14 +-- app/schemas/schemas_core.py | 103 ++++++++--------------- app/schemas/schemas_loan.py | 14 +-- app/schemas/schemas_notification.py | 21 ++--- app/schemas/schemas_raffle.py | 48 +++-------- app/utils/communication/notifications.py | 2 +- requirements.txt | 6 +- tests/commons.py | 2 +- tests/test_loan.py | 4 +- 35 files changed, 212 insertions(+), 336 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 20776184f..d0283fd42 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -2,9 +2,10 @@ from jose import jwk from jose.exceptions import JWKError -from pydantic import BaseSettings, root_validator +from pydantic import model_validator from app.utils.auth import providers +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): @@ -20,13 +21,22 @@ class Settings(BaseSettings): To access these settings, the `get_settings` dependency should be used. """ + # By default, the settings are loaded from the `.env` file but this behaviour can be overridden by using + # `_env_file` parameter during instantiation + # Ex: `Settings(_env_file=".env.dev")` + # Without this property, @cached_property decorator raise "TypeError: cannot pickle '_thread.RLock' object" + # See https://github.com/samuelcolvin/pydantic/issues/1241 + model_config = SettingsConfigDict( + env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore" + ) + # NOTE: Variables without a value should not be configured in this class, but added to the dotenv .env file ##################################### # SMTP configuration using starttls # ##################################### - SMTP_ACTIVE: bool + SMTP_ACTIVE: bool = False SMTP_PORT: int SMTP_SERVER: str SMTP_USERNAME: str @@ -41,10 +51,10 @@ class Settings(BaseSettings): # If the following parameters are not set, logging won't use the Matrix handler # MATRIX_SERVER_BASE_URL is optional, the official Matrix server will be used if not configured # Advanced note: Username and password will be used to ask for an access token. A Matrix custom client `Hyperion` is used to make all requests - MATRIX_SERVER_BASE_URL: str | None - MATRIX_TOKEN: str | None - MATRIX_LOG_ERROR_ROOM_ID: str | None - MATRIX_LOG_AMAP_ROOM_ID: str | None + MATRIX_SERVER_BASE_URL: str | None = None + MATRIX_TOKEN: str | None = None + MATRIX_LOG_ERROR_ROOM_ID: str | None = None + MATRIX_LOG_AMAP_ROOM_ID: str | None = None ######################## # Redis configuration # @@ -54,7 +64,7 @@ class Settings(BaseSettings): # If you want to use a custom configuration, a password and a specific binds should be used to avoid security issues REDIS_HOST: str REDIS_PORT: int - REDIS_PASSWORD: str | None + REDIS_PASSWORD: str | None = None REDIS_LIMIT: int REDIS_WINDOW: int @@ -72,11 +82,11 @@ class Settings(BaseSettings): SQLITE_DB: str | None = ( None # If set, the application use a SQLite database instead of PostgreSQL, for testing or development purposes (should not be used if possible) ) - POSTGRES_HOST: str - POSTGRES_USER: str - POSTGRES_PASSWORD: str - POSTGRES_DB: str - DATABASE_DEBUG: bool # If True, the database will log all queries + POSTGRES_HOST: str = "" + POSTGRES_USER: str = "" + POSTGRES_PASSWORD: str = "" + POSTGRES_DB: str = "" + DATABASE_DEBUG: bool = False # If True, the database will log all queries ##################### # Hyperion settings # @@ -105,11 +115,11 @@ class Settings(BaseSettings): # Tokens validity # ################### - USER_ACTIVATION_TOKEN_EXPIRE_HOURS = 24 - PASSWORD_RESET_TOKEN_EXPIRE_HOURS = 12 - ACCESS_TOKEN_EXPIRE_MINUTES = 30 - REFRESH_TOKEN_EXPIRE_MINUTES = 60 * 24 * 14 # 14 days - AUTHORIZATION_CODE_EXPIRE_MINUTES = 7 + USER_ACTIVATION_TOKEN_EXPIRE_HOURS: int = 24 + PASSWORD_RESET_TOKEN_EXPIRE_HOURS: int = 12 + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 14 # 14 days + AUTHORIZATION_CODE_EXPIRE_MINUTES: int = 7 ############################################### # Authorization using OAuth or Openid connect # @@ -129,7 +139,7 @@ class Settings(BaseSettings): ) # Openid connect issuer name - AUTH_ISSUER = "hyperion" + AUTH_ISSUER: str = "hyperion" # Add an AUTH_CLIENTS variable to the .env dotenv to configure auth clients # This variable should have the format: [["client id", "client secret", "redirect_uri", "app.utils.auth.providers class name"]] @@ -169,8 +179,8 @@ def RSA_PUBLIC_JWK(cls): return {"keys": [JWK]} # Tokens validity - USER_ACTIVATION_TOKEN_EXPIRES_HOURS = 24 - PASSWORD_RESET_TOKEN_EXPIRES_HOURS = 12 + USER_ACTIVATION_TOKEN_EXPIRES_HOURS: int = 24 + PASSWORD_RESET_TOKEN_EXPIRES_HOURS: int = 12 # This property parse AUTH_CLIENTS to create a dictionary of auth clients: # {"client_id": AuthClientClassInstance} @@ -205,58 +215,46 @@ def KNOWN_AUTH_CLIENTS(cls) -> dict[str, providers.BaseAuthClient]: # Validators may be used to perform more complexe validation # For example, we can check that at least one of two optional fields is set or that the RSA key is provided and valid - # TODO: Pydantic 2.0 will allow to use `@model_validator` - - @root_validator - def check_database_settings(cls, settings: dict): + @model_validator(mode="after") + def check_database_settings(self) -> "Settings": """ All fields are optional, but the dotenv should configure SQLITE_DB or a Postgres database """ - SQLITE_DB = settings.get("SQLITE_DB") - POSTGRES_HOST = settings.get("POSTGRES_HOST") - POSTGRES_USER = settings.get("POSTGRES_USER") - POSTGRES_PASSWORD = settings.get("POSTGRES_PASSWORD") - POSTGRES_DB = settings.get("POSTGRES_DB") + print(self) + print(self.model_config) + print() + print(self.SQLITE_DB) if not ( - SQLITE_DB - or (POSTGRES_HOST and POSTGRES_USER and POSTGRES_PASSWORD and POSTGRES_DB) + self.SQLITE_DB + or ( + self.POSTGRES_HOST + and self.POSTGRES_USER + and self.POSTGRES_PASSWORD + and self.POSTGRES_DB + ) ): raise ValueError( "Either SQLITE_DB or POSTGRES_HOST, POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB should be configured in the dotenv" ) - return settings + return self - @root_validator - def check_secrets(cls, settings: dict): - ACCESS_TOKEN_SECRET_KEY = settings.get("ACCESS_TOKEN_SECRET_KEY") - RSA_PRIVATE_PEM_STRING = settings.get("RSA_PRIVATE_PEM_STRING") - - if not ACCESS_TOKEN_SECRET_KEY: + @model_validator(mode="after") + def check_secrets(self) -> "Settings": + if not self.ACCESS_TOKEN_SECRET_KEY: raise ValueError( "ACCESS_TOKEN_SECRET_KEY should be configured in the dotenv" ) - if not RSA_PRIVATE_PEM_STRING: + if not self.RSA_PRIVATE_PEM_STRING: raise ValueError( "RSA_PRIVATE_PEM_STRING should be configured in the dotenv" ) try: - jwk.construct(RSA_PRIVATE_PEM_STRING, algorithm="RS256") + jwk.construct(self.RSA_PRIVATE_PEM_STRING, algorithm="RS256") except JWKError as e: raise ValueError("RSA_PRIVATE_PEM_STRING is not a valid RSA key", e) - return settings - - class Config: - # By default, the settings are loaded from the `.env` file but this behaviour can be overridden by using - # `_env_file` parameter during instantiation - # Ex: `Settings(_env_file=".env.dev")` - env_file = ".env" - env_file_encoding = "utf-8" - - # Without this property, @cached_property decorator raise "TypeError: cannot pickle '_thread.RLock' object" - # See https://github.com/samuelcolvin/pydantic/issues/1241 - keep_untouched = (cached_property,) + return self diff --git a/app/core/security.py b/app/core/security.py index 86670426f..a243479a7 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -98,7 +98,7 @@ def create_access_token( if expires_delta is None: # We use the default value expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - to_encode = data.dict(exclude_none=True) + to_encode = data.model_dump(exclude_none=True) iat = datetime.utcnow() expire_on = datetime.utcnow() + expires_delta to_encode.update({"exp": expire_on, "iat": iat}) @@ -124,7 +124,7 @@ def create_access_token_RS256( expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) to_encode: dict[str, Any] = additional_data - to_encode.update(data.dict(exclude_none=True)) + to_encode.update(data.model_dump(exclude_none=True)) iat = datetime.utcnow() expire_on = datetime.utcnow() + expires_delta diff --git a/app/cruds/cruds_advert.py b/app/cruds/cruds_advert.py index f1ce543f1..22b661df9 100644 --- a/app/cruds/cruds_advert.py +++ b/app/cruds/cruds_advert.py @@ -55,7 +55,7 @@ async def update_advertiser( await db.execute( update(models_advert.Advertiser) .where(models_advert.Advertiser.id == advertiser_id) - .values(**advertiser_update.dict(exclude_none=True)) + .values(**advertiser_update.model_dump(exclude_none=True)) ) try: await db.commit() @@ -127,7 +127,7 @@ async def update_advert( await db.execute( update(models_advert.Advert) .where(models_advert.Advert.id == advert_id) - .values(**advert_update.dict(exclude_none=True)) + .values(**advert_update.model_dump(exclude_none=True)) ) try: await db.commit() diff --git a/app/cruds/cruds_amap.py b/app/cruds/cruds_amap.py index b8435398e..ede9157e5 100644 --- a/app/cruds/cruds_amap.py +++ b/app/cruds/cruds_amap.py @@ -56,7 +56,7 @@ async def edit_product( await db.execute( update(models_amap.Product) .where(models_amap.Product.id == product_id) - .values(**product_update.dict(exclude_none=True)) + .values(**product_update.model_dump(exclude_none=True)) ) await db.commit() @@ -139,7 +139,7 @@ async def create_delivery( db: AsyncSession, ) -> models_amap.Delivery | None: """Create a new delivery in database and return it""" - db.add(models_amap.Delivery(**delivery.dict(exclude={"products_ids"}))) + db.add(models_amap.Delivery(**delivery.model_dump(exclude={"products_ids"}))) try: await db.commit() except IntegrityError: @@ -208,7 +208,7 @@ async def edit_delivery( await db.execute( update(models_amap.Delivery) .where(models_amap.Delivery.id == delivery_id) - .values(**delivery.dict(exclude_none=True)) + .values(**delivery.model_dump(exclude_none=True)) ) await db.commit() @@ -254,7 +254,9 @@ async def add_order_to_delivery( order: schemas_amap.OrderComplete, ): db.add( - models_amap.Order(**order.dict(exclude={"products_ids", "products_quantity"})) + models_amap.Order( + **order.model_dump(exclude={"products_ids", "products_quantity"}) + ) ) try: await db.commit() @@ -278,7 +280,7 @@ async def edit_order_without_products( await db.execute( update(models_amap.Order) .where(models_amap.Order.order_id == order_id) - .values(**order.dict(exclude_none=True)) + .values(**order.model_dump(exclude_none=True)) ) try: await db.commit() @@ -477,6 +479,6 @@ async def edit_information( await db.execute( update(models_amap.AmapInformation) .where(models_amap.AmapInformation.unique_id == "information") - .values(**information_update.dict(exclude_none=True)) + .values(**information_update.model_dump(exclude_none=True)) ) await db.commit() diff --git a/app/cruds/cruds_booking.py b/app/cruds/cruds_booking.py index bf89fd77f..ef57d91ad 100644 --- a/app/cruds/cruds_booking.py +++ b/app/cruds/cruds_booking.py @@ -37,7 +37,7 @@ async def update_manager( await db.execute( update(models_booking.Manager) .where(models_booking.Manager.id == manager_id) - .values(**manager_update.dict(exclude_none=True)) + .values(**manager_update.model_dump(exclude_none=True)) ) try: await db.commit() @@ -130,7 +130,7 @@ async def get_booking_by_id( async def create_booking(db: AsyncSession, booking: schemas_booking.BookingComplete): - db_booking = models_booking.Booking(**booking.dict()) + db_booking = models_booking.Booking(**booking.model_dump()) db.add(db_booking) try: await db.commit() @@ -145,7 +145,7 @@ async def edit_booking( await db.execute( update(models_booking.Booking) .where(models_booking.Booking.id == booking_id) - .values(**booking.dict(exclude_none=True)) + .values(**booking.model_dump(exclude_none=True)) ) try: await db.commit() diff --git a/app/cruds/cruds_calendar.py b/app/cruds/cruds_calendar.py index e36535115..6c37bb3e7 100644 --- a/app/cruds/cruds_calendar.py +++ b/app/cruds/cruds_calendar.py @@ -79,7 +79,7 @@ async def edit_event( await db.execute( update(models_calendar.Event) .where(models_calendar.Event.id == event_id) - .values(**event.dict(exclude_none=True)) + .values(**event.model_dump(exclude_none=True)) ) try: await db.commit() diff --git a/app/cruds/cruds_campaign.py b/app/cruds/cruds_campaign.py index da9bc6043..b235f5a32 100644 --- a/app/cruds/cruds_campaign.py +++ b/app/cruds/cruds_campaign.py @@ -271,7 +271,7 @@ async def update_list( await db.execute( update(models_campaign.Lists) .where(models_campaign.Lists.id == list_id) - .values(**campaign_list.dict(exclude={"members"}, exclude_none=True)) + .values(**campaign_list.model_dump(exclude={"members"}, exclude_none=True)) ) # We may need to recreate the list of members @@ -292,7 +292,7 @@ async def update_list( update(models_campaign.Lists) .where(models_campaign.Lists.id == list_id) .values( - **campaign_list.dict(exclude={"members"}, exclude_none=True), + **campaign_list.model_dump(exclude={"members"}, exclude_none=True), ) ) diff --git a/app/cruds/cruds_cinema.py b/app/cruds/cruds_cinema.py index e005c3efd..e2499a896 100644 --- a/app/cruds/cruds_cinema.py +++ b/app/cruds/cruds_cinema.py @@ -38,7 +38,7 @@ async def get_session_by_id( async def create_session( session: schemas_cinema.CineSessionComplete, db: AsyncSession ) -> models_cinema.Session: - db_session = models_cinema.Session(**session.dict()) + db_session = models_cinema.Session(**session.model_dump()) db.add(db_session) try: await db.commit() @@ -53,7 +53,7 @@ async def update_session( await db.execute( update(models_cinema.Session) .where(models_cinema.Session.id == session_id) - .values(**session_update.dict(exclude_none=True)) + .values(**session_update.model_dump(exclude_none=True)) ) await db.commit() diff --git a/app/cruds/cruds_groups.py b/app/cruds/cruds_groups.py index b160f9bbf..44c08e41f 100644 --- a/app/cruds/cruds_groups.py +++ b/app/cruds/cruds_groups.py @@ -116,6 +116,6 @@ async def update_group( await db.execute( update(models_core.CoreGroup) .where(models_core.CoreGroup.id == group_id) - .values(**group_update.dict(exclude_none=True)) + .values(**group_update.model_dump(exclude_none=True)) ) await db.commit() diff --git a/app/cruds/cruds_loan.py b/app/cruds/cruds_loan.py index cda3bf3bd..51e3472c6 100644 --- a/app/cruds/cruds_loan.py +++ b/app/cruds/cruds_loan.py @@ -44,7 +44,7 @@ async def update_loaner( await db.execute( update(models_loan.Loaner) .where(models_loan.Loaner.id == loaner_id) - .values(**loaner_update.dict(exclude_none=True)) + .values(**loaner_update.model_dump(exclude_none=True)) ) await db.commit() @@ -122,7 +122,7 @@ async def update_loaner_item( await db.execute( update(models_loan.Item) .where(models_loan.Item.id == item_id) - .values(**item_update.dict(exclude_none=True)) + .values(**item_update.model_dump(exclude_none=True)) ) await db.commit() @@ -184,7 +184,7 @@ async def update_loan( await db.execute( update(models_loan.Loan) .where(models_loan.Loan.id == loan_id) - .values(**loan_update.dict(exclude_none=True)) + .values(**loan_update.model_dump(exclude_none=True)) ) await db.commit() diff --git a/app/cruds/cruds_raffle.py b/app/cruds/cruds_raffle.py index 184ab4844..c5072bfea 100644 --- a/app/cruds/cruds_raffle.py +++ b/app/cruds/cruds_raffle.py @@ -68,7 +68,7 @@ async def edit_raffle( await db.execute( update(models_raffle.Raffle) .where(models_raffle.Raffle.id == raffle_id) - .values(**raffle_update.dict(exclude_none=True)) + .values(**raffle_update.model_dump(exclude_none=True)) ) await db.commit() @@ -139,7 +139,7 @@ async def edit_prize( await db.execute( update(models_raffle.Prize) .where(models_raffle.Prize.id == prize_id) - .values(**prize_update.dict(exclude_none=True)) + .values(**prize_update.model_dump(exclude_none=True)) ) await db.commit() @@ -225,7 +225,7 @@ async def edit_packticket( await db.execute( update(models_raffle.PackTicket) .where(models_raffle.PackTicket.id == packticket_id) - .values(**packticket_update.dict(exclude_none=True)) + .values(**packticket_update.model_dump(exclude_none=True)) ) await db.commit() diff --git a/app/cruds/cruds_users.py b/app/cruds/cruds_users.py index 670bbc3d6..163e59bf6 100644 --- a/app/cruds/cruds_users.py +++ b/app/cruds/cruds_users.py @@ -88,7 +88,7 @@ async def update_user(db: AsyncSession, user_id: str, user_update): await db.execute( update(models_core.CoreUser) .where(models_core.CoreUser.id == user_id) - .values(**user_update.dict(exclude_none=True)) + .values(**user_update.model_dump(exclude_none=True)) ) await db.commit() diff --git a/app/endpoints/advert.py b/app/endpoints/advert.py index 86dcf491a..56e72e9f1 100644 --- a/app/endpoints/advert.py +++ b/app/endpoints/advert.py @@ -254,7 +254,7 @@ async def create_advert( detail=f"Unauthorized to manage {advertiser.name} adverts", ) - advert_params = advert.dict() + advert_params = advert.model_dump() db_advert = models_advert.Advert( id=str(uuid.uuid4()), diff --git a/app/endpoints/amap.py b/app/endpoints/amap.py index 80381fbad..eb865fd61 100644 --- a/app/endpoints/amap.py +++ b/app/endpoints/amap.py @@ -70,7 +70,7 @@ async def create_product( **The user must be a member of the group AMAP to use this endpoint** """ - db_product = models_amap.Product(id=str(uuid.uuid4()), **product.dict()) + db_product = models_amap.Product(id=str(uuid.uuid4()), **product.__dict__) try: result = await cruds_amap.create_product(product=db_product, db=db) @@ -191,7 +191,7 @@ async def create_delivery( db_delivery = schemas_amap.DeliveryComplete( id=str(uuid.uuid4()), status=DeliveryStatusType.creation, - **delivery.dict(), + **delivery.model_dump(), ) if await cruds_amap.is_there_a_delivery_on( db=db, delivery_date=db_delivery.delivery_date diff --git a/app/endpoints/booking.py b/app/endpoints/booking.py index 011370f09..361004045 100644 --- a/app/endpoints/booking.py +++ b/app/endpoints/booking.py @@ -267,7 +267,7 @@ async def create_booking( id=str(uuid.uuid4()), decision=Decision.pending, applicant_id=user.id, - **booking.dict(), + **booking.model_dump(), ) await cruds_booking.create_booking(booking=db_booking, db=db) result = await cruds_booking.get_booking_by_id(db=db, booking_id=db_booking.id) @@ -434,7 +434,7 @@ async def create_room( try: room_db = models_booking.Room( id=str(uuid.uuid4()), - **room.dict(), + **room.model_dump(), ) return await cruds_booking.create_room(db=db, room=room_db) except ValueError as error: diff --git a/app/endpoints/calendar.py b/app/endpoints/calendar.py index 227ef9fa2..012c15057 100644 --- a/app/endpoints/calendar.py +++ b/app/endpoints/calendar.py @@ -138,7 +138,7 @@ async def add_event( id=event_id, decision=Decision.pending, applicant_id=user.id, - **event.dict(), + **event.model_dump(), ) try: return await cruds_calendar.add_event(event=db_event, db=db, settings=settings) diff --git a/app/endpoints/campaign.py b/app/endpoints/campaign.py index b21635d7b..595a6da84 100644 --- a/app/endpoints/campaign.py +++ b/app/endpoints/campaign.py @@ -385,7 +385,7 @@ async def add_voter( detail=f"VoterGroups can only be edited in waiting mode. The current status is {status}", ) - db_voter = models_campaign.VoterGroups(**voter.dict(exclude_none=True)) + db_voter = models_campaign.VoterGroups(**voter.model_dump(exclude_none=True)) try: await cruds_campaign.add_voter(voter=db_voter, db=db) except IntegrityError as error: diff --git a/app/endpoints/cinema.py b/app/endpoints/cinema.py index 215ac7c44..012430bfb 100644 --- a/app/endpoints/cinema.py +++ b/app/endpoints/cinema.py @@ -56,7 +56,7 @@ async def create_session( notification_tool: NotificationTool = Depends(get_notification_tool), ): db_session = schemas_cinema.CineSessionComplete( - id=str(uuid.uuid4()), **session.dict() + id=str(uuid.uuid4()), **session.model_dump() ) try: result = await cruds_cinema.create_session(session=db_session, db=db) diff --git a/app/endpoints/loan.py b/app/endpoints/loan.py index adf65f413..4c7ce2116 100644 --- a/app/endpoints/loan.py +++ b/app/endpoints/loan.py @@ -693,7 +693,7 @@ async def update_loan( # noqa: C901 try: # We need to remove the item_ids list from the schema before calling the update_loan crud function - loan_in_db_update = schemas_loan.LoanInDBUpdate(**loan_update.dict()) + loan_in_db_update = schemas_loan.LoanInDBUpdate(**loan_update.model_dump()) await cruds_loan.update_loan( loan_id=loan_id, loan_update=loan_in_db_update, db=db ) diff --git a/app/endpoints/raffle.py b/app/endpoints/raffle.py index 653f3adb8..3b1a62bed 100644 --- a/app/endpoints/raffle.py +++ b/app/endpoints/raffle.py @@ -72,7 +72,7 @@ async def create_raffle( if not group: raise HTTPException(status_code=404, detail="Group not found") raffle.status = RaffleStatusType.creation - db_raffle = models_raffle.Raffle(id=str(uuid.uuid4()), **raffle.dict()) + db_raffle = models_raffle.Raffle(id=str(uuid.uuid4()), **raffle.model_dump()) try: result = await cruds_raffle.create_raffle(raffle=db_raffle, db=db) @@ -309,7 +309,9 @@ async def create_packticket( detail=f"{user.id} user is unauthorized to manage the raffle {packticket.raffle_id}", ) - db_packticket = models_raffle.PackTicket(id=str(uuid.uuid4()), **packticket.dict()) + db_packticket = models_raffle.PackTicket( + id=str(uuid.uuid4()), **packticket.model_dump() + ) try: result = await cruds_raffle.create_packticket(packticket=db_packticket, db=db) @@ -638,7 +640,7 @@ async def create_prize( detail=f"Raffle {raffle.id} is not in Creation Mode", ) - db_prize = models_raffle.Prize(id=str(uuid.uuid4()), **prize.dict()) + db_prize = models_raffle.Prize(id=str(uuid.uuid4()), **prize.model_dump()) try: result = await cruds_raffle.create_prize(prize=db_prize, db=db) diff --git a/app/schemas/schemas_advert.py b/app/schemas/schemas_advert.py index 1a122c2a2..f370096ae 100644 --- a/app/schemas/schemas_advert.py +++ b/app/schemas/schemas_advert.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import ConfigDict, BaseModel, Field class AdvertiserBase(BaseModel): @@ -12,9 +12,7 @@ class AdvertiserBase(BaseModel): class AdvertiserComplete(AdvertiserBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class AdvertiserUpdate(BaseModel): @@ -32,18 +30,14 @@ class AdvertBase(BaseModel): class AdvertComplete(AdvertBase): id: str date: datetime | None = None - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class AdvertReturnComplete(AdvertBase): id: str advertiser: AdvertiserComplete date: datetime | None = None - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class AdvertUpdate(BaseModel): diff --git a/app/schemas/schemas_amap.py b/app/schemas/schemas_amap.py index daba09561..d8fdf95d9 100644 --- a/app/schemas/schemas_amap.py +++ b/app/schemas/schemas_amap.py @@ -3,7 +3,7 @@ from datetime import date, datetime from typing import Sequence -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel from app.schemas.schemas_core import CoreUserSimple from app.utils.types.amap_types import AmapSlotType, DeliveryStatusType @@ -13,9 +13,7 @@ class Rights(BaseModel): view: bool manage: bool amap_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ProductBase(BaseModel): @@ -37,17 +35,13 @@ class ProductEdit(BaseModel): class ProductComplete(ProductSimple): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ProductQuantity(BaseModel): quantity: int product: ProductComplete - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class DeliveryBase(BaseModel): @@ -60,9 +54,7 @@ class DeliveryBase(BaseModel): class DeliveryComplete(DeliveryBase): id: str status: DeliveryStatusType - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class DeliveryUpdate(BaseModel): @@ -86,9 +78,7 @@ class OrderComplete(OrderBase): amount: float ordering_date: datetime delivery_date: date - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class OrderReturn(BaseModel): @@ -100,18 +90,14 @@ class OrderReturn(BaseModel): amount: float ordering_date: datetime delivery_date: date - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class OrderEdit(BaseModel): products_ids: list[str] | None = None collection_slot: AmapSlotType | None = None products_quantity: list[int] | None = None - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class DeliveryReturn(BaseModel): @@ -119,9 +105,7 @@ class DeliveryReturn(BaseModel): products: list[ProductComplete] = [] id: str status: DeliveryStatusType - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class AddProductDelivery(BaseModel): @@ -132,9 +116,7 @@ class AddProductDelivery(BaseModel): class CashBase(BaseModel): balance: float user_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CashComplete(CashBase): @@ -153,9 +135,7 @@ class Information(BaseModel): manager: str link: str description: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class InformationEdit(BaseModel): diff --git a/app/schemas/schemas_auth.py b/app/schemas/schemas_auth.py index 2a18609ae..7c7cf1ba8 100644 --- a/app/schemas/schemas_auth.py +++ b/app/schemas/schemas_auth.py @@ -3,7 +3,7 @@ from datetime import datetime from fastapi import Form -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from app.utils import validators from app.utils.examples import examples_auth @@ -38,7 +38,9 @@ class AuthorizeValidation(Authorize): # Email normalization, this will modify the email variable # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators - _normalize_email = validator("email", allow_reuse=True)(validators.email_normalizer) + _normalize_email = field_validator( + "email", + )(validators.email_normalizer) class config: schema_extra = {"example": examples_auth.example_AuthorizeValidation} diff --git a/app/schemas/schemas_booking.py b/app/schemas/schemas_booking.py index 2b4cde289..4599658e2 100644 --- a/app/schemas/schemas_booking.py +++ b/app/schemas/schemas_booking.py @@ -2,7 +2,7 @@ from datetime import datetime -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from app.schemas.schemas_core import CoreUserSimple from app.utils.types.booking_type import Decision @@ -16,9 +16,7 @@ class Rights(BaseModel): class ManagerBase(BaseModel): name: str group_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ManagerUpdate(BaseModel): @@ -37,9 +35,7 @@ class RoomBase(BaseModel): class RoomComplete(RoomBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class BookingBase(BaseModel): @@ -47,7 +43,7 @@ class BookingBase(BaseModel): start: datetime end: datetime creation: datetime - note: str | None + note: str | None = None room_id: str key: bool recurrence_rule: str | None = None @@ -68,9 +64,7 @@ class Applicant(CoreUserSimple): class BookingReturn(BookingComplete): room: RoomComplete - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class BookingReturnSimpleApplicant(BookingReturn): diff --git a/app/schemas/schemas_calendar.py b/app/schemas/schemas_calendar.py index 30947b738..e762a66c1 100644 --- a/app/schemas/schemas_calendar.py +++ b/app/schemas/schemas_calendar.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import BaseModel, validator +from pydantic import ConfigDict, BaseModel, field_validator from app.schemas.schemas_core import CoreUserSimple from app.utils import validators @@ -17,21 +17,17 @@ class EventBase(BaseModel): location: str type: CalendarEventType description: str - recurrence_rule: str | None + recurrence_rule: str | None = None - _normalize_start = validator("start", allow_reuse=True)( - validators.time_zone_converter - ) - _normalize_end = validator("end", allow_reuse=True)(validators.time_zone_converter) + _normalize_start = field_validator("start")(validators.time_zone_converter) + _normalize_end = field_validator("end")(validators.time_zone_converter) class EventComplete(EventBase): id: str decision: Decision applicant_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class EventEdit(BaseModel): @@ -45,13 +41,9 @@ class EventEdit(BaseModel): description: str | None = None recurrence_rule: str | None = None - _normalize_start = validator("start", allow_reuse=True)( - validators.time_zone_converter - ) - _normalize_end = validator("end", allow_reuse=True)(validators.time_zone_converter) - - class Config: - orm_mode = True + _normalize_start = field_validator("start")(validators.time_zone_converter) + _normalize_end = field_validator("end")(validators.time_zone_converter) + model_config = ConfigDict(from_attributes=True) class EventApplicant(CoreUserSimple): @@ -62,6 +54,4 @@ class EventApplicant(CoreUserSimple): class EventReturn(EventComplete): applicant: EventApplicant - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/app/schemas/schemas_campaign.py b/app/schemas/schemas_campaign.py index 5e641770f..1fbfb6fe1 100644 --- a/app/schemas/schemas_campaign.py +++ b/app/schemas/schemas_campaign.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel from app.schemas import schemas_core from app.utils.types.campaign_type import ListType, StatusType @@ -13,24 +13,18 @@ class SectionBase(BaseModel): class SectionComplete(SectionBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ListMemberBase(BaseModel): user_id: str role: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ListMemberComplete(ListMemberBase): user: schemas_core.CoreUserSimple - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ListBase(BaseModel): @@ -41,14 +35,12 @@ class ListBase(BaseModel): type: ListType section_id: str members: list[ListMemberBase] - program: str | None + program: str | None = None class ListComplete(ListBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class SectionReturn(BaseModel): @@ -56,9 +48,7 @@ class SectionReturn(BaseModel): description: str id: str lists: list[ListComplete] - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ListReturn(BaseModel): @@ -68,10 +58,8 @@ class ListReturn(BaseModel): type: ListType section: SectionComplete members: list[ListMemberComplete] - program: str | None - - class Config: - orm_mode = True + program: str | None = None + model_config = ConfigDict(from_attributes=True) class ListEdit(BaseModel): @@ -86,33 +74,25 @@ class VoterGroup(BaseModel): """Base schema for voters (groups allowed to vote).""" group_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class VoteBase(BaseModel): """Base schema for a vote.""" list_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class VoteStatus(BaseModel): status: StatusType - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class Result(BaseModel): list_id: str count: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class VoteStats(BaseModel): diff --git a/app/schemas/schemas_cinema.py b/app/schemas/schemas_cinema.py index 35486af80..c22594013 100644 --- a/app/schemas/schemas_cinema.py +++ b/app/schemas/schemas_cinema.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import BaseModel, validator +from pydantic import ConfigDict, BaseModel, field_validator from app.utils import validators @@ -9,9 +9,7 @@ class CineSessionTime(BaseModel): start: datetime duration: int - _normalize_start = validator("start", allow_reuse=True)( - validators.time_zone_converter - ) + _normalize_start = field_validator("start")(validators.time_zone_converter) class CineSessionBase(CineSessionTime): @@ -23,9 +21,7 @@ class CineSessionBase(CineSessionTime): class CineSessionComplete(CineSessionBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CineSessionUpdate(BaseModel): @@ -36,6 +32,4 @@ class CineSessionUpdate(BaseModel): genre: str | None = None tagline: str | None = None - _normalize_start = validator("start", allow_reuse=True)( - validators.time_zone_converter - ) + _normalize_start = field_validator("start")(validators.time_zone_converter) diff --git a/app/schemas/schemas_core.py b/app/schemas/schemas_core.py index 553ac3041..fee28cd0e 100644 --- a/app/schemas/schemas_core.py +++ b/app/schemas/schemas_core.py @@ -2,7 +2,8 @@ from datetime import date, datetime -from pydantic import BaseModel, Field, validator +from pydantic import ConfigDict, BaseModel, Field +from pydantic.functional_validators import field_validator from app.utils import validators from app.utils.examples import examples_core @@ -28,13 +29,11 @@ class CoreUserBase(BaseModel): firstname: str nickname: str | None = None - _normalize_name = validator("name", allow_reuse=True)( + _normalize_name = field_validator("name")(validators.trailing_spaces_remover) + _normalize_firstname = field_validator("firstname")( validators.trailing_spaces_remover ) - _normalize_firstname = validator("firstname", allow_reuse=True)( - validators.trailing_spaces_remover - ) - _normalize_nickname = validator("nickname", allow_reuse=True)( + _normalize_nickname = field_validator("nickname")( validators.trailing_spaces_remover ) @@ -45,27 +44,21 @@ class CoreGroupBase(BaseModel): name: str description: str | None = None - _normalize_name = validator("name", allow_reuse=True)( - validators.trailing_spaces_remover - ) + _normalize_name = field_validator("name")(validators.trailing_spaces_remover) class CoreUserSimple(CoreUserBase): """Simplified schema for user's model, used when getting all users""" id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CoreGroupSimple(CoreGroupBase): """Simplified schema for group's model, used when getting all groups""" id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CoreUser(CoreUserSimple): @@ -88,12 +81,10 @@ class CoreUserUpdate(BaseModel): phone: str | None = None floor: FloorsType | None = None - _normalize_nickname = validator("nickname", allow_reuse=True)( + _normalize_nickname = field_validator("nickname")( validators.trailing_spaces_remover ) - - class Config: - schema_extra = examples_core.example_CoreUserUpdate + model_config = ConfigDict(json_schema_extra=examples_core.example_CoreUserUpdate) class CoreUserUpdateAdmin(BaseModel): @@ -105,18 +96,14 @@ class CoreUserUpdateAdmin(BaseModel): phone: str | None = None floor: FloorsType | None = None - _normalize_name = validator("name", allow_reuse=True)( + _normalize_name = field_validator("name")(validators.trailing_spaces_remover) + _normalize_firstname = field_validator("firstname")( validators.trailing_spaces_remover ) - _normalize_firstname = validator("firstname", allow_reuse=True)( + _normalize_nickname = field_validator("nickname")( validators.trailing_spaces_remover ) - _normalize_nickname = validator("nickname", allow_reuse=True)( - validators.trailing_spaces_remover - ) - - class Config: - schema_extra = examples_core.example_CoreUserUpdate + model_config = ConfigDict(json_schema_extra=examples_core.example_CoreUserUpdate) class CoreUserCreateRequest(BaseModel): @@ -128,12 +115,11 @@ class CoreUserCreateRequest(BaseModel): # Email normalization, this will modify the email variable # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators - _normalize_email = validator("email", allow_reuse=True)(validators.email_normalizer) - - class Config: - orm_mode = True - - schema_extra = examples_core.example_CoreUserCreateRequest + _normalize_email = field_validator("email")(validators.email_normalizer) + model_config = ConfigDict( + from_attributes=True, + json_schema_extra=examples_core.example_CoreUserCreateRequest, + ) class CoreBatchUserCreateRequest(BaseModel): @@ -146,12 +132,11 @@ class CoreBatchUserCreateRequest(BaseModel): # Email normalization, this will modify the email variable # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators - _normalize_email = validator("email", allow_reuse=True)(validators.email_normalizer) - - class Config: - orm_mode = True - - schema_extra = examples_core.example_CoreBatchUserCreateRequest + _normalize_email = field_validator("email")(validators.email_normalizer) + model_config = ConfigDict( + from_attributes=True, + json_schema_extra=examples_core.example_CoreBatchUserCreateRequest, + ) class CoreUserActivateRequest(CoreUserBase): @@ -166,14 +151,12 @@ class CoreUserActivateRequest(CoreUserBase): # Password validator # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators - _normalize_password = validator("password", allow_reuse=True)( - validators.password_validator + _normalize_password = field_validator("password")(validators.password_validator) + model_config = ConfigDict( + from_attributes=True, + json_schema_extra=examples_core.example_CoreUserActivateRequest, ) - class Config: - orm_mode = True - schema_extra = examples_core.example_CoreUserActivateRequest - class CoreGroup(CoreGroupSimple): """Schema for group's model similar to core_group table in database""" @@ -191,9 +174,7 @@ class CoreGroupInDB(CoreGroupBase): """Schema for user activation""" id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CoreGroupUpdate(BaseModel): @@ -202,9 +183,7 @@ class CoreGroupUpdate(BaseModel): name: str | None = None description: str | None = None - _normalize_name = validator("name", allow_reuse=True)( - validators.trailing_spaces_remover - ) + _normalize_name = field_validator("name")(validators.trailing_spaces_remover) class CoreUserRecoverRequest(BaseModel): @@ -214,10 +193,8 @@ class CoreUserRecoverRequest(BaseModel): created_on: datetime expire_on: datetime - _normalize_email = validator("email", allow_reuse=True)(validators.email_normalizer) - - class Config: - orm_mode = True + _normalize_email = field_validator("email")(validators.email_normalizer) + model_config = ConfigDict(from_attributes=True) class ChangePasswordRequest(BaseModel): @@ -227,9 +204,7 @@ class ChangePasswordRequest(BaseModel): # Password validator # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators - _normalize_password = validator("new_password", allow_reuse=True)( - validators.password_validator - ) + _normalize_password = field_validator("new_password")(validators.password_validator) class ResetPasswordRequest(BaseModel): @@ -238,9 +213,7 @@ class ResetPasswordRequest(BaseModel): # Password validator # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators - _normalize_password = validator("new_password", allow_reuse=True)( - validators.password_validator - ) + _normalize_password = field_validator("new_password")(validators.password_validator) class MailMigrationRequest(BaseModel): @@ -281,14 +254,10 @@ class CoreMembershipDelete(BaseModel): class ModuleVisibility(BaseModel): root: str allowed_group_ids: list[str] - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ModuleVisibilityCreate(BaseModel): root: str allowed_group_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/app/schemas/schemas_loan.py b/app/schemas/schemas_loan.py index bc3c1a9d9..81da6fadb 100644 --- a/app/schemas/schemas_loan.py +++ b/app/schemas/schemas_loan.py @@ -1,6 +1,6 @@ from datetime import date, timedelta -from pydantic import BaseModel, Field +from pydantic import ConfigDict, BaseModel, Field from app.schemas import schemas_core @@ -10,9 +10,7 @@ class LoanerBase(BaseModel): group_manager_id: str = Field( description="The group manager id should by a group identifier" ) - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class LoanerUpdate(BaseModel): @@ -31,9 +29,7 @@ class ItemBase(BaseModel): suggested_caution: int total_quantity: int suggested_lending_duration: timedelta - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ItemUpdate(BaseModel): @@ -66,9 +62,7 @@ class LoanBase(BaseModel): end: date notes: str | None = None caution: str | None = None - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class ItemBorrowed(BaseModel): diff --git a/app/schemas/schemas_notification.py b/app/schemas/schemas_notification.py index aef294f48..84a427308 100644 --- a/app/schemas/schemas_notification.py +++ b/app/schemas/schemas_notification.py @@ -1,6 +1,6 @@ from datetime import date, datetime -from pydantic import BaseModel, Field +from pydantic import ConfigDict, BaseModel, Field class Message(BaseModel): @@ -15,26 +15,23 @@ class Message(BaseModel): description="A message can be visible or not, if it is not visible, it should only trigger an action" ) - title: str | None - content: str | None + title: str | None = None + content: str | None = None action_module: str | None = Field( - description="An identifier for the module that should be triggered when the notification is clicked" + None, + description="An identifier for the module that should be triggered when the notification is clicked", ) - action_table: str | None + action_table: str | None = None delivery_datetime: datetime | None = Field( - description="The date the notification should be shown" + None, description="The date the notification should be shown" ) expire_on: date - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class FirebaseDevice(BaseModel): user_id: str = Field(description="The Hyperion user id") firebase_device_token: str = Field("Firebase device token") - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) diff --git a/app/schemas/schemas_raffle.py b/app/schemas/schemas_raffle.py index 1175c30c9..5d2fdb046 100644 --- a/app/schemas/schemas_raffle.py +++ b/app/schemas/schemas_raffle.py @@ -1,6 +1,6 @@ """Schemas file for endpoint /amap""" -from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from app.schemas.schemas_core import CoreUserSimple from app.utils.types.raffle_types import RaffleStatusType @@ -22,9 +22,7 @@ class RaffleEdit(BaseModel): class RaffleComplete(RaffleBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class RaffleStats(BaseModel): @@ -37,9 +35,7 @@ class PrizeBase(BaseModel): description: str raffle_id: str quantity: int - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class PrizeEdit(BaseModel): @@ -51,49 +47,37 @@ class PrizeEdit(BaseModel): class PrizeSimple(PrizeBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class PrizeComplete(PrizeBase): id: str raffle: RaffleComplete - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class PackTicketBase(BaseModel): price: float pack_size: int raffle_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class PackTicketEdit(BaseModel): raffle_id: str | None = None price: float | None = None pack_size: int | None = None - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class PackTicketSimple(PackTicketBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class PackTicketComplete(PackTicketSimple): raffle: RaffleComplete - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class TicketBase(BaseModel): @@ -104,26 +88,20 @@ class TicketBase(BaseModel): class TicketSimple(TicketBase): id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class TicketComplete(TicketSimple): - prize: PrizeSimple | None + prize: PrizeSimple | None = None pack_ticket: PackTicketSimple user: CoreUserSimple - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CashBase(BaseModel): balance: float user_id: str - - class Config: - orm_mode = True + model_config = ConfigDict(from_attributes=True) class CashComplete(CashBase): diff --git a/app/utils/communication/notifications.py b/app/utils/communication/notifications.py index 0f5394fa4..7c31e3cd2 100644 --- a/app/utils/communication/notifications.py +++ b/app/utils/communication/notifications.py @@ -218,7 +218,7 @@ async def _add_message_for_user_in_database( message_models.append( models_notification.Message( firebase_device_token=token, - **message.dict(), + **message.model_dump(), ) ) diff --git a/requirements.txt b/requirements.txt index 590636f77..25f366b29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,12 @@ alembic==1.13.1 # database migrations -fastapi==0.99.0 +fastapi==0.109.1 Jinja2==3.1.3 # template engine for html files bcrypt==4.0.1 # password hashing +pydantic-settings==2.2.1 +pydantic==2.5.0 python-dotenv==1.0.0 # load environment variables from .env file python-jose[cryptography]==3.3.0 # generate and verify the JWT tokens -python-multipart==0.0.6 # a form data parser, as oauth flow requires form-data parameters +python-multipart==0.0.9 # a form data parser, as oauth flow requires form-data parameters SQLAlchemy[asyncio]==2.0.23 # [asyncio] allows greenlet to be installed on Apple M1 devices. The my[py] plugin is required in devellopment but Dependabot fails to manage two versions of the same module. rapidfuzz==3.6.1 # Fuzzy String Matching requests==2.31.0 diff --git a/tests/commons.py b/tests/commons.py index f51bdffa5..0c32590e6 100644 --- a/tests/commons.py +++ b/tests/commons.py @@ -27,7 +27,7 @@ @lru_cache() def override_get_settings() -> Settings: """Override the get_settings function to use the testing session""" - return Settings(_env_file=".env.test") # type: ignore[call-arg] # See https://github.com/pydantic/pydantic/issues/3072, TODO: remove when fixes + return Settings(_env_file=".env.test", _env_file_encoding="utf-8") # type: ignore[call-arg] # See https://github.com/pydantic/pydantic/issues/3072, TODO: remove when fixes settings = override_get_settings() diff --git a/tests/test_loan.py b/tests/test_loan.py index b51f5514e..e55a1e4ba 100644 --- a/tests/test_loan.py +++ b/tests/test_loan.py @@ -166,7 +166,7 @@ def test_create_items_for_loaner(): "name": "TestItem", "suggested_caution": 100, "total_quantity": 4, - "suggested_lending_duration": str(timedelta(days=10)), + "suggested_lending_duration": timedelta(days=10).seconds, }, headers={"Authorization": f"Bearer {token_loaner}"}, ) @@ -180,7 +180,7 @@ def test_update_items_for_loaner(): "name": "TestItem", "suggested_caution": 100, "total_quantity": 7, - "suggested_lending_duration": str(timedelta(days=10)), + "suggested_lending_duration": timedelta(days=10).seconds, }, headers={"Authorization": f"Bearer {token_loaner}"}, ) From cfb7dc78787f8db4bf0a29fb39ecbd907e9d9c5b Mon Sep 17 00:00:00 2001 From: AtomHare <29772841+AtomHare@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:51:30 +0100 Subject: [PATCH 02/16] fix: typo and missed dict() --- app/endpoints/amap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/endpoints/amap.py b/app/endpoints/amap.py index eb865fd61..8243e4372 100644 --- a/app/endpoints/amap.py +++ b/app/endpoints/amap.py @@ -460,7 +460,7 @@ async def add_order_to_delievery( user_id=order.user_id, ) balance = models_amap.Cash( - **new_cash_db.dict(), + **new_cash_db.model_dump(), ) await cruds_amap.create_cash_of_user( cash=balance, @@ -510,7 +510,7 @@ async def add_order_to_delievery( status_code=204, tags=[Tags.amap], ) -async def edit_order_from_delievery( +async def edit_order_from_delivery( order_id: str, order: schemas_amap.OrderEdit, db: AsyncSession = Depends(get_db), @@ -580,7 +580,7 @@ async def edit_order_from_delievery( delivery_id=previous_order.delivery_id, user_id=previous_order.user_id, amount=amount, - **order.dict(), + **order.model_dump(), ) previous_amount = previous_order.amount From 60f4b641aa9277d8d77f592900db900906e65eb4 Mon Sep 17 00:00:00 2001 From: AtomHare <29772841+AtomHare@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:54:26 +0100 Subject: [PATCH 03/16] fix: missed dict (again) --- app/endpoints/amap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/endpoints/amap.py b/app/endpoints/amap.py index 8243e4372..10c95c064 100644 --- a/app/endpoints/amap.py +++ b/app/endpoints/amap.py @@ -446,7 +446,7 @@ async def add_order_to_delievery( amount=amount, ordering_date=ordering_date, delivery_date=delivery.delivery_date, - **order.dict(), + **order.model_dump(), ) balance: models_amap.Cash | None = await cruds_amap.get_cash_by_id( db=db, From f8c93a97502d9aa4af48e805d743e5a5d1ebd346 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:57:55 +0100 Subject: [PATCH 04/16] Sort imports --- app/core/config.py | 2 +- app/schemas/schemas_advert.py | 2 +- app/schemas/schemas_amap.py | 2 +- app/schemas/schemas_calendar.py | 2 +- app/schemas/schemas_campaign.py | 2 +- app/schemas/schemas_cinema.py | 2 +- app/schemas/schemas_core.py | 2 +- app/schemas/schemas_loan.py | 2 +- app/schemas/schemas_notification.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index d0283fd42..e121ff9ff 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -3,9 +3,9 @@ from jose import jwk from jose.exceptions import JWKError from pydantic import model_validator +from pydantic_settings import BaseSettings, SettingsConfigDict from app.utils.auth import providers -from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): diff --git a/app/schemas/schemas_advert.py b/app/schemas/schemas_advert.py index f370096ae..22eb84f8c 100644 --- a/app/schemas/schemas_advert.py +++ b/app/schemas/schemas_advert.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import ConfigDict, BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field class AdvertiserBase(BaseModel): diff --git a/app/schemas/schemas_amap.py b/app/schemas/schemas_amap.py index d8fdf95d9..60eabc09d 100644 --- a/app/schemas/schemas_amap.py +++ b/app/schemas/schemas_amap.py @@ -3,7 +3,7 @@ from datetime import date, datetime from typing import Sequence -from pydantic import ConfigDict, BaseModel +from pydantic import BaseModel, ConfigDict from app.schemas.schemas_core import CoreUserSimple from app.utils.types.amap_types import AmapSlotType, DeliveryStatusType diff --git a/app/schemas/schemas_calendar.py b/app/schemas/schemas_calendar.py index e762a66c1..e6758453b 100644 --- a/app/schemas/schemas_calendar.py +++ b/app/schemas/schemas_calendar.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import ConfigDict, BaseModel, field_validator +from pydantic import BaseModel, ConfigDict, field_validator from app.schemas.schemas_core import CoreUserSimple from app.utils import validators diff --git a/app/schemas/schemas_campaign.py b/app/schemas/schemas_campaign.py index 1fbfb6fe1..aed7aa57b 100644 --- a/app/schemas/schemas_campaign.py +++ b/app/schemas/schemas_campaign.py @@ -1,4 +1,4 @@ -from pydantic import ConfigDict, BaseModel +from pydantic import BaseModel, ConfigDict from app.schemas import schemas_core from app.utils.types.campaign_type import ListType, StatusType diff --git a/app/schemas/schemas_cinema.py b/app/schemas/schemas_cinema.py index c22594013..e4a87fdc5 100644 --- a/app/schemas/schemas_cinema.py +++ b/app/schemas/schemas_cinema.py @@ -1,6 +1,6 @@ from datetime import datetime -from pydantic import ConfigDict, BaseModel, field_validator +from pydantic import BaseModel, ConfigDict, field_validator from app.utils import validators diff --git a/app/schemas/schemas_core.py b/app/schemas/schemas_core.py index fee28cd0e..1b33d3fa2 100644 --- a/app/schemas/schemas_core.py +++ b/app/schemas/schemas_core.py @@ -2,7 +2,7 @@ from datetime import date, datetime -from pydantic import ConfigDict, BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from pydantic.functional_validators import field_validator from app.utils import validators diff --git a/app/schemas/schemas_loan.py b/app/schemas/schemas_loan.py index 81da6fadb..1eda916ea 100644 --- a/app/schemas/schemas_loan.py +++ b/app/schemas/schemas_loan.py @@ -1,6 +1,6 @@ from datetime import date, timedelta -from pydantic import ConfigDict, BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from app.schemas import schemas_core diff --git a/app/schemas/schemas_notification.py b/app/schemas/schemas_notification.py index 84a427308..4a1d1561d 100644 --- a/app/schemas/schemas_notification.py +++ b/app/schemas/schemas_notification.py @@ -1,6 +1,6 @@ from datetime import date, datetime -from pydantic import ConfigDict, BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field class Message(BaseModel): From e8fc165f18485320ef857f39ec58e1b327b68f50 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:19:06 +0100 Subject: [PATCH 05/16] Remove "example" from example dict --- app/utils/examples/examples_core.py | 46 ++++++++++++----------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/app/utils/examples/examples_core.py b/app/utils/examples/examples_core.py index da974f9ab..2cd9e5794 100644 --- a/app/utils/examples/examples_core.py +++ b/app/utils/examples/examples_core.py @@ -1,36 +1,28 @@ -example_CoreUserUpdate = { - "example": { - "name": "Backend", - "firstname": "MyECL", - "nickname": "Hyperion", - "birthday": "2022-05-04", - "promo": 2021, - "floor": "Adoma", - } +example_CoreUserUpdate: dict[str, object] = { + "name": "Backend", + "firstname": "MyECL", + "nickname": "Hyperion", + "birthday": "2022-05-04", + "promo": 2021, + "floor": "Adoma", } -example_CoreUserCreateRequest = { - "example": { - "email": "user@example.fr", - } +example_CoreUserCreateRequest: dict[str, object] = { + "email": "user@example.fr", } -example_CoreBatchUserCreateRequest = { - "example": { - "email": "user@example.fr", - "account_type": "39691052-2ae5-4e12-99d0-7a9f5f2b0136", - } +example_CoreBatchUserCreateRequest: dict[str, object] = { + "email": "user@example.fr", + "account_type": "39691052-2ae5-4e12-99d0-7a9f5f2b0136", } -example_CoreUserActivateRequest = { - "example": { - "name": "Name", - "firstname": "Firstname", - "nickname": "Antoine", - "activation_token": "62D-QJI5IYrjuywH8IWnuBo0xHrbTCfw_18HP4mdRrA", - "password": "areallycomplexpassword", - "floor": "Autre", - } +example_CoreUserActivateRequest: dict[str, object] = { + "name": "Name", + "firstname": "Firstname", + "nickname": "Antoine", + "activation_token": "62D-QJI5IYrjuywH8IWnuBo0xHrbTCfw_18HP4mdRrA", + "password": "areallycomplexpassword", + "floor": "Autre", } From 8f16df768475ed2c8b88dbfc10036dbbf2739bbe Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:19:29 +0100 Subject: [PATCH 06/16] Fix as_form type inference --- app/schemas/schemas_auth.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/schemas/schemas_auth.py b/app/schemas/schemas_auth.py index 7c7cf1ba8..995afa834 100644 --- a/app/schemas/schemas_auth.py +++ b/app/schemas/schemas_auth.py @@ -36,6 +36,15 @@ class AuthorizeValidation(Authorize): email: str password: str + # If we don't add these parameters + # the heritage from Authorize does not allow Mypy to infer the str | None + redirect_uri: str | None = None + scope: str | None = None + state: str | None = None + nonce: str | None = None + code_challenge: str | None = None + code_challenge_method: str | None = None + # Email normalization, this will modify the email variable # https://pydantic-docs.helpmanual.io/usage/validators/#reuse-validators _normalize_email = field_validator( From 5e91e0257fef0e98a7df31bccb90dbb9072c65ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Maistre?= Date: Mon, 13 Nov 2023 19:59:30 +0100 Subject: [PATCH 07/16] Update to pydantic 2.5.0 and put version in requirements.txt --- app/utils/examples/examples_core.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/utils/examples/examples_core.py b/app/utils/examples/examples_core.py index 2cd9e5794..085ec4b9a 100644 --- a/app/utils/examples/examples_core.py +++ b/app/utils/examples/examples_core.py @@ -1,4 +1,6 @@ -example_CoreUserUpdate: dict[str, object] = { +from pydantic.config import JsonDict + +example_CoreUserUpdate: JsonDict = { "name": "Backend", "firstname": "MyECL", "nickname": "Hyperion", @@ -7,18 +9,18 @@ "floor": "Adoma", } -example_CoreUserCreateRequest: dict[str, object] = { +example_CoreUserCreateRequest: JsonDict = { "email": "user@example.fr", } -example_CoreBatchUserCreateRequest: dict[str, object] = { +example_CoreBatchUserCreateRequest: JsonDict = { "email": "user@example.fr", "account_type": "39691052-2ae5-4e12-99d0-7a9f5f2b0136", } -example_CoreUserActivateRequest: dict[str, object] = { +example_CoreUserActivateRequest: JsonDict = { "name": "Name", "firstname": "Firstname", "nickname": "Antoine", From b45a4ff80b849cc7096dc9c27c67c16700c64f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Maistre?= Date: Mon, 13 Nov 2023 20:20:07 +0100 Subject: [PATCH 08/16] Remove unsafe prints in config.py --- app/core/config.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index e121ff9ff..c413935f4 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -220,11 +220,6 @@ def check_database_settings(self) -> "Settings": """ All fields are optional, but the dotenv should configure SQLITE_DB or a Postgres database """ - print(self) - print(self.model_config) - print() - print(self.SQLITE_DB) - if not ( self.SQLITE_DB or ( From 9a02cfcdb97a1ce0db4c44aebe8c587a79a21032 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:52:45 +0100 Subject: [PATCH 09/16] Return loan duration in seconds instead of a timedelta which is converted in an ISO 8601 str --- app/endpoints/loan.py | 3 ++- app/models/models_loan.py | 10 +++++----- app/schemas/schemas_loan.py | 12 +++++++----- tests/test_loan.py | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/endpoints/loan.py b/app/endpoints/loan.py index 4c7ce2116..3c76393fa 100644 --- a/app/endpoints/loan.py +++ b/app/endpoints/loan.py @@ -1,4 +1,5 @@ import uuid +from datetime import timedelta from typing import Sequence from fastapi import APIRouter, Depends, HTTPException @@ -852,7 +853,7 @@ async def extend_loan( ) elif loan_extend.duration is not None: loan_update = schemas_loan.LoanUpdate( - end=loan.end + loan_extend.duration, + end=loan.end + timedelta(seconds=loan_extend.duration), ) await cruds_loan.update_loan( diff --git a/app/models/models_loan.py b/app/models/models_loan.py index 5bca4df86..c62d6cc76 100644 --- a/app/models/models_loan.py +++ b/app/models/models_loan.py @@ -1,6 +1,6 @@ -from datetime import date, timedelta +from datetime import date -from sqlalchemy import TEXT, Boolean, Date, ForeignKey, Integer, Interval, String +from sqlalchemy import TEXT, Boolean, Date, ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base @@ -41,9 +41,9 @@ class Item(Base): loaner_id: Mapped[str] = mapped_column(String, ForeignKey("loaner.id")) suggested_caution: Mapped[int] = mapped_column(Integer) total_quantity: Mapped[int] = mapped_column(Integer, nullable=False) - suggested_lending_duration: Mapped[timedelta] = mapped_column( - Interval, nullable=False - ) + suggested_lending_duration: Mapped[int] = mapped_column( + Integer, nullable=False + ) # duration in seconds loaner: Mapped[Loaner] = relationship(Loaner, lazy="joined", back_populates="items") diff --git a/app/schemas/schemas_loan.py b/app/schemas/schemas_loan.py index 1eda916ea..d1a196e3b 100644 --- a/app/schemas/schemas_loan.py +++ b/app/schemas/schemas_loan.py @@ -1,4 +1,4 @@ -from datetime import date, timedelta +from datetime import date from pydantic import BaseModel, ConfigDict, Field @@ -28,7 +28,7 @@ class ItemBase(BaseModel): name: str suggested_caution: int total_quantity: int - suggested_lending_duration: timedelta + suggested_lending_duration: int = Field(description="duration in seconds") model_config = ConfigDict(from_attributes=True) @@ -36,7 +36,9 @@ class ItemUpdate(BaseModel): name: str | None = None suggested_caution: int | None = None total_quantity: int | None = None - suggested_lending_duration: timedelta | None = None + suggested_lending_duration: int | None = Field( + description="duration in seconds", default=None + ) class Item(ItemBase): @@ -130,6 +132,6 @@ class Loan(LoanBase): class LoanExtend(BaseModel): # The client can either provide a new end date or a timedelta to be added to the old end date. end: date | None = Field(None, description="A new return date for the Loan") - duration: timedelta | None = Field( - None, description="The duration by which the loan should be extended" + duration: int | None = Field( + None, description="The duration by which the loan should be extended in seconds" ) diff --git a/tests/test_loan.py b/tests/test_loan.py index e55a1e4ba..64f8b5821 100644 --- a/tests/test_loan.py +++ b/tests/test_loan.py @@ -62,7 +62,7 @@ async def init_objects(): loaner_id=loaner.id, suggested_lending_duration=timedelta( days=50, - ), + ).seconds, suggested_caution=10, total_quantity=8, loaner=loaner, @@ -75,7 +75,7 @@ async def init_objects(): loaner_id=loaner.id, suggested_lending_duration=timedelta( days=50, - ), + ).seconds, suggested_caution=10, total_quantity=5, loaner=loaner, From a60c3262dd7264bca1b354327ae63797f22a380b Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:54:06 +0100 Subject: [PATCH 10/16] Remove comment --- app/schemas/schemas_raffle.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/schemas/schemas_raffle.py b/app/schemas/schemas_raffle.py index 5d2fdb046..4956f6dd1 100644 --- a/app/schemas/schemas_raffle.py +++ b/app/schemas/schemas_raffle.py @@ -1,5 +1,3 @@ -"""Schemas file for endpoint /amap""" - from pydantic import BaseModel, ConfigDict from app.schemas.schemas_core import CoreUserSimple From 24fbb5f0d00ce139482e13389c67d9f7b195289d Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:16:21 +0100 Subject: [PATCH 11/16] Use Pydantic v2 computed_field for settings --- app/core/config.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index c413935f4..4baba51f5 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,8 +1,9 @@ from functools import cached_property +from typing import Any from jose import jwk from jose.exceptions import JWKError -from pydantic import model_validator +from pydantic import computed_field, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict from app.utils.auth import providers @@ -159,16 +160,19 @@ class Settings(BaseSettings): # The combination of `@property` and `@lru_cache` should be replaced by `@cached_property` # See https://docs.python.org/3.8/library/functools.html?highlight=#functools.cached_property + @computed_field @cached_property - def RSA_PRIVATE_KEY(cls): + def RSA_PRIVATE_KEY(cls) -> Any: return jwk.construct(cls.RSA_PRIVATE_PEM_STRING, algorithm="RS256") + @computed_field @cached_property - def RSA_PUBLIC_KEY(cls): + def RSA_PUBLIC_KEY(cls) -> Any: return cls.RSA_PRIVATE_KEY.public_key() + @computed_field @cached_property - def RSA_PUBLIC_JWK(cls): + def RSA_PUBLIC_JWK(cls) -> dict[str, dict[str, str]]: JWK = cls.RSA_PUBLIC_KEY.to_dict() JWK.update( { @@ -184,6 +188,7 @@ def RSA_PUBLIC_JWK(cls): # This property parse AUTH_CLIENTS to create a dictionary of auth clients: # {"client_id": AuthClientClassInstance} + @computed_field @cached_property def KNOWN_AUTH_CLIENTS(cls) -> dict[str, providers.BaseAuthClient]: clients = {} From 851ccda78d782eb8bdc98a78859422fd26c239b0 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:18:19 +0100 Subject: [PATCH 12/16] Add a validator to init cached property on startup --- app/core/config.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/core/config.py b/app/core/config.py index 4baba51f5..dab619148 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -258,3 +258,17 @@ def check_secrets(self) -> "Settings": raise ValueError("RSA_PRIVATE_PEM_STRING is not a valid RSA key", e) return self + + @model_validator(mode="after") + def init_cached_property(self) -> "Settings": + """ + Cached property are not computed during the instantiation of the class, but when they are accessed for the first time. + By calling them in this validator, we force their initialization during the instantiation of the class. + This allow them to raise error on Hyperion startup if they are not correctly configured instead of creating an error on runtime. + """ + self.KNOWN_AUTH_CLIENTS + self.RSA_PRIVATE_KEY + self.RSA_PUBLIC_KEY + self.RSA_PUBLIC_JWK + + return self From 59ad7f5636b700921b2a168d4afe79197a7484a3 Mon Sep 17 00:00:00 2001 From: armanddidierjean <95971503+armanddidierjean@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:25:47 +0100 Subject: [PATCH 13/16] Ignore mypy error See https://github.com/python/mypy/issues/1362 and https://docs.pydantic.dev/2.0/usage/computed_fields/ --- app/core/config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index dab619148..9db8eaee8 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -160,19 +160,19 @@ class Settings(BaseSettings): # The combination of `@property` and `@lru_cache` should be replaced by `@cached_property` # See https://docs.python.org/3.8/library/functools.html?highlight=#functools.cached_property - @computed_field + @computed_field # type: ignore[misc] # Current issue with mypy, see https://docs.pydantic.dev/2.0/usage/computed_fields/ and https://github.com/python/mypy/issues/1362 @cached_property def RSA_PRIVATE_KEY(cls) -> Any: return jwk.construct(cls.RSA_PRIVATE_PEM_STRING, algorithm="RS256") - @computed_field + @computed_field # type: ignore[misc] @cached_property def RSA_PUBLIC_KEY(cls) -> Any: return cls.RSA_PRIVATE_KEY.public_key() - @computed_field + @computed_field # type: ignore[misc] @cached_property - def RSA_PUBLIC_JWK(cls) -> dict[str, dict[str, str]]: + def RSA_PUBLIC_JWK(cls) -> dict[str, list[dict[str, str]]]: JWK = cls.RSA_PUBLIC_KEY.to_dict() JWK.update( { @@ -188,7 +188,7 @@ def RSA_PUBLIC_JWK(cls) -> dict[str, dict[str, str]]: # This property parse AUTH_CLIENTS to create a dictionary of auth clients: # {"client_id": AuthClientClassInstance} - @computed_field + @computed_field # type: ignore[misc] @cached_property def KNOWN_AUTH_CLIENTS(cls) -> dict[str, providers.BaseAuthClient]: clients = {} From 6e7bac94cd530a9a39ae74c3d1ddd964d5f7de7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Maistre?= Date: Tue, 20 Feb 2024 19:01:54 +0100 Subject: [PATCH 14/16] Update fastapi to 0.109.2 and fix bug introduced by https://github.com/encode/starlette/pull/2377 --- app/app.py | 1 + requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/app.py b/app/app.py index 8e6016335..b57bba0b2 100644 --- a/app/app.py +++ b/app/app.py @@ -279,6 +279,7 @@ async def logging_middleware( port = request.client.port client_address = f"{ip_address}:{port}" else: + ip_address = "0.0.0.0" # In case of a test (see https://github.com/encode/starlette/pull/2377) client_address = "unknown" settings: Settings = app.dependency_overrides.get(get_settings, get_settings)() diff --git a/requirements.txt b/requirements.txt index 25f366b29..919726959 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ alembic==1.13.1 # database migrations -fastapi==0.109.1 +fastapi==0.109.2 Jinja2==3.1.3 # template engine for html files bcrypt==4.0.1 # password hashing pydantic-settings==2.2.1 From 0837507cfd57a265c43c52f5d65183c953df926f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Maistre?= Date: Wed, 21 Feb 2024 08:49:06 +0100 Subject: [PATCH 15/16] Add migration file --- .../versions/f17e6182b0a9_pydantic_v2.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 migrations/versions/f17e6182b0a9_pydantic_v2.py diff --git a/migrations/versions/f17e6182b0a9_pydantic_v2.py b/migrations/versions/f17e6182b0a9_pydantic_v2.py new file mode 100644 index 000000000..166d8d38c --- /dev/null +++ b/migrations/versions/f17e6182b0a9_pydantic_v2.py @@ -0,0 +1,39 @@ +"""pydantic_v2 + +Revision ID: f17e6182b0a9 +Revises: 28aa5ef44bf3 +Create Date: 2024-02-20 19:13:26.528768 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "f17e6182b0a9" +down_revision: Union[str, None] = "28aa5ef44bf3" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.alter_column( + "loaner_item", + "suggested_lending_duration", + existing_type=sa.Interval(), + type_=sa.Integer(), + postgresql_using="extract(EPOCH FROM suggested_lending_duration)", + ) + + +def downgrade() -> None: + op.alter_column( + "loaner_item", + "suggested_lending_duration", + existing_type=sa.Integer(), + type_=sa.Interval(), + postgresql_using="suggested_lending_duration * INTERVAL '1 second'", + ) From 864f07663b0e1d045e2a7aeeb11b0f05bcb09b20 Mon Sep 17 00:00:00 2001 From: Rotheem <114694873+Rotheem@users.noreply.github.com> Date: Fri, 23 Feb 2024 08:27:59 +0100 Subject: [PATCH 16/16] fix : Rename migration file using current naming system --- .../versions/{f17e6182b0a9_pydantic_v2.py => 3-pydantic_v2.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename migrations/versions/{f17e6182b0a9_pydantic_v2.py => 3-pydantic_v2.py} (100%) diff --git a/migrations/versions/f17e6182b0a9_pydantic_v2.py b/migrations/versions/3-pydantic_v2.py similarity index 100% rename from migrations/versions/f17e6182b0a9_pydantic_v2.py rename to migrations/versions/3-pydantic_v2.py