diff --git a/README.md b/README.md index d22af68..a3ccbce 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # DF Wallet User Service -Developers Foundry Wallet User Service System +Developers Foundry Wallet User Service   -[![Version: v1.2.0](https://img.shields.io/badge/api-v1.2.0-blue?style=flat&logo=money)](CHANGELOG.md) +[![Version: v1.0.0](https://img.shields.io/badge/api-v1.0.0-blue?style=flat&logo=money)](CHANGELOG.md) [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) @@ -18,8 +18,8 @@ The Swagger documentation for the application is hosted on [Render](https://df-u - Clone the repository ```bash - git clone https://github.com/Ifechukwu001/finapp.git finapp - cd finapp + git clone https://github.com/Developer-s-Foundry/user-service.git user-service + cd user-service ``` - Setup UV. \ diff --git a/pyproject.toml b/pyproject.toml index e67d750..599f306 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "df-wallet-user-service" dynamic = ["version"] -description = "Developers Foundry Wallet System" +description = "Developers Foundry Wallet User Service" readme = "README.md" requires-python = ">=3.12" dependencies = [ diff --git a/src/__init__.py b/src/__init__.py index ccb0b3a..015ef6e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,5 +1,4 @@ __name__ = "df-wallet-user-service" __display_name__ = "DF Wallet User Service API" -__description__ = "Developers Foundry Wallet System User Service RESTful API" -__version__ = "1.2.0" -__author__ = "Ogidi Ifechukwu (ogidiifechukwu6@gmail.com)" +__description__ = "Developers Foundry Wallet User Service RESTful API" +__version__ = "1.0.0" diff --git a/src/api/controllers/AuthController.py b/src/api/controllers/AuthController.py deleted file mode 100644 index eb5cbd3..0000000 --- a/src/api/controllers/AuthController.py +++ /dev/null @@ -1,69 +0,0 @@ -from http import HTTPStatus -from typing import Annotated - -from src.utils.svcs import Service -from src.utils.logger import Logger -from src.api.constants.messages import MESSAGES -from src.api.services.AuthService import AuthService -from src.api.utils.response_format import error_response, success_response -from src.api.models.payload.requests.ResendUserOtp import ResendUserOtp -from src.api.models.payload.requests.CreateUserRequest import CreateUserRequest -from src.api.models.payload.requests.AuthenticateUserOtp import AuthenticateUserOtp -from src.api.models.payload.requests.AuthenticateUserRequest import ( - AuthenticateUserRequest, -) - - -@Service() -class AuthController: - def __init__( - self, logger: Annotated[Logger, "AuthController"], auth_service: AuthService - ) -> None: - self.logger = logger - self.auth_service = auth_service - - async def register(self, user_data: CreateUserRequest) -> tuple: - user_exists = await self.auth_service.register(user_data) - if user_exists["is_exists"]: - return error_response( - user_exists["message"], status_code=HTTPStatus.BAD_REQUEST - ) - return success_response( - message=user_exists["message"], - data=user_exists["user"], - status_code=HTTPStatus.CREATED, - ) - - async def resend_email(self, credentials: ResendUserOtp) -> tuple: - email_message = await self.auth_service.resend_email(credentials) - if not email_message["is_success"]: - return error_response( - email_message["message"], status_code=HTTPStatus.BAD_REQUEST - ) - return success_response( - message=email_message["message"], status_code=HTTPStatus.OK - ) - - async def validate_email(self, credentials: AuthenticateUserOtp) -> tuple: - is_valid = await self.auth_service.validate_email(credentials) - if not is_valid: - return error_response( - MESSAGES["REGISTRATION"]["VERIFICATION_FAILED"], - status_code=HTTPStatus.BAD_REQUEST, - ) - return success_response( - message=MESSAGES["REGISTRATION"]["VERIFICATION_SUCCESS"], - status_code=HTTPStatus.OK, - ) - - async def login(self, credentials: AuthenticateUserRequest) -> tuple: - auth_user = await self.auth_service.login(credentials) - if not auth_user["is_success"]: - return error_response( - message=auth_user["message"], status_code=HTTPStatus.BAD_REQUEST - ) - return success_response( - message=auth_user["message"], - data={"user": auth_user["user"], "token": auth_user["token"]}, - status_code=HTTPStatus.OK, - ) diff --git a/src/api/controllers/PasswordResetController.py b/src/api/controllers/PasswordResetController.py deleted file mode 100644 index 0b6d6d2..0000000 --- a/src/api/controllers/PasswordResetController.py +++ /dev/null @@ -1,66 +0,0 @@ -from http import HTTPStatus -from typing import Annotated - -from ninja.errors import HttpError - -from src.utils.svcs import Service -from src.utils.logger import Logger -from src.api.utils.response_format import success_response -from src.api.services.PasswordResetService import PasswordResetService -from src.api.models.payload.requests.PasswordResetRequest import ( - PasswordResetRequest, - ConfirmPasswordResetRequest, -) - - -@Service() -class PasswordResetController: - def __init__( - self, - logger: Annotated[Logger, "PasswordResetController"], - password_reset_service: PasswordResetService, - ) -> None: - self.logger = logger - self.password_reset_service = password_reset_service - - async def request_password_reset(self, user_data: PasswordResetRequest) -> tuple: - try: - sent_request = await self.password_reset_service.request_password_reset( - user_data - ) - return success_response( - message=sent_request["message"], status_code=HTTPStatus.OK - ) - except Exception as exc: - if isinstance(exc, HttpError): - raise - self.logger.error( - { - "activity_type": "Request for password reset", - "message": str(exc), - "metadata": user_data.model_dump(), - } - ) - raise HttpError(HTTPStatus.INTERNAL_SERVER_ERROR, "Something went wrong") - - async def confirm_password_reset( - self, user_data: ConfirmPasswordResetRequest - ) -> tuple: - try: - sent_request = await self.password_reset_service.confirm_password_reset( - user_data - ) - return success_response( - message=sent_request["message"], status_code=HTTPStatus.OK - ) - except Exception as exc: - if isinstance(exc, HttpError): - raise - self.logger.error( - { - "activity_type": "Confirm password reset", - "message": str(exc), - "metadata": user_data.model_dump(), - } - ) - raise HttpError(HTTPStatus.INTERNAL_SERVER_ERROR, "Something went wrong") diff --git a/src/api/controllers/UserController.py b/src/api/controllers/UserController.py index eff4d76..51e0c22 100644 --- a/src/api/controllers/UserController.py +++ b/src/api/controllers/UserController.py @@ -7,10 +7,7 @@ from src.api.services.UserService import UserService from src.api.utils.response_format import error_response, success_response from src.api.models.payload.requests.Pin import Pin -from src.api.models.payload.requests.UpdateUserRequest import ( - UpdateUserRequest, - ChangeUserPasswordRequest, -) +from src.api.models.payload.requests.UpdateUserRequest import UpdateUserRequest @Service() @@ -55,16 +52,3 @@ async def set_account_pin(self, id: str, user_pin: Pin) -> tuple: return success_response( message=updated_pin["message"], status_code=HTTPStatus.OK ) - - async def change_password( - self, id: str, user_data: ChangeUserPasswordRequest - ) -> tuple: - updated_password = await self.user_service.change_password(id, user_data) - if not updated_password["is_success"]: - return error_response( - message=updated_password["message"], status_code=HTTPStatus.BAD_REQUEST - ) - return success_response( - message=updated_password["message"], - status_code=HTTPStatus.OK, - ) diff --git a/src/api/migrations/0001_initial.py b/src/api/migrations/0001_initial.py index 37ee444..b959326 100644 --- a/src/api/migrations/0001_initial.py +++ b/src/api/migrations/0001_initial.py @@ -1,7 +1,6 @@ -# Generated by Django 5.1.6 on 2025-03-31 13:40 +# Generated by Django 5.1.8 on 2025-04-03 15:46 import django.db.models.deletion -import uuid from django.db import migrations, models @@ -43,9 +42,8 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('id', models.CharField(default=uuid.uuid4, max_length=255, primary_key=True, serialize=False)), + ('id', models.CharField(max_length=255, primary_key=True, serialize=False)), ('email', models.EmailField(max_length=255, unique=True)), - ('password', models.CharField(max_length=255)), ('first_name', models.CharField(max_length=255)), ('last_name', models.CharField(max_length=255)), ('address', models.CharField(max_length=255)), @@ -53,8 +51,6 @@ class Migration(migrations.Migration): ('profile_picture', models.CharField(max_length=255)), ('tier', models.IntegerField(default=1)), ('pin', models.CharField(max_length=10)), - ('password_reset_token', models.CharField(max_length=255)), - ('token_expires_at', models.DateTimeField(null=True)), ('is_validated', models.BooleanField(default=False)), ('is_active', models.BooleanField(default=False)), ('is_enabled', models.BooleanField(default=False)), diff --git a/src/api/migrations/0002_otp.py b/src/api/migrations/0002_otp.py deleted file mode 100644 index ce137d7..0000000 --- a/src/api/migrations/0002_otp.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.1.6 on 2025-03-31 20:59 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Otp', - fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('key', models.CharField(max_length=255)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.user')), - ], - options={ - 'db_table': 'otps', - 'indexes': [models.Index(fields=['key'], name='otps_key_496193_idx'), models.Index(fields=['created_at'], name='otps_created_b35e98_idx')], - }, - ), - ] diff --git a/src/api/models/payload/requests/AuthenticateUserOtp.py b/src/api/models/payload/requests/AuthenticateUserOtp.py deleted file mode 100644 index 89cc8b1..0000000 --- a/src/api/models/payload/requests/AuthenticateUserOtp.py +++ /dev/null @@ -1,6 +0,0 @@ -from pydantic import EmailStr, BaseModel - - -class AuthenticateUserOtp(BaseModel): - email: EmailStr - otp: str diff --git a/src/api/models/payload/requests/AuthenticateUserRequest.py b/src/api/models/payload/requests/AuthenticateUserRequest.py deleted file mode 100644 index db8de27..0000000 --- a/src/api/models/payload/requests/AuthenticateUserRequest.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import EmailStr, BaseModel - -from src.api.typing.PasswordValidator import IsStrongPassword - - -class AuthenticateUserRequest(BaseModel): - email: EmailStr - password: IsStrongPassword diff --git a/src/api/models/payload/requests/PasswordResetRequest.py b/src/api/models/payload/requests/PasswordResetRequest.py deleted file mode 100644 index e2aa6a0..0000000 --- a/src/api/models/payload/requests/PasswordResetRequest.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import EmailStr, BaseModel - -from src.api.typing.PasswordValidator import IsStrongPassword - - -class PasswordResetRequest(BaseModel): - email: EmailStr - - -class ConfirmPasswordResetRequest(BaseModel): - reset_token: str - new_password: IsStrongPassword diff --git a/src/api/models/payload/requests/ResendUserOtp.py b/src/api/models/payload/requests/ResendUserOtp.py deleted file mode 100644 index 7213a06..0000000 --- a/src/api/models/payload/requests/ResendUserOtp.py +++ /dev/null @@ -1,5 +0,0 @@ -from pydantic import EmailStr, BaseModel - - -class ResendUserOtp(BaseModel): - email: EmailStr diff --git a/src/api/models/payload/requests/UpdateUserRequest.py b/src/api/models/payload/requests/UpdateUserRequest.py index 2676715..f3023e0 100644 --- a/src/api/models/payload/requests/UpdateUserRequest.py +++ b/src/api/models/payload/requests/UpdateUserRequest.py @@ -2,8 +2,6 @@ from pydantic import Field, BaseModel -from src.api.typing.PasswordValidator import IsStrongPassword - class UpdateUserRequest(BaseModel): first_name: str | None = None @@ -11,8 +9,3 @@ class UpdateUserRequest(BaseModel): address: str | None = None phone_number: str | None = None state_lga_id: Annotated[int, Field(default=None, ge=1)] - - -class ChangeUserPasswordRequest(BaseModel): - old_password: str - new_password: IsStrongPassword diff --git a/src/api/models/postgres/Otp.py b/src/api/models/postgres/Otp.py deleted file mode 100644 index 102a961..0000000 --- a/src/api/models/postgres/Otp.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.db import models - -from .User import User -from ._base import PostgresBaseModel - - -class Otp(PostgresBaseModel): - id: models.BigAutoField = models.BigAutoField(primary_key=True) - user: models.ForeignKey = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True - ) - key: models.CharField = models.CharField(max_length=255) - created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True) - - class Meta: - db_table = "otps" - indexes = ( - models.Index(fields=["key"]), - models.Index(fields=["created_at"]), - ) - - def __str__(self) -> str: - return str(self.id) diff --git a/src/api/models/postgres/User.py b/src/api/models/postgres/User.py index cc3b9da..d8d5a3c 100644 --- a/src/api/models/postgres/User.py +++ b/src/api/models/postgres/User.py @@ -1,5 +1,3 @@ -import uuid - from django.db import models from ._base import PostgresBaseModel @@ -7,13 +5,8 @@ class User(PostgresBaseModel): - id: models.CharField = models.CharField( - max_length=255, - primary_key=True, - default=uuid.uuid4, # type: ignore - ) + id: models.CharField = models.CharField(max_length=255, primary_key=True) email: models.EmailField = models.EmailField(max_length=255, unique=True) - password: models.CharField = models.CharField(max_length=255) first_name: models.CharField = models.CharField(max_length=255) last_name: models.CharField = models.CharField(max_length=255) address: models.CharField = models.CharField(max_length=255) @@ -24,8 +17,6 @@ class User(PostgresBaseModel): profile_picture: models.CharField = models.CharField(max_length=255) tier: models.IntegerField = models.IntegerField(default=1) pin: models.CharField = models.CharField(max_length=10) - password_reset_token: models.CharField = models.CharField(max_length=255) - token_expires_at: models.DateTimeField = models.DateTimeField(null=True) is_validated: models.BooleanField = models.BooleanField(default=False) is_active: models.BooleanField = models.BooleanField(default=False) is_enabled: models.BooleanField = models.BooleanField(default=False) diff --git a/src/api/models/postgres/__init__.py b/src/api/models/postgres/__init__.py index c8d78c8..7e3a593 100644 --- a/src/api/models/postgres/__init__.py +++ b/src/api/models/postgres/__init__.py @@ -1,4 +1,3 @@ -from .Otp import Otp from .Bank import Bank from .User import User from .StateLGA import StateLGA @@ -8,7 +7,6 @@ __all__ = [ "Bank", - "Otp", "StateLGA", "User", "UserKYCInformation", diff --git a/src/api/repositories/OtpRepository.py b/src/api/repositories/OtpRepository.py deleted file mode 100644 index 9a49c96..0000000 --- a/src/api/repositories/OtpRepository.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import timedelta - -from django.utils import timezone - -from src.api.models.postgres import Otp, User - -from ._base import BaseRepository - - -class OtpRepository(BaseRepository[Otp]): - model = Otp - - @classmethod - async def add(cls, key: str, user: User | None = None) -> Otp: - return await cls.manager.acreate(key=key, user=user) - - @classmethod - async def find_by_key(cls, key: str) -> Otp | None: - return await cls.manager.filter(key=key).afirst() - - @classmethod - async def find_valid_key(cls, key: str, lifetime: timedelta) -> Otp | None: - return await cls.manager.filter( - key=key, created_at__gte=timezone.now() - lifetime - ).afirst() - - @classmethod - async def find_valid_user_key(cls, user: User, lifetime: timedelta) -> Otp | None: - return await cls.manager.filter( - user=user, created_at__gte=timezone.now() - lifetime - ).afirst() diff --git a/src/api/repositories/PasswordResetRepository.py b/src/api/repositories/PasswordResetRepository.py deleted file mode 100644 index c416268..0000000 --- a/src/api/repositories/PasswordResetRepository.py +++ /dev/null @@ -1,18 +0,0 @@ -from uuid import UUID -from datetime import datetime - -from src.api.models.postgres import User - -from ._base import BaseRepository - - -class PasswordResetRepository(BaseRepository[User]): - model = User - - @classmethod - async def set_new_password_reset_token( - cls, user: User, token: UUID, expires_at: datetime - ) -> None: - user.password_reset_token = token - user.token_expires_at = expires_at - await user.asave() diff --git a/src/api/repositories/UserRepository.py b/src/api/repositories/UserRepository.py index 8c77f86..e41a6d5 100644 --- a/src/api/repositories/UserRepository.py +++ b/src/api/repositories/UserRepository.py @@ -39,7 +39,3 @@ async def update_by_id(cls, id: str, updates: dict | None = None) -> User | None setattr(user, key, value) await user.asave() return user - - @classmethod - async def find_by_reset_token(cls, reset_token: str) -> User | None: - return await cls.manager.filter(password_reset_token=reset_token).afirst() diff --git a/src/api/routes/Auth.py b/src/api/routes/Auth.py deleted file mode 100644 index 4ee41a5..0000000 --- a/src/api/routes/Auth.py +++ /dev/null @@ -1,75 +0,0 @@ -from http import HTTPStatus - -from ninja import Router -from django.http import HttpRequest - -from src.utils.svcs import ADepends -from src.api.controllers.AuthController import AuthController -from src.api.models.payload.responses.User import UserResponse, UserLoginResponse -from src.api.models.payload.requests.ResendUserOtp import ResendUserOtp -from src.api.models.payload.responses.ErrorResponse import ( - ErrorResponse, - ServerErrorResponse, -) -from src.api.models.payload.responses.SuccessResponse import SuccessResponse -from src.api.models.payload.requests.CreateUserRequest import CreateUserRequest -from src.api.models.payload.requests.AuthenticateUserOtp import AuthenticateUserOtp -from src.api.models.payload.requests.AuthenticateUserRequest import ( - AuthenticateUserRequest, -) - -router = Router() - - -@router.post( - "/register", - response={ - HTTPStatus.CREATED: SuccessResponse[UserResponse], - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def create_user(request: HttpRequest, user_data: CreateUserRequest) -> tuple: - auth_controller = await ADepends(AuthController) - return await auth_controller.register(user_data) - - -@router.put( - "/email/resend", - response={ - HTTPStatus.OK: SuccessResponse, - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def resend_email(request: HttpRequest, credentials: ResendUserOtp) -> tuple: - auth_controller = await ADepends(AuthController) - return await auth_controller.resend_email(credentials) - - -@router.put( - "/email/verification", - response={ - HTTPStatus.OK: SuccessResponse, - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def validate_email( - request: HttpRequest, credentials: AuthenticateUserOtp -) -> tuple: - auth_controller = await ADepends(AuthController) - return await auth_controller.validate_email(credentials) - - -@router.post( - "/login", - response={ - HTTPStatus.OK: SuccessResponse[UserLoginResponse], - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def login(request: HttpRequest, credentials: AuthenticateUserRequest) -> tuple: - auth_controller = await ADepends(AuthController) - return await auth_controller.login(credentials) diff --git a/src/api/routes/PasswordReset.py b/src/api/routes/PasswordReset.py deleted file mode 100644 index 701372d..0000000 --- a/src/api/routes/PasswordReset.py +++ /dev/null @@ -1,51 +0,0 @@ -from http import HTTPStatus - -from ninja import Router -from django.http import HttpRequest - -from src.utils.svcs import ADepends -from src.api.controllers.PasswordResetController import PasswordResetController -from src.api.models.payload.responses.ErrorResponse import ( - ErrorResponse, - ServerErrorResponse, -) -from src.api.models.payload.responses.SuccessResponse import SuccessResponse -from src.api.models.payload.requests.PasswordResetRequest import ( - PasswordResetRequest, - ConfirmPasswordResetRequest, -) - -router = Router() - - -@router.post( - "/", - response={ - HTTPStatus.OK: SuccessResponse, - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def reset_password( - request: HttpRequest, credentials: PasswordResetRequest -) -> tuple: - reset_controller = await ADepends(PasswordResetController) - return await reset_controller.request_password_reset(credentials) - - -# TODO: Implement router for password reset confirm and change password - - -@router.post( - "/confirm", - response={ - HTTPStatus.OK: SuccessResponse, - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def confirm_password_reset( - request: HttpRequest, credentials: ConfirmPasswordResetRequest -) -> tuple: - reset_controller = await ADepends(PasswordResetController) - return await reset_controller.confirm_password_reset(credentials) diff --git a/src/api/routes/User.py b/src/api/routes/User.py index b79c95f..f71d1be 100644 --- a/src/api/routes/User.py +++ b/src/api/routes/User.py @@ -12,10 +12,7 @@ ServerErrorResponse, ) from src.api.models.payload.responses.SuccessResponse import SuccessResponse -from src.api.models.payload.requests.UpdateUserRequest import ( - UpdateUserRequest, - ChangeUserPasswordRequest, -) +from src.api.models.payload.requests.UpdateUserRequest import UpdateUserRequest router = Router() @@ -60,19 +57,3 @@ async def update_user(request: HttpRequest, user_data: UpdateUserRequest) -> tup user_id = getattr(request, "auth_id", "") user_controller = await ADepends(UserController) return await user_controller.update_user(user_id, user_data) - - -@router.put( - "/change-password", - response={ - HTTPStatus.OK: SuccessResponse, - HTTPStatus.BAD_REQUEST: ErrorResponse, - HTTPStatus.INTERNAL_SERVER_ERROR: ServerErrorResponse, - }, -) -async def update_password( - request: HttpRequest, user_data: ChangeUserPasswordRequest -) -> tuple: - user_id = getattr(request, "auth_id", "") - user_controller = await ADepends(UserController) - return await user_controller.change_password(user_id, user_data) diff --git a/src/api/routes/__init__.py b/src/api/routes/__init__.py index e369a07..9347391 100644 --- a/src/api/routes/__init__.py +++ b/src/api/routes/__init__.py @@ -18,10 +18,6 @@ async def home(request: HttpRequest) -> dict: return {"message": "Hello, World!"} -api.add_router("/auth", "src.api.routes.Auth.router", tags=["Auth"]) -api.add_router( - "/password/reset", "src.api.routes.PasswordReset.router", tags=["Password"] -) api.add_router( "/users", "src.api.routes.User.router", auth=authentication, tags=["User"] ) diff --git a/src/api/services/AuthService.py b/src/api/services/AuthService.py deleted file mode 100644 index 29c52aa..0000000 --- a/src/api/services/AuthService.py +++ /dev/null @@ -1,231 +0,0 @@ -from typing import Annotated - -from src.utils.svcs import Service -from src.utils.logger import Logger -from src.api.typing.UserExists import UserExists -from src.api.constants.messages import MESSAGES -from src.api.typing.UserSuccess import UserSuccess -from src.api.constants.activity_types import ACTIVITY_TYPES -from src.api.repositories.UserRepository import UserRepository -from src.api.models.payload.requests.ResendUserOtp import ResendUserOtp -from src.api.models.payload.requests.CreateUserRequest import CreateUserRequest -from src.api.models.payload.requests.AuthenticateUserOtp import AuthenticateUserOtp -from src.api.models.payload.requests.AuthenticateUserRequest import ( - AuthenticateUserRequest, -) - -from .OtpService import OtpService -from .UtilityService import UtilityService - - -@Service() -class AuthService: - def __init__( - self, - logger: Annotated[Logger, "AuthService"], - otp_service: OtpService, - utility_service: UtilityService, - ) -> None: - self.logger = logger - self.otp_service = otp_service - self.utility_service = utility_service - - async def register(self, req: CreateUserRequest) -> UserExists: - email = req.email - password = req.password - - existing_user = await UserRepository.find_by_email(email) - if existing_user: - message = MESSAGES["REGISTRATION"]["EMAIL_EXISTS"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_REGISTRATION"], - "message": message, - "metadata": {"user": {"email": existing_user.email}}, - } - ) - return { - "is_exists": True, - "user": existing_user, - "message": message, - } - - hashed_password: str = await self.utility_service.hash_string(password) - req = req.model_copy(update={"password": hashed_password}) - - created_user = await UserRepository.add(req) - - await self.otp_service.send_otp(created_user.id) - - user = self.utility_service.sanitize_user_object(created_user) - - message = MESSAGES["REGISTRATION"]["USER_REGISTERED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_REGISTRATION"], - "message": message, - "metadata": {"user": {"email": user.email}}, - } - ) - return { - "is_exists": False, - "user": user, - "message": message, - } - - async def resend_email(self, req: ResendUserOtp) -> UserSuccess: - email = req.email - - user = await UserRepository.find_by_email(email) - if not user: - self.logger.error( - { - "activity_type": ACTIVITY_TYPES["RESEND_EMAIL"], - "message": MESSAGES["USER"]["DOESNT_EXIST"], - "metadata": {"email": email}, - } - ) - return {"is_success": False, "message": MESSAGES["USER"]["DOESNT_EXIST"]} - - otp_success = await self.otp_service.send_otp(user.id) - return { - "is_success": otp_success["is_success"], - "message": otp_success["message"], - } - - async def validate_email(self, req: AuthenticateUserOtp) -> bool: - email = req.email - otp = req.otp - - user = await UserRepository.find_by_email(email) - if not user: - self.logger.error( - { - "activity_type": ACTIVITY_TYPES["EMAIL_VALIDATION"], - "message": MESSAGES["USER"]["DOESNT_EXIST"], - "metadata": {"email": email, "otp": otp}, - } - ) - return False - - is_valid = await self.otp_service.validate_otp(user.id, otp) - if not is_valid: - return False - - await UserRepository.update_by_user( - user, {"is_active": True, "is_enabled": True, "is_validated": True} - ) - return True - - async def login(self, req: AuthenticateUserRequest) -> UserSuccess: - email = req.email - password = req.password - - existing_user = await UserRepository.find_by_email(email) - if not existing_user: - message = MESSAGES["AUTH"]["INVALID_CRED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": message, - "metadata": req.model_dump(), - } - ) - return {"is_success": False, "message": message} - - is_password_check_ok = await self.utility_service.compare_hash( - password, existing_user.password - ) - if not is_password_check_ok: - message = MESSAGES["AUTH"]["INVALID_CRED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": message, - "metadata": req.model_dump(), - } - ) - return {"is_success": False, "message": message} - - if not existing_user.is_validated: - await self.otp_service.send_otp(existing_user.id) - - message = MESSAGES["AUTH"]["NOT_VALIDATED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": message, - "metadata": req.model_dump(), - } - ) - return { - "is_success": False, - "message": message, - } - - if not existing_user.is_active: - message = MESSAGES["AUTH"]["NOT_ACTIVE"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": message, - "metadata": req.model_dump(), - } - ) - return { - "is_success": False, - "message": message, - } - - if not existing_user.is_enabled: - message = MESSAGES["AUTH"]["NOT_ENABLED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": message, - "metadata": req.model_dump(), - } - ) - return { - "is_success": False, - "message": message, - } - - if existing_user.is_deleted: - message = MESSAGES["AUTH"]["IS_DELETED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": message, - "metadata": req.model_dump(), - } - ) - return { - "is_success": False, - "message": message, - } - - user = self.utility_service.sanitize_user_object(existing_user) - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": MESSAGES["USER"]["SANITIZED"], - "metadata": {"email": email}, - } - ) - - jwt_details = self.utility_service.generate_jwt(user.email, user.id) - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["USER_LOGIN"], - "message": MESSAGES["COMMON"]["JWT_GENERATED"], - "metadata": {"email": email}, - } - ) - - return { - "is_success": True, - "message": MESSAGES["AUTH"]["SUCCESS"], - "user": user, - "token": jwt_details, - } diff --git a/src/api/services/OtpService.py b/src/api/services/OtpService.py deleted file mode 100644 index 529cb48..0000000 --- a/src/api/services/OtpService.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import Annotated -from datetime import timedelta - -from src.env import otp -from src.utils.svcs import Service -from src.utils.logger import Logger -from src.api.typing.OTPSuccess import OTPSuccess -from src.api.constants.messages import MESSAGES -from src.api.constants.activity_types import ACTIVITY_TYPES -from src.api.repositories.OtpRepository import OtpRepository -from src.api.repositories.UserRepository import UserRepository - -from .UtilityService import UtilityService - -OTP_LIFETIME = timedelta(minutes=otp["lifetime"]) - - -@Service() -class OtpService: - def __init__( - self, logger: Annotated[Logger, "OtpService"], utility_service: UtilityService - ) -> None: - self.logger = logger - self.utility_service = utility_service - - async def send_otp(self, user_id: str) -> OTPSuccess: - existing_user = await UserRepository.find_by_id(user_id) - if not existing_user: - message = MESSAGES["USER"]["DOESNT_EXIST"] - self.logger.warn( - { - "activity_type": ACTIVITY_TYPES["SEND_OTP"], - "message": message, - "metadata": {"user": {"id": user_id}}, - } - ) - raise ValueError(message) - - if existing_user.is_validated: - message = MESSAGES["OTP"]["USER_VALIDATED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["SEND_OTP"], - "message": message, - "metadata": {"user": {"id": user_id}}, - } - ) - return {"is_success": False, "message": message} - - otp = await OtpRepository.find_valid_user_key(existing_user, OTP_LIFETIME) - if not otp: - key = self.utility_service.generate_random_string( - length=6, numeric_only=True - ) - otp = await OtpRepository.add(key, existing_user) - - otp # Process the otp notification to the user - - message = MESSAGES["OTP"]["SEND_SUCCESS"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["SEND_OTP"], - "message": message, - "metadata": {"user": {"id": user_id}, "otp": {"key": otp.key}}, - } - ) - return {"is_success": True, "message": message} - - async def validate_otp(self, user_id: str, key: str) -> bool: - user = await UserRepository.find_by_id(user_id) - if not user: - self.logger.error( - { - "activity_type": ACTIVITY_TYPES["VALIDATE_OTP"], - "message": MESSAGES["USER"]["DOESNT_EXIST"], - "metadata": {"user": {"id": user_id}}, - } - ) - return False - - otp = await OtpRepository.find_valid_user_key(user, OTP_LIFETIME) - if not otp: - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["VALIDATE_OTP"], - "message": MESSAGES["OTP"]["VALIDATE_FAIL"], - "metadata": {"user": {"id": user_id}, "key": key}, - } - ) - return False - - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["VALIDATE_OTP"], - "message": MESSAGES["OTP"]["VALIDATE_SUCCESS"], - "metadata": {"user": {"id": user_id}, "key": key}, - } - ) - return True diff --git a/src/api/services/PasswordResetService.py b/src/api/services/PasswordResetService.py deleted file mode 100644 index 94c9db2..0000000 --- a/src/api/services/PasswordResetService.py +++ /dev/null @@ -1,156 +0,0 @@ -from typing import Annotated - -from django.utils import timezone - -from src.utils.svcs import Service -from src.utils.logger import Logger -from src.api.constants.messages import MESSAGES, DYNAMIC_MESSAGES -from src.api.typing.UserSuccess import UserSuccess -from src.api.constants.activity_types import ACTIVITY_TYPES -from src.api.repositories.UserRepository import UserRepository -from src.api.repositories.PasswordResetRepository import PasswordResetRepository -from src.api.models.payload.requests.PasswordResetRequest import ( - PasswordResetRequest, - ConfirmPasswordResetRequest, -) - -from .UtilityService import UtilityService - - -@Service() -class PasswordResetService: - def __init__( - self, - logger: Annotated[Logger, "PasswordResetService"], - utility_service: UtilityService, - ) -> None: - self.logger = logger - self.utility_service = utility_service - - async def request_password_reset(self, req: PasswordResetRequest) -> UserSuccess: - existing_user = await UserRepository.find_by_email(req.email) - if not existing_user: - self.logger.warn( - { - "activity_type": ACTIVITY_TYPES["REQUEST_RESET_PASSWORD"], - "message": MESSAGES["USER"]["DOESNT_EXIST"], - "metadata": {"user": {"email": req.email}}, - } - ) - return { - "is_success": False, - "message": MESSAGES["PASSWORD_RESET"]["DOESNT_EXIST"], - } - elif not existing_user.is_active: - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["REQUEST_RESET_PASSWORD"], - "message": "User is not active", - "metadata": {"user": {"email": req.email}}, - } - ) - return { - "is_success": False, - "message": "You can only reset the password of an active account!", - } - elif not existing_user.is_enabled: - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["REQUEST_RESET_PASSWORD"], - "message": "User is not enabled", - "metadata": {"user": {"email": req.email}}, - } - ) - return { - "is_success": False, - "message": "You can only reset the password of an enabled account!", - } - password_reset_token = self.utility_service.generate_uuid() - uuid = password_reset_token["uuid"] - expires_at = password_reset_token["expires_at"] - await PasswordResetRepository.set_new_password_reset_token( - existing_user, uuid, expires_at - ) - - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["REQUEST_RESET_PASSWORD"], - "message": "Password reset token set", - "metadata": { - "user": {"email": req.email}, - "reset_token": existing_user.password_reset_token, - }, - } - ) - return { - "is_success": True, - "message": DYNAMIC_MESSAGES["PASSWORD_RESET"]["EMAIL_SENT"](req.email), - } - - async def confirm_password_reset( - self, req: ConfirmPasswordResetRequest - ) -> UserSuccess: - existing_user = await UserRepository.find_by_reset_token(req.reset_token) - if not existing_user: - self.logger.warn( - { - "activity_type": "Confirm password reset", - "message": "User doesn't exist", - "metadata": {"token": req.reset_token}, - } - ) - return { - "is_success": False, - "message": "Invalid password reset token!", - } - elif not existing_user.is_active: - self.logger.info( - { - "activity_type": "Confirm password reset", - "message": "User is not active", - "metadata": {"token": req.reset_token}, - } - ) - return { - "is_success": False, - "message": "You can only reset the password of an active account!", - } - elif not existing_user.is_enabled: - self.logger.info( - { - "activity_type": "Confirm password reset", - "message": "User is not enabled", - "metadata": {"token": req.reset_token}, - } - ) - return { - "is_success": False, - "message": "You can only reset the password of an enabled account!", - } - - if existing_user.token_expires_at < timezone.now(): - self.logger.warn( - { - "activity_type": "Confirm password reset", - "message": MESSAGES["PASSWORD_RESET"]["TOKEN_EXPIRED"], - "metadata": {"token": req.reset_token}, - } - ) - return { - "is_success": False, - "message": MESSAGES["PASSWORD_RESET"]["TOKEN_EXPIRED"], - } - new_password_hash = await self.utility_service.hash_string(req.new_password) - await UserRepository.update_by_user( - existing_user, {"password": new_password_hash} - ) - message = MESSAGES["PASSWORD_RESET"]["PASSWORD_RESET"] - self.logger.info( - { - "activity_type": "Confirm password reset", - "message": message, - "metadata": {"user": {"id": id}}, - } - ) - - return {"is_success": True, "message": message} diff --git a/src/api/services/UserService.py b/src/api/services/UserService.py index 3a7bb56..c3d32e5 100644 --- a/src/api/services/UserService.py +++ b/src/api/services/UserService.py @@ -7,10 +7,7 @@ from src.api.typing.UserSuccess import UserSuccess from src.api.constants.activity_types import ACTIVITY_TYPES from src.api.repositories.UserRepository import UserRepository -from src.api.models.payload.requests.UpdateUserRequest import ( - UpdateUserRequest, - ChangeUserPasswordRequest, -) +from src.api.models.payload.requests.UpdateUserRequest import UpdateUserRequest from .UtilityService import UtilityService @@ -115,49 +112,3 @@ async def update(self, id: str, req: UpdateUserRequest) -> UserSuccess: user = self.utility_service.sanitize_user_object(updated_user) return {"is_success": True, "user": user} - - async def change_password( - self, id: str, req: ChangeUserPasswordRequest - ) -> UserSuccess: - existing_user = await UserRepository.find_by_id(id) - old_password = req.old_password - new_password = req.new_password - - if not existing_user: - self.logger.warn( - { - "activity_type": ACTIVITY_TYPES["CHANGE_PASSWORD"], - "message": DYNAMIC_MESSAGES["COMMON"]["FETCHED_FAILED"]("User"), - "metadata": {"user": {"id": id}}, - } - ) - return {"is_success": False, "message": MESSAGES["USER"]["DOESNT_EXIST"]} - - is_valid_old_password = await self.utility_service.compare_hash( - old_password, existing_user.password - ) - if not is_valid_old_password: - message = MESSAGES["USER"]["INCORRECT_PASSWORD"] - self.logger.warn( - { - "activity_type": ACTIVITY_TYPES["CHANGE_PASSWORD"], - "message": message, - "metadata": {"user": {"id": id}}, - } - ) - return {"is_success": False, "message": message} - - new_password_hash = await self.utility_service.hash_string(new_password) - await UserRepository.update_by_user( - existing_user, {"password": new_password_hash} - ) - message = MESSAGES["USER"]["PASSWORD_CHANGED"] - self.logger.info( - { - "activity_type": ACTIVITY_TYPES["CHANGE_PASSWORD"], - "message": message, - "metadata": {"user": {"id": id}}, - } - ) - - return {"is_success": True, "message": message} diff --git a/src/api/services/UtilityService.py b/src/api/services/UtilityService.py index 07d3696..e03388c 100644 --- a/src/api/services/UtilityService.py +++ b/src/api/services/UtilityService.py @@ -60,7 +60,6 @@ def generate_random_string( @staticmethod def sanitize_user_object(user: User | None = None) -> User: # type: ignore if user: - del user.password del user.pin return user diff --git a/uv.lock b/uv.lock index 1bd1d81..462b666 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,4 @@ version = 1 -revision = 1 requires-python = ">=3.12" [[package]] @@ -22,11 +21,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, ] [[package]] @@ -90,6 +89,7 @@ wheels = [ [[package]] name = "df-wallet-user-service" +version = "1.0.0" source = { virtual = "." } dependencies = [ { name = "bcrypt" }, @@ -155,16 +155,16 @@ wheels = [ [[package]] name = "django" -version = "5.1.6" +version = "5.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, { name = "sqlparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/e4/901f54ee114a080371a49bd08fa688d301aaffd9751febaf4ae855fc8fcd/Django-5.1.6.tar.gz", hash = "sha256:1e39eafdd1b185e761d9fab7a9f0b9fa00af1b37b25ad980a8aa0dac13535690", size = 10700620 } +sdist = { url = "https://files.pythonhosted.org/packages/00/40/45adc1b93435d1b418654a734b68351bb6ce0a0e5e37b2f0e9aeb1a2e233/Django-5.1.8.tar.gz", hash = "sha256:42e92a1dd2810072bcc40a39a212b693f94406d0ba0749e68eb642f31dc770b4", size = 10723602 } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/6f/d2c216d00975e2604b10940937b0ba6b2c2d9b3cc0cc633e414ae3f14b2e/Django-5.1.6-py3-none-any.whl", hash = "sha256:8d203400bc2952fbfb287c2bbda630297d654920c72a73cc82a9ad7926feaad5", size = 8277066 }, + { url = "https://files.pythonhosted.org/packages/ec/0d/e6dd0ed898b920fec35c6eeeb9acbeb831fff19ad21c5e684744df1d4a36/Django-5.1.8-py3-none-any.whl", hash = "sha256:11b28fa4b00e59d0def004e9ee012fefbb1065a5beb39ee838983fd24493ad4f", size = 8277130 }, ] [[package]] @@ -182,28 +182,28 @@ wheels = [ [[package]] name = "django-mongodb-backend" -version = "5.1.0b0" +version = "5.1.0b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "pymongo" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/54/b8aa20aae84ba6f8ff4804f8f131a5af73e859853d4b03102807e7621a9f/django_mongodb_backend-5.1.0b0.tar.gz", hash = "sha256:9fee196da2c739b25f83f2898f5f386bf20dc55580cd6727fd5bad8e8a149887", size = 96440 } +sdist = { url = "https://files.pythonhosted.org/packages/b3/de/ec75a0fc44b2b65874d1ac295d4f89e209ed67869dd8e450685c0a06b84b/django_mongodb_backend-5.1.0b1.tar.gz", hash = "sha256:9f0233921a87671871962ec44257c944c0cc79cd8f9517551a519fdb04eb6bbb", size = 105009 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/65/e50d468970f8a1f748608b24da1a420bda05095d842876516f48ceed7cda/django_mongodb_backend-5.1.0b0-py3-none-any.whl", hash = "sha256:be0de84f557811fe3e99a9236f386765d5b4137de2ba47d5b9899ecab063f6f5", size = 74527 }, + { url = "https://files.pythonhosted.org/packages/a7/da/5860183316aa76058c5585d43f2a1373e2d49064cb3157f91a51818d3741/django_mongodb_backend-5.1.0b1-py3-none-any.whl", hash = "sha256:4422f9e696d2b31dbf0f5a575fa9e8c969ac7bbff45a6a7f73e755ce123636f4", size = 75109 }, ] [[package]] name = "django-ninja" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/77/89ee4ebaa5151b7d85cebaf8d6ec0b9e5074326c3ad8259c763763306d51/django_ninja-1.3.0.tar.gz", hash = "sha256:5b320e2dc0f41a6032bfa7e1ebc33559ae1e911a426f0c6be6674a50b20819be", size = 3702324 } +sdist = { url = "https://files.pythonhosted.org/packages/45/a6/f8c158358e5e892839929c342872456b42d5e06c325aef27b3e8569b8932/django_ninja-1.4.0.tar.gz", hash = "sha256:590bb1c8fc32f07bac1d5b5d404419205c41b4945b86d4237b0676174c1b6df7", size = 3707937 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/72/fd2589323b40893d3224e174eeec0c4ce5a42c7d2d384d11ba269ad4d050/django_ninja-1.3.0-py3-none-any.whl", hash = "sha256:f58096b6c767d1403dfd6c49743f82d780d7b9688d9302ecab316ac1fa6131bb", size = 2423381 }, + { url = "https://files.pythonhosted.org/packages/85/d0/defc86eff755cdf32c717b2ed7872a2b19da151b34e0749a9567ce7810f8/django_ninja-1.4.0-py3-none-any.whl", hash = "sha256:b64bb7f269d0c0a890b429802dffbe3689298eb1ed7dc324224f3b325e8f8871", size = 2425903 }, ] [[package]] @@ -271,23 +271,23 @@ wheels = [ [[package]] name = "faker" -version = "36.1.1" +version = "37.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/8f/40d002bed58bd6b79bf970505582b769fc975afcacc62c2fe1518d5729c2/faker-36.1.1.tar.gz", hash = "sha256:7cb2bbd4c8f040e4a340ae4019e9a48b6cf1db6a71bda4e5a61d8d13b7bef28d", size = 1874935 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/a6/b77f42021308ec8b134502343da882c0905d725a4d661c7adeaf7acaf515/faker-37.1.0.tar.gz", hash = "sha256:ad9dc66a3b84888b837ca729e85299a96b58fdaef0323ed0baace93c9614af06", size = 1875707 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/79/e13ae542f63ce40d02b0fe63809563b102f19ffa3b94e6062ee9286a7801/Faker-36.1.1-py3-none-any.whl", hash = "sha256:ad1f1be7fd692ec0256517404a9d7f007ab36ac5d4674082fa72404049725eaa", size = 1917865 }, + { url = "https://files.pythonhosted.org/packages/d7/a1/8936bc8e79af80ca38288dd93ed44ed1f9d63beb25447a4c59e746e01f8d/faker-37.1.0-py3-none-any.whl", hash = "sha256:dc2f730be71cb770e9c715b13374d80dbcee879675121ab51f9683d262ae9a1c", size = 1918783 }, ] [[package]] name = "filelock" -version = "3.17.0" +version = "3.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, ] [[package]] @@ -330,11 +330,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.7" +version = "2.6.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/d1/524aa3350f78bcd714d148ade6133d67d6b7de2cdbae7d99039c024c9a25/identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684", size = 99260 } +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/00/1fd4a117c6c93f2dcc5b7edaeaf53ea45332ef966429be566ca16c2beb94/identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0", size = 99097 }, + { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101 }, ] [[package]] @@ -373,16 +373,16 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, ] [[package]] name = "pre-commit" -version = "4.1.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -391,9 +391,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, ] [[package]] @@ -429,16 +429,17 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.11.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 }, + { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295 }, ] [package.optional-dependencies] @@ -448,41 +449,44 @@ email = [ [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 }, - { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 }, - { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 }, - { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 }, - { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 }, - { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 }, - { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 }, - { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 }, - { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 }, - { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 }, - { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 }, - { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 }, - { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, - { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, - { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, + { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, + { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, + { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, + { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, + { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, + { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, + { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, + { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, + { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, + { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, + { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, + { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, + { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, ] [[package]] @@ -496,53 +500,53 @@ wheels = [ [[package]] name = "pymongo" -version = "4.11.1" +version = "4.11.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/18/63fd06769a2f47842c374fc5d937445fe8dc2f31b3a859c8bf7df73daa14/pymongo-4.11.1.tar.gz", hash = "sha256:3757ce9257c3486eead45680a8895a0ed9ba27efaf1791fc0cf854367c21c638", size = 2054021 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/8a/81fdd61a0764c0ba1072cd70f67c7f4a83008ceaa61305e20add2ad580c6/pymongo-4.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f96683f1dec7d28f12fe43a4d5c0df35d6b80348a9fbf5aac47fa284332a1f92", size = 895365 }, - { url = "https://files.pythonhosted.org/packages/05/60/32910044b2329b7a580a1b4d4f895ecb9616cdffeb57c2d7622214659ac5/pymongo-4.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:157e6a722d051c4bab3e6bc34a1f80fc98101cf2d12139a94e51638d023198c5", size = 895061 }, - { url = "https://files.pythonhosted.org/packages/00/11/30d3351f24cf8e652a0d5fe76e56a50478ea7e81dabcfea7339b1338cccd/pymongo-4.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74503e853758e1eaa1cad2df9c08c8c35a3d26222cf6426d2cde4b2e8593b9b3", size = 1673794 }, - { url = "https://files.pythonhosted.org/packages/a7/90/5ff61e8bad861621361868addeb34c4d2539a4c973a5767d1a266878cb32/pymongo-4.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b630596089106c968ddd252bde3fe692c420e24f214dd39ca517d26343d81012", size = 1738027 }, - { url = "https://files.pythonhosted.org/packages/d0/91/1fdf2843a664f01b8ca83d22cd7accb48f3a5371e61813a5451bc33f93c3/pymongo-4.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7007669eef871079d39a9bbcda0fbcd4252f9b575592804343d0b5c05849d65b", size = 1707052 }, - { url = "https://files.pythonhosted.org/packages/c6/f7/1bd23ea674c957b24256f9ef87875892801cf77b3d2535e59dd78b04db2e/pymongo-4.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d1da6201e1350cfcd4deab599b32237ac2ac591180d44553a2c8e614f2c0e", size = 1677027 }, - { url = "https://files.pythonhosted.org/packages/62/42/077b138efd223ed3cd03f3b8622d2315096e7cd1d9476cd8f1cf219c420e/pymongo-4.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:908e65ab42cd4bf1ffeaafe8f11bb86b3f804d54227058794e33fff2963ccc86", size = 1636150 }, - { url = "https://files.pythonhosted.org/packages/c9/a5/f958fcdc944f97d02b6a46c94dbbcdde0d355639c8564974b31b4685e97a/pymongo-4.11.1-cp312-cp312-win32.whl", hash = "sha256:2d1d956c15dd05f1e41c61f0dbcaec59f274db4814cff2c3d9c2508f58004c39", size = 864029 }, - { url = "https://files.pythonhosted.org/packages/d2/e2/b1747eabad8bf172aa66fae50ed7290c4992b8adbeaddbe31944755dbed4/pymongo-4.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:c71655f4188c70032ba56ac7ead688449e4f86a4ccd8e57201ee283f2f591e1d", size = 882299 }, - { url = "https://files.pythonhosted.org/packages/71/b6/dc403a4dda2adaf0f0088d3fcfe6eb17c9e16098eca98f705f2a8e73e693/pymongo-4.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f845b46d77a5bcf0c9ee16f11c5bc84c63f4668d9ea4fc54cd923c8d48a1d521", size = 949622 }, - { url = "https://files.pythonhosted.org/packages/52/54/0572ffa3d1c43fec0bdd065c5008b57f7ce4da90e6c6ade0a3c32f34c21e/pymongo-4.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aadea45e01103f6ee4e80d76d4a27393a4e2bd93472ce4ebb894781f395e1053", size = 949301 }, - { url = "https://files.pythonhosted.org/packages/7f/d6/5bf309a20892f47898e7bc626cb3169a1120b16b2d7b7a60c3fab607907c/pymongo-4.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63348c850df796199abef7e9afbd86c34449f56731c7ec70b3901df1f5c135b", size = 1937689 }, - { url = "https://files.pythonhosted.org/packages/16/03/1c792ab1e1e5a48fde005bbf739f04846ae48c8c8543a2f1e74ce42d465b/pymongo-4.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dd7656794bfbfbe10723813332ec33eed29bd9bb7fc122c63829fd445eb8425", size = 2015119 }, - { url = "https://files.pythonhosted.org/packages/ad/cc/8765bbec58392929f414b5b26f4c3fe333bfb75ad2f03e92fc48c81bd25f/pymongo-4.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7146ae04300ce6f83b75c639e97c3d0ce873f30edaac4b719ae173e886b9ff90", size = 1978788 }, - { url = "https://files.pythonhosted.org/packages/77/a6/b700ccb2695f3233a12943e78760f68adc19516cf120949ad7c67fdc81a9/pymongo-4.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698fb3d13126c0719077c98b40378cb9a6f4ab4a72b7691779aa01f1f6c66493", size = 1939607 }, - { url = "https://files.pythonhosted.org/packages/d1/10/c0e4c38c7a6d318a80a4dcd8cfc42bfb8a072145f152089e5bc8d60db902/pymongo-4.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f415d9569720f408cc4dcc171f60299d454b0414f120666e6fdd349d414bf010", size = 1889041 }, - { url = "https://files.pythonhosted.org/packages/1d/86/7145841c425e4f6b012116db38e3bf4652dce7b8537961b2391e3c52e051/pymongo-4.11.1-cp313-cp313-win32.whl", hash = "sha256:4aa2c40e391ca29a337bef2b46b495c3f24b5696a87a58f0a0676a8bf131f9f8", size = 910368 }, - { url = "https://files.pythonhosted.org/packages/a0/d4/97632e8f230e95a877220c785a69478cae97610e1ec48b5f9be59a926b29/pymongo-4.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:1f871efa14a1f368559edff39ec03799ca108bfa8e1ba330b7ffc05eb958661f", size = 932942 }, - { url = "https://files.pythonhosted.org/packages/db/61/f719841bc59d3d33c6002950e8b9978705b6f9f1dd5efb66e73fe6919a7d/pymongo-4.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d293cec18624825937bd7f1d8bacf16104c79ced45a8ada93f08ec8a7a2ad17a", size = 1006140 }, - { url = "https://files.pythonhosted.org/packages/cb/05/f43900c675e158cc024bc82a062dfcaaf12d4d7f574947b73f41d843d189/pymongo-4.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b3ea3494f3e166a524529bb05a4fdda97afd77031fed3a63862fd815288c9df", size = 1006124 }, - { url = "https://files.pythonhosted.org/packages/31/2f/7bccadbcf272b5e8c617a6a329b07671ecfd1faea080d9ab311240b93737/pymongo-4.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12f4c4579076b7351c63378e22f43d4ce4ed4f2c93208b653c4752f18f47309", size = 2266399 }, - { url = "https://files.pythonhosted.org/packages/78/ac/6bf48a7c99b574c9afcb0f68b7a8b9bf9617a1a54773d0f8b1568f8a079c/pymongo-4.11.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a8aba4818350d2a463e084ae2426d395e725525fe86bd0219240b265dc1ca52", size = 2353616 }, - { url = "https://files.pythonhosted.org/packages/3c/f3/c4cd608ddda2dbc7fa668dd8356bb728313b8eec5b118eca3fa937d4fc8c/pymongo-4.11.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f97f62e6edde15d1d3d08abd7e43f1787ee9e672b1bb8e9d9f5fd6ded24f5599", size = 2312480 }, - { url = "https://files.pythonhosted.org/packages/c8/3e/2261ac8e0b6a150d92d35ba2db30b8387c78f9ecba725b0b6a363250f9c6/pymongo-4.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a4e82dce301c97bb132dec28a487c1a609dc67948e9db7cbd23485875367204", size = 2263792 }, - { url = "https://files.pythonhosted.org/packages/73/80/41568f1ff09cb73976f7e6f9d11dae63003e4c1156834366ad03f91f27df/pymongo-4.11.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:985a614ec24519f4a3d82aafb766c3f782a452fc46b32112d508a4e19b33fff3", size = 2202805 }, - { url = "https://files.pythonhosted.org/packages/0c/d3/d7ca22d5eb654a451e18f616442b7c6d472ffe76560d6623a2a4ddfd4854/pymongo-4.11.1-cp313-cp313t-win32.whl", hash = "sha256:889d20850d5aaa4f19814462c06488553e70ed4c62195dbaad5d5662884778af", size = 959247 }, - { url = "https://files.pythonhosted.org/packages/95/7b/8d0767251e687966cf19a4ad032d597ab135d26af5ecebbdb8895ea92cf0/pymongo-4.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3854db4be39cb9e0c34add1fd7e515deab0b4ee30f3cc3978e057746d119ac12", size = 987871 }, +sdist = { url = "https://files.pythonhosted.org/packages/db/e6/cdb1105c14a86aa2b1663a6cccc6bf54722bb12fb5d479979628142dde42/pymongo-4.11.3.tar.gz", hash = "sha256:b6f24aec7c0cfcf0ea9f89e92b7d40ba18a1e18c134815758f111ecb0122e61c", size = 2054848 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/cf/c606c9d889d8f34dcf80455e045854ef2fa187c439b22a6d30357790c12a/pymongo-4.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5f48b7faf4064e5f484989608a59503b11b7f134ca344635e416b1b12e7dc255", size = 895374 }, + { url = "https://files.pythonhosted.org/packages/c6/f5/287e84ba6c8e34cb13f798e7e859b4dcbc5fab99261f91202a8027f62ba6/pymongo-4.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:722f22bf18d208aa752591bde93e018065641711594e7a2fef0432da429264e8", size = 895063 }, + { url = "https://files.pythonhosted.org/packages/0e/ba/fe8964ec3f8d7348e9cd6a11864e1e84b2be62ea98ca0ba01a4f5b4d417d/pymongo-4.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5be1b35c4897626327c4e8bae14655807c2bc710504fa790bc19a72403142264", size = 1673722 }, + { url = "https://files.pythonhosted.org/packages/92/89/925b7160c517b66c80d05b36f63d4cc0d0ff23f01b5150b55936b5fab097/pymongo-4.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14f9e4d2172545798738d27bc6293b972c4f1f98cce248aa56e1e62c4c258ca7", size = 1737946 }, + { url = "https://files.pythonhosted.org/packages/f8/97/bcedba78ddbc1b8837bf556da55eb08a055e93b331722ecd1dad602a3427/pymongo-4.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd3f7bafe441135f58d2b91a312714f423e15fed5afe3854880c8c61ad78d3ce", size = 1706981 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/63719be395ec29b8f71fd267014af4957736b5297a1f51f76ef32d05a0cf/pymongo-4.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73de1b9f416a2662ba95b4b49edc963d47b93760a7e2b561b932c8099d160151", size = 1676948 }, + { url = "https://files.pythonhosted.org/packages/c1/36/de366cee39e6c2e64d824d1f2e5672381ec766c51224304d1aebf7db3507/pymongo-4.11.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e24268e2d7ae96eab12161985b39e75a75185393134fc671f4bb1a16f50bf6f4", size = 1636072 }, + { url = "https://files.pythonhosted.org/packages/07/48/34751291a152e8098b4cf6f467046f00edd71b695d5cf6be1b15778cda63/pymongo-4.11.3-cp312-cp312-win32.whl", hash = "sha256:33a936d3c1828e4f52bed3dad6191a3618cc28ab056e2770390aec88d9e9f9ea", size = 864025 }, + { url = "https://files.pythonhosted.org/packages/96/8a/604fab1e1f45deb0dc19e06053369e7db44e3d1359a39e0fe376bdb95b41/pymongo-4.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:c4673d8ef0c8ef712491a750adf64f7998202a82abd72be5be749749275b3edb", size = 882290 }, + { url = "https://files.pythonhosted.org/packages/01/f1/19f8a81ca1ef180983b89e24f8003863612aea358a06d7685566ccc18a87/pymongo-4.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e53b98c9700bb69f33a322b648d028bfe223ad135fb04ec48c0226998b80d0e", size = 949622 }, + { url = "https://files.pythonhosted.org/packages/67/9a/ae232aa9379a9e6cf325facf0f65176d70520d6a16807f4de2e1ccfb76ec/pymongo-4.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8464aff011208cf86eae28f4a3624ebc4a40783634e119b2b35852252b901ef3", size = 949299 }, + { url = "https://files.pythonhosted.org/packages/70/6d/1ddef8b6c6d598fe21c917d93c49a6304611a252a07e98a9b7e70e1b995b/pymongo-4.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3742ffc1951bec1450a5a6a02cfd40ddd4b1c9416b36c70ae439a532e8be0e05", size = 1937616 }, + { url = "https://files.pythonhosted.org/packages/13/9c/e735715789a876140f453def1b2015948708d224f1728f9b8412b6e495d2/pymongo-4.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a29294b508975a5dfd384f4b902cd121dc2b6e5d55ea2be2debffd2a63461cd9", size = 2015041 }, + { url = "https://files.pythonhosted.org/packages/fc/d3/cf41e9ce81644de9d8db54cc039823863e7240e021466ae093edc061683a/pymongo-4.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:051c741586ab6efafe72e027504ac4e5f01c88eceec579e4e1a438a369a61b0c", size = 1978716 }, + { url = "https://files.pythonhosted.org/packages/be/c8/c3f15c6cc5a9e0a75d18ae86209584cb14fdca017197def9741bff19c151/pymongo-4.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b05e03a327cdef28ec2bb72c974d412d308f5cf867a472ef17f9ac95d18ec05", size = 1939524 }, + { url = "https://files.pythonhosted.org/packages/1b/0d/613cd91c736325d05d2d5d389d06ed899bcdce5a265cb486b948729bf1eb/pymongo-4.11.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dafeddf1db51df19effd0828ae75492b15d60c7faec388da08f1fe9593c88e7a", size = 1888960 }, + { url = "https://files.pythonhosted.org/packages/e7/eb/b1e9cf2e03a47c4f35ffc5db1cb0ed0f92c5fe58c6f5f04d5a2da9d6bb77/pymongo-4.11.3-cp313-cp313-win32.whl", hash = "sha256:40c55afb34788ae6a6b8c175421fa46a37cfc45de41fe4669d762c3b1bbda48e", size = 910370 }, + { url = "https://files.pythonhosted.org/packages/77/f3/023f12ee9028f341880016fd6251255bf755f70730440ad11bf745f5f9e4/pymongo-4.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:a5b8b7ba9614a081d1f932724b7a6a20847f6c9629420ae81ce827db3b599af2", size = 932930 }, + { url = "https://files.pythonhosted.org/packages/d3/c7/0a145cc66fc756cea547b948150583357e5518cfa60b3ad0d3266d3ee168/pymongo-4.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0f23f849693e829655f667ea18b87bf34e1395237eb45084f3495317d455beb2", size = 1006138 }, + { url = "https://files.pythonhosted.org/packages/81/88/4ed3cd03d2f7835393a72ed87f5e9186f6fc54bcb0e9b7f718424c0b5db8/pymongo-4.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:62bcfa88deb4a6152a7c93bedd1a808497f6c2881424ca54c3c81964a51c5040", size = 1006125 }, + { url = "https://files.pythonhosted.org/packages/91/a9/d86844a9aff958c959e84b8223b9d226c3b39a71f2f2fbf2aa3a4a748212/pymongo-4.11.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2eaa0233858f72074bf0319f5034018092b43f19202bd7ecb822980c35bfd623", size = 2266315 }, + { url = "https://files.pythonhosted.org/packages/1d/06/fff82b09382a887dab6207bb23778395c5986a5ddab6f55905ebdd82e10c/pymongo-4.11.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a434e081017be360595237cd1aeac3d047dd38e8785c549be80748608c1d4ca", size = 2353538 }, + { url = "https://files.pythonhosted.org/packages/5d/f7/ff5399baee5888eb686c1508d28b4e9d82b9da5ca63215f958356dee4016/pymongo-4.11.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e8aa65a9e4a989245198c249816d86cb240221861b748db92b8b3a5356bd6f1", size = 2312410 }, + { url = "https://files.pythonhosted.org/packages/b0/4d/1746ee984b229eddf5f768265b553a90b31b2395fb5ae1d30d28e430a862/pymongo-4.11.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0a91004029d1fc9e66a800e6da4170afaa9b93bcf41299e4b5951b837b3467a", size = 2263706 }, + { url = "https://files.pythonhosted.org/packages/1c/dc/5d4154c5baf62af9ffb9391cf41848a87cda97798f92e4336730690be7d5/pymongo-4.11.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b992904ac78cb712b42c4b7348974ba1739137c1692cdf8bf75c3eeb22881a4", size = 2202724 }, + { url = "https://files.pythonhosted.org/packages/72/15/c18fcc456fdcb793714776da273fc4cba4579f21818f2219e23ff9512314/pymongo-4.11.3-cp313-cp313t-win32.whl", hash = "sha256:45e18bda802d95a2aed88e487f06becc3bd0b22286a25aeca8c46b8c64980dbb", size = 959256 }, + { url = "https://files.pythonhosted.org/packages/7d/64/11d87df61cdca4fef90388af592247e17f3d31b15a909780f186d2739592/pymongo-4.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:07d40b831590bc458b624f421849c2b09ad2b9110b956f658b583fe01fe01c01", size = 987855 }, ] [[package]] name = "pyright" -version = "1.1.397" +version = "1.1.398" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/23/cefa10c9cb198e0858ed0b9233371d62bca880337f628e58f50dfdfb12f0/pyright-1.1.397.tar.gz", hash = "sha256:07530fd65a449e4b0b28dceef14be0d8e0995a7a5b1bb2f3f897c3e548451ce3", size = 3818998 } +sdist = { url = "https://files.pythonhosted.org/packages/24/d6/48740f1d029e9fc4194880d1ad03dcf0ba3a8f802e0e166b8f63350b3584/pyright-1.1.398.tar.gz", hash = "sha256:357a13edd9be8082dc73be51190913e475fa41a6efb6ec0d4b7aab3bc11638d8", size = 3892675 } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/b5/98ec41e1e0ad5576ecd42c90ec363560f7b389a441722ea3c7207682dec7/pyright-1.1.397-py3-none-any.whl", hash = "sha256:2e93fba776e714a82b085d68f8345b01f91ba43e1ab9d513e79b70fc85906257", size = 5693631 }, + { url = "https://files.pythonhosted.org/packages/58/e0/5283593f61b3c525d6d7e94cfb6b3ded20b3df66e953acaf7bb4f23b3f6e/pyright-1.1.398-py3-none-any.whl", hash = "sha256:0a70bfd007d9ea7de1cf9740e1ad1a40a122592cfe22a3f6791b06162ad08753", size = 5780235 }, ] [package.optional-dependencies] @@ -596,27 +600,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/e1/e265aba384343dd8ddd3083f5e33536cd17e1566c41453a5517b5dd443be/ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9", size = 3639454 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/e3/3d2c022e687e18cf5d93d6bfa2722d46afc64eaa438c7fbbdd603b3597be/ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba", size = 11714128 }, - { url = "https://files.pythonhosted.org/packages/e1/22/aff073b70f95c052e5c58153cba735748c9e70107a77d03420d7850710a0/ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504", size = 11682539 }, - { url = "https://files.pythonhosted.org/packages/75/a7/f5b7390afd98a7918582a3d256cd3e78ba0a26165a467c1820084587cbf9/ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83", size = 11132512 }, - { url = "https://files.pythonhosted.org/packages/a6/e3/45de13ef65047fea2e33f7e573d848206e15c715e5cd56095589a7733d04/ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc", size = 11929275 }, - { url = "https://files.pythonhosted.org/packages/7d/f2/23d04cd6c43b2e641ab961ade8d0b5edb212ecebd112506188c91f2a6e6c/ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b", size = 11466502 }, - { url = "https://files.pythonhosted.org/packages/b5/6f/3a8cf166f2d7f1627dd2201e6cbc4cb81f8b7d58099348f0c1ff7b733792/ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e", size = 12676364 }, - { url = "https://files.pythonhosted.org/packages/f5/c4/db52e2189983c70114ff2b7e3997e48c8318af44fe83e1ce9517570a50c6/ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666", size = 13335518 }, - { url = "https://files.pythonhosted.org/packages/66/44/545f8a4d136830f08f4d24324e7db957c5374bf3a3f7a6c0bc7be4623a37/ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5", size = 12823287 }, - { url = "https://files.pythonhosted.org/packages/c5/26/8208ef9ee7431032c143649a9967c3ae1aae4257d95e6f8519f07309aa66/ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5", size = 14592374 }, - { url = "https://files.pythonhosted.org/packages/31/70/e917781e55ff39c5b5208bda384fd397ffd76605e68544d71a7e40944945/ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217", size = 12500173 }, - { url = "https://files.pythonhosted.org/packages/84/f5/e4ddee07660f5a9622a9c2b639afd8f3104988dc4f6ba0b73ffacffa9a8c/ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6", size = 11906555 }, - { url = "https://files.pythonhosted.org/packages/f1/2b/6ff2fe383667075eef8656b9892e73dd9b119b5e3add51298628b87f6429/ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897", size = 11538958 }, - { url = "https://files.pythonhosted.org/packages/3c/db/98e59e90de45d1eb46649151c10a062d5707b5b7f76f64eb1e29edf6ebb1/ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08", size = 12117247 }, - { url = "https://files.pythonhosted.org/packages/ec/bc/54e38f6d219013a9204a5a2015c09e7a8c36cedcd50a4b01ac69a550b9d9/ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656", size = 12554647 }, - { url = "https://files.pythonhosted.org/packages/a5/7d/7b461ab0e2404293c0627125bb70ac642c2e8d55bf590f6fce85f508f1b2/ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d", size = 9949214 }, - { url = "https://files.pythonhosted.org/packages/ee/30/c3cee10f915ed75a5c29c1e57311282d1a15855551a64795c1b2bbe5cf37/ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa", size = 10999914 }, - { url = "https://files.pythonhosted.org/packages/e8/a8/d71f44b93e3aa86ae232af1f2126ca7b95c0f515ec135462b3e1f351441c/ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a", size = 10177499 }, +version = "0.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/93/f51326459536f64876c932ed26c54fad11775dfda9a690966a8a8a3388d2/ruff-0.11.3.tar.gz", hash = "sha256:8d5fcdb3bb359adc12b757ed832ee743993e7474b9de714bb9ea13c4a8458bf9", size = 3902954 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/54/34341a6363405eea37d05d0062d3f4bff4b268b08e8f4f36fb6f4593b653/ruff-0.11.3-py3-none-linux_armv6l.whl", hash = "sha256:cb893a5eedff45071d52565300a20cd4ac088869e156b25e0971cb98c06f5dd7", size = 10097109 }, + { url = "https://files.pythonhosted.org/packages/ee/33/636511dcacae6710660aa1d746c98f1b63d969b5b04fb4dcaf9a3b068a3f/ruff-0.11.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:58edd48af0e201e2f494789de80f5b2f2b46c9a2991a12ea031254865d5f6aa3", size = 10896580 }, + { url = "https://files.pythonhosted.org/packages/1c/d0/b196c659fa4c9bea394833fcf1e9ff92a941d59474374e3cbda0ba548d2b/ruff-0.11.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:520f6ade25cea98b2e5cb29eb0906f6a0339c6b8e28a024583b867f48295f1ed", size = 10235125 }, + { url = "https://files.pythonhosted.org/packages/31/27/8010ce0b5dae8ad994635c2b112df76f10e9747802ac417a68a06349971f/ruff-0.11.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ca4405a93ebbc05e924358f872efceb1498c3d52a989ddf9476712a5480b16", size = 10398941 }, + { url = "https://files.pythonhosted.org/packages/ed/82/0e6eba1371cc221d5a7255a144dc5ab05f13d2aba46224f38b6628781647/ruff-0.11.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4341d38775a6be605ce7cd50e951b89de65cbd40acb0399f95b8e1524d604c8", size = 9946629 }, + { url = "https://files.pythonhosted.org/packages/4c/9d/8c03b84476187d48eae3ba5f3b7d550da9b5947ab967d47f832e6141c1b2/ruff-0.11.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72bf5b49e4b546f4bea6c05448ab71919b09cf75363adf5e3bf5276124afd31c", size = 11551896 }, + { url = "https://files.pythonhosted.org/packages/a8/63/cf7915adf71d72ccc95b24f9ea3637311f8efe8221a24400d823607e998a/ruff-0.11.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9fa791ee6c3629ba7f9ba2c8f2e76178b03f3eaefb920e426302115259819237", size = 12210030 }, + { url = "https://files.pythonhosted.org/packages/9c/b3/2bbfd8aee10de3eed807c9c3d5b48f927efbdada8c0e87a20073f1eb2537/ruff-0.11.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c81d3fe718f4d303aaa4ccdcd0f43e23bb2127da3353635f718394ca9b26721", size = 11643431 }, + { url = "https://files.pythonhosted.org/packages/5b/00/0343bec91e505be5f6ac1db13ffca0afe691789e1dc263a05a72b931570f/ruff-0.11.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4c38e9b6c01caaba46b6d8e732791f4c78389a9923319991d55b298017ce02", size = 13834449 }, + { url = "https://files.pythonhosted.org/packages/d4/d1/95ef70afe169400d1878e69ed4fa8b8361e3c5d0a25d2d3d5c25e6347590/ruff-0.11.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9686f5d1a2b4c918b5a6e9876bfe7f47498a990076624d41f57d17aadd02a4dd", size = 11356995 }, + { url = "https://files.pythonhosted.org/packages/92/fa/a1d68e12c9a2cb25bf8eef099381ca42ea3c8ed589fc4f04004466f4d19f/ruff-0.11.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4800ddc4764d42d8961ce4cb972bcf5cc2730d11cca3f11f240d9f7360460408", size = 10287108 }, + { url = "https://files.pythonhosted.org/packages/3c/31/711a3f2c0972f44e3770951a19a1b6ea551b9b7c08f257518c35a46666bd/ruff-0.11.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e63a2808879361aa9597d88d86380d8fb934953ef91f5ff3dafe18d9cb0b1e14", size = 9933317 }, + { url = "https://files.pythonhosted.org/packages/c4/ee/8c8dd6ec903f29a4bd1bd4510d1c9ba1a955cd792601ac3822764c7397d8/ruff-0.11.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8f8b1c4ae62638cc220df440140c21469232d8f2cb7f5059f395f7f48dcdb59e", size = 10966227 }, + { url = "https://files.pythonhosted.org/packages/f5/7c/ba479eb45803165dd3dc8accf32c7d52769f9011df958f983f2bcd40566f/ruff-0.11.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3ea2026be50f6b1fbedd2d1757d004e1e58bd0f414efa2a6fa01235468d4c82a", size = 11412919 }, + { url = "https://files.pythonhosted.org/packages/51/a2/6878e74efef39cb0996342c48918aff9a9f5632d8d40c307610688d382ae/ruff-0.11.3-py3-none-win32.whl", hash = "sha256:73d8b90d12674a0c6e98cd9e235f2dcad09d1a80e559a585eac994bb536917a3", size = 10306265 }, + { url = "https://files.pythonhosted.org/packages/95/95/30646e735a201266ec93504a8640190e4a47a9efb10990cb095bf1111c3a/ruff-0.11.3-py3-none-win_amd64.whl", hash = "sha256:faf1bfb0a51fb3a82aa1112cb03658796acef978e37c7f807d3ecc50b52ecbf6", size = 11403990 }, + { url = "https://files.pythonhosted.org/packages/cd/2e/d04d606d0b13c2c8188111a4ff9a99811c40fe170e1523e20f13cf85235e/ruff-0.11.3-py3-none-win_arm64.whl", hash = "sha256:67f8b68d7ab909f08af1fb601696925a89d65083ae2bb3ab286e572b5dc456aa", size = 10525855 }, ] [[package]] @@ -651,41 +655,53 @@ wheels = [ [[package]] name = "types-pyyaml" -version = "6.0.12.20241230" +version = "6.0.12.20250402" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/f9/4d566925bcf9396136c0a2e5dc7e230ff08d86fa011a69888dd184469d80/types_pyyaml-6.0.12.20241230.tar.gz", hash = "sha256:7f07622dbd34bb9c8b264fe860a17e0efcad00d50b5f27e93984909d9363498c", size = 17078 } +sdist = { url = "https://files.pythonhosted.org/packages/2d/68/609eed7402f87c9874af39d35942744e39646d1ea9011765ec87b01b2a3c/types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075", size = 17282 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/c1/48474fbead512b70ccdb4f81ba5eb4a58f69d100ba19f17c92c0c4f50ae6/types_PyYAML-6.0.12.20241230-py3-none-any.whl", hash = "sha256:fa4d32565219b68e6dee5f67534c722e53c00d1cfc09c435ef04d7353e1e96e6", size = 20029 }, + { url = "https://files.pythonhosted.org/packages/ed/56/1fe61db05685fbb512c07ea9323f06ea727125951f1eb4dff110b3311da3/types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681", size = 20329 }, ] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, ] [[package]] name = "tzdata" -version = "2025.1" +version = "2025.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 }, + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, ] [[package]] name = "virtualenv" -version = "20.29.2" +version = "20.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/88/dacc875dd54a8acadb4bcbfd4e3e86df8be75527116c91d8f9784f5e9cab/virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728", size = 4320272 } +sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, + { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, ]