diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 4847c1c6..4bde7861 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v4 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 65c70da0..e4f2aad5 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -7,11 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Linting - uses: chartboost/ruff-action@v1 + - uses: astral-sh/ruff-action@v2 with: - args: check - - name: Check Formatting - uses: chartboost/ruff-action@v1 - with: - args: format --check + args: " check --output-format=concise" + - run: ruff format --check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 41afa077..c3fe3302 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv - uses: astral-sh/setup-uv@v3 + uses: astral-sh/setup-uv@v4 with: enable-cache: true cache-dependency-glob: "uv.lock" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43bfb0cc..9b29d871 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,16 +11,17 @@ repos: - id: check-merge-conflict - id: end-of-file-fixer - - repo: https://github.com/renovatebot/pre-commit-hooks - rev: 39.20.1 - hooks: - - id: renovate-config-validator - files: ^renovate\.json$ + # - repo: https://github.com/renovatebot/pre-commit-hooks + # rev: 39.69.2 + # hooks: + # - id: renovate-config-validator + # files: ^renovate\.json$ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.3 hooks: - id: ruff + args: ["--output-format=concise"] name: "lint with ruff" - id: ruff-format name: "format with ruff" @@ -35,7 +36,7 @@ repos: - repo: https://github.com/astral-sh/uv-pre-commit # uv version. - rev: 0.5.2 + rev: 0.5.9 hooks: # Update the uv lockfile - id: uv-lock diff --git a/app/api_admin.py b/app/api_admin.py index 78680d4e..0f4ec138 100644 --- a/app/api_admin.py +++ b/app/api_admin.py @@ -3,7 +3,7 @@ from typing import Optional import typer -from rich import print # pylint: disable=W0622 +from rich import print as rprint from rich.panel import Panel from app.commands import custom, db, dev, docs, test, user @@ -15,7 +15,7 @@ def cli_header() -> None: """Show a common header for all commands.""" name, _, _ = get_api_details() - print( + rprint( Panel( f"[bold]{name} configuration tool[/bold] {get_api_version()}", highlight=True, @@ -40,7 +40,7 @@ def main( name, desc, _ = get_api_details() output = f"[bold]{name}[/bold] v{get_api_version()}\n{desc}" - print( + rprint( Panel( output, title="Version", diff --git a/app/commands/custom.py b/app/commands/custom.py index b9a9f133..c385fbbc 100644 --- a/app/commands/custom.py +++ b/app/commands/custom.py @@ -10,7 +10,7 @@ import rtoml import typer from jinja2 import Template -from rich import print # pylint: disable=W0622 +from rich import print as rprint from app.config.helpers import ( LICENCES, @@ -50,14 +50,14 @@ def init() -> None: with get_config_path().open("w", encoding="UTF-8") as file: file.write(out) except OSError as err: - print(f"Cannot Write the metadata : {err}") + rprint(f"Cannot Write the metadata : {err}") raise typer.Exit(2) from err try: from app.config.metadata import custom_metadata except ModuleNotFoundError as exc: - print( + rprint( "[red]The metadata file could not be found, it may have been deleted.\n" "Recreating with defaults, please re-run the command." ) @@ -166,27 +166,27 @@ def metadata() -> None: datetime.datetime.now(tz=datetime.timezone.utc).today().year ) - print("\nYou have entered the following data:") - print(f"[green]Title : [/green]{data['title']}") - print(f"[green]Name : [/green]{data['name']}") - print(f"[green]Description : [/green]{data['desc']}") - print(f"[green]Version : [/green]{data['version']}") - print(f"[green]Repository : [/green]{data['repo']}") - print(f"[green]License : [/green]{data['license']['name']}") - print(f"[green]Author : [/green]{data['author']}") - print(f"[green]Email : [/green]{data['email']}") - print(f"[green]Website : [/green]{data['website']}") - print(f"[green](C) Year : [/green]{data['this_year']}") + rprint("\nYou have entered the following data:") + rprint(f"[green]Title : [/green]{data['title']}") + rprint(f"[green]Name : [/green]{data['name']}") + rprint(f"[green]Description : [/green]{data['desc']}") + rprint(f"[green]Version : [/green]{data['version']}") + rprint(f"[green]Repository : [/green]{data['repo']}") + rprint(f"[green]License : [/green]{data['license']['name']}") + rprint(f"[green]Author : [/green]{data['author']}") + rprint(f"[green]Email : [/green]{data['email']}") + rprint(f"[green]Website : [/green]{data['website']}") + rprint(f"[green](C) Year : [/green]{data['this_year']}") if click.confirm("\nIs this Correct?", abort=True, default=True): # write the metadata - print("\n[green]-> Writing out Metadata .... ", end="") + rprint("\n[green]-> Writing out Metadata .... ", end="") out = Template(TEMPLATE).render(data) try: with get_config_path().open(mode="w", encoding="UTF-8") as file: file.write(out) except OSError as err: - print(f"Cannot Write the metadata : {err}") + rprint(f"Cannot Write the metadata : {err}") sys.exit(2) # update the pyproject.toml file @@ -202,7 +202,7 @@ def metadata() -> None: rtoml.dump(config, get_toml_path(), pretty=False) except OSError as err: - print(f"Cannot update the pyproject.toml file : {err}") + rprint(f"Cannot update the pyproject.toml file : {err}") sys.exit(3) - print("Done!") - print("\n[cyan]-> Remember to RESTART the API if it is running.\n") + rprint("Done!") + rprint("\n[cyan]-> Remember to RESTART the API if it is running.\n") diff --git a/app/commands/db.py b/app/commands/db.py index 95d5a295..3025cd18 100644 --- a/app/commands/db.py +++ b/app/commands/db.py @@ -5,7 +5,7 @@ import typer from alembic import command from alembic.config import Config -from rich import print # pylint: disable=W0622 +from rich import print as rprint app = typer.Typer(no_args_is_help=True, rich_markup_mode="rich") @@ -31,13 +31,13 @@ def init( If the database already exists, it will be dropped and recreated. """ if force: - print("\nInitialising Database ... ", end="") + rprint("\nInitialising Database ... ", end="") command.downgrade(ALEMBIC_CFG, "base") command.upgrade(ALEMBIC_CFG, "head") - print(DONE_MSG) + rprint(DONE_MSG) else: - print("[cyan]Operation Cancelled.") + rprint("[cyan]Operation Cancelled.") @app.command() @@ -57,21 +57,21 @@ def drop( This will delete all data from your Database!. """ if force: - print("\nDropping all tables ... ", end="") + rprint("\nDropping all tables ... ", end="") command.downgrade(ALEMBIC_CFG, "base") - print(DONE_MSG) + rprint(DONE_MSG) else: - print("[cyan]Operation Cancelled.") + rprint("[cyan]Operation Cancelled.") @app.command() def upgrade() -> None: """Apply the latest Database Migrations.""" - print("\nUpgrading Database ... ", end="") + rprint("\nUpgrading Database ... ", end="") command.upgrade(ALEMBIC_CFG, "head") - print(DONE_MSG) + rprint(DONE_MSG) @app.command() @@ -89,6 +89,6 @@ def revision( The revision will be created in the `alembic/versions` directory, and is autogenerated based on the current state of the database. """ - print() + rprint() command.revision(ALEMBIC_CFG, message=message, autogenerate=True) command.upgrade(ALEMBIC_CFG, "head") diff --git a/app/commands/dev.py b/app/commands/dev.py index b43afd95..0c3134b1 100644 --- a/app/commands/dev.py +++ b/app/commands/dev.py @@ -4,7 +4,7 @@ from typing import Optional # nosec import typer -from rich import print # pylint: disable=W0622 +from rich import print as rprint app = typer.Typer() @@ -29,7 +29,7 @@ def serve( This will auto-refresh on any changes to the source in real-time. """ - print("\n[cyan] -> Running a development server.\n") + rprint("\n[cyan] -> Running a development server.\n") cmd_line = ( f"uvicorn app.main:app --port={port} --host={host} " f"{'--reload' if reload else ''}" diff --git a/app/commands/docs.py b/app/commands/docs.py index 6d1c7708..ee9390e4 100644 --- a/app/commands/docs.py +++ b/app/commands/docs.py @@ -5,7 +5,7 @@ import typer from fastapi.openapi.utils import get_openapi -from rich import print # pylint: disable=W0622 +from rich import print as rprint from app.main import app as main_app @@ -27,7 +27,7 @@ def openapi( You can also specify a filename using `--filename`. """ openapi_file = Path(prefix, filename) - print( + rprint( "Generating OpenAPI schema at [bold]" f"{openapi_file.resolve()}[/bold]\n" ) diff --git a/app/commands/test.py b/app/commands/test.py index 2d897f90..2b5b8a15 100644 --- a/app/commands/test.py +++ b/app/commands/test.py @@ -4,7 +4,7 @@ import typer from asyncpg.exceptions import InvalidCatalogNameError, InvalidPasswordError -from rich import print # pylint: disable=W0622 +from rich import print as rprint from sqlalchemy.ext.asyncio import create_async_engine from app.config.settings import get_settings @@ -33,14 +33,14 @@ async def prepare_database() -> None: def setup() -> None: """Populate the test databases.""" try: - print("Migrating the test database ... ", end="") + rprint("Migrating the test database ... ", end="") asyncio.run(prepare_database()) - print("Done!") + rprint("Done!") except ( InvalidCatalogNameError, ConnectionRefusedError, InvalidPasswordError, ) as exc: - print(f"\n[red] -> Error: {exc}") - print("Failed to migrate the test database.") + rprint(f"\n[red] -> Error: {exc}") + rprint("Failed to migrate the test database.") raise typer.Exit(1) from exc diff --git a/app/commands/user.py b/app/commands/user.py index 6f9f3f7e..b9fba5a7 100644 --- a/app/commands/user.py +++ b/app/commands/user.py @@ -8,7 +8,7 @@ import typer from fastapi import HTTPException -from rich import print # pylint: disable=W0622 +from rich import print as rprint from rich.console import Console from rich.table import Table from sqlalchemy.exc import SQLAlchemyError @@ -116,15 +116,15 @@ async def _create_user(user_data: dict[str, str | RoleType]) -> None: await session.commit() user_level = "Admin" if admin else "" - print( + rprint( f"\n[green]-> {user_level} User [bold]{user_data['email']}" "[/bold] added succesfully.\n" ) except HTTPException as exc: - print(f"\n[red]-> ERROR adding User : [bold]{exc.detail}\n") + rprint(f"\n[red]-> ERROR adding User : [bold]{exc.detail}\n") raise typer.Exit(1) from exc except SQLAlchemyError as exc: - print(f"\n[red]-> ERROR adding User : [bold]{exc}\n") + rprint(f"\n[red]-> ERROR adding User : [bold]{exc}\n") raise typer.Exit(1) from exc role_type = RoleType.admin if admin else RoleType.user @@ -155,7 +155,7 @@ async def _list_users() -> Sequence[User]: user_list = await UserManager.get_all_users(session) except SQLAlchemyError as exc: - print(f"\n[red]-> ERROR listing Users : [bold]{exc}\n") + rprint(f"\n[red]-> ERROR listing Users : [bold]{exc}\n") raise typer.Exit(1) from exc else: return user_list @@ -164,7 +164,7 @@ async def _list_users() -> Sequence[User]: if user_list: show_table("Registered Users", user_list) else: - print("\n[red]-> ERROR listing Users : [bold]No Users found\n") + rprint("\n[red]-> ERROR listing Users : [bold]No Users found\n") @app.command() @@ -183,7 +183,7 @@ async def _show_user() -> User: async with async_session() as session: user = await UserManager.get_user_by_id(user_id, session) except HTTPException as exc: - print( + rprint( f"\n[red]-> ERROR getting User details : [bold]{exc.detail}\n" ) raise typer.Exit(1) from exc @@ -214,18 +214,18 @@ async def _verify_user(user_id: int) -> User | None: user.verified = True await session.commit() except SQLAlchemyError as exc: - print(f"\n[red]-> ERROR verifying User : [bold]{exc}\n") + rprint(f"\n[red]-> ERROR verifying User : [bold]{exc}\n") raise typer.Exit(1) from exc else: return user user = aiorun(_verify_user(user_id)) if user: - print( + rprint( f"\n[green]-> User [bold]{user_id}[/bold] verified succesfully.\n" ) else: - print("\n[red]-> ERROR verifying User : [bold]User not found\n") + rprint("\n[red]-> ERROR verifying User : [bold]User not found\n") raise typer.Exit(1) @@ -255,20 +255,20 @@ async def _ban_user(user_id: int, unban: Optional[bool]) -> User | None: user.banned = not unban await session.commit() except SQLAlchemyError as exc: - print(f"\n[RED]-> ERROR banning or unbanning User : [bold]{exc}\n") + rprint(f"\n[RED]-> ERROR banning or unbanning User : [bold]{exc}\n") raise typer.Exit(1) from exc else: return user user = aiorun(_ban_user(user_id, unban)) if user: - print( + rprint( f"\n[green]-> User [bold]{user_id}[/bold] " f"[red]{'UN' if unban else ''}BANNED[/red] succesfully." ) show_table("", [user]) else: - print( + rprint( "\n[red]-> ERROR banning or unbanning User : [bold]User not found\n" ) raise typer.Exit(1) @@ -293,7 +293,7 @@ async def _delete_user(user_id: int) -> User | None: await session.delete(user) await session.commit() except SQLAlchemyError as exc: - print(f"\n[RED]-> ERROR deleting that User : [bold]{exc}\n") + rprint(f"\n[RED]-> ERROR deleting that User : [bold]{exc}\n") raise typer.Exit(1) from exc else: return user @@ -301,10 +301,10 @@ async def _delete_user(user_id: int) -> User | None: user = aiorun(_delete_user(user_id)) if user: - print( + rprint( f"\n[green]-> User [bold]{user_id}[/bold] " f"[red]DELETED[/red] succesfully." ) else: - print("\n[red]-> ERROR deleting that User : [bold]User not found\n") + rprint("\n[red]-> ERROR deleting that User : [bold]User not found\n") raise typer.Exit(1) diff --git a/app/config/settings.py b/app/config/settings.py index be5d9bf1..cffd109e 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -4,7 +4,7 @@ import sys from functools import lru_cache -from pathlib import Path # noqa: TCH003 +from pathlib import Path # noqa: TC003 from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict diff --git a/app/main.py b/app/main.py index 8fdc175e..d0abfc4e 100644 --- a/app/main.py +++ b/app/main.py @@ -8,7 +8,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles -from rich import print # pylint: disable=W0622 +from rich import print as rprint from sqlalchemy.exc import SQLAlchemyError from app.config.helpers import get_api_version, get_project_root @@ -43,10 +43,10 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[Any, None]: async with async_session() as session: await session.connection() - print("[green]INFO: [/green][bold]Database configuration Tested.") + rprint("[green]INFO: [/green][bold]Database configuration Tested.") except SQLAlchemyError as exc: - print(f"[red]ERROR: [bold]Have you set up your .env file?? ({exc})") - print( + rprint(f"[red]ERROR: [bold]Have you set up your .env file?? ({exc})") + rprint( "[yellow]WARNING: [/yellow]Clearing routes and enabling " "error message." ) diff --git a/app/managers/email.py b/app/managers/email.py index fdb976b7..490f48a5 100644 --- a/app/managers/email.py +++ b/app/managers/email.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Optional -from fastapi import BackgroundTasks # noqa: TCH002 +from fastapi import BackgroundTasks # noqa: TC002 from fastapi.responses import JSONResponse from fastapi_mail import ConnectionConfig, FastMail, MessageSchema, MessageType diff --git a/app/resources/auth.py b/app/resources/auth.py index d2477017..54b2102b 100644 --- a/app/resources/auth.py +++ b/app/resources/auth.py @@ -1,5 +1,7 @@ """Define routes for Authentication.""" +from typing import Annotated + from fastapi import APIRouter, BackgroundTasks, Depends, status from sqlalchemy.ext.asyncio import AsyncSession @@ -22,7 +24,7 @@ async def register( background_tasks: BackgroundTasks, user_data: UserRegisterRequest, - session: AsyncSession = Depends(get_database), + session: Annotated[AsyncSession, Depends(get_database)], ) -> dict[str, str]: """Register a new User and return a JWT token plus a Refresh Token. @@ -48,7 +50,8 @@ async def register( status_code=status.HTTP_200_OK, ) async def login( - user_data: UserLoginRequest, session: AsyncSession = Depends(get_database) + user_data: UserLoginRequest, + session: Annotated[AsyncSession, Depends(get_database)], ) -> dict[str, str]: """Login an existing User and return a JWT token plus a Refresh Token. @@ -70,7 +73,7 @@ async def login( ) async def generate_refresh_token( refresh_token: TokenRefreshRequest, - session: AsyncSession = Depends(get_database), + session: Annotated[AsyncSession, Depends(get_database)], ) -> dict[str, str]: """Return a new JWT, given a valid Refresh token. @@ -83,7 +86,7 @@ async def generate_refresh_token( @router.get("/verify/", status_code=status.HTTP_200_OK) async def verify( - code: str = "", session: AsyncSession = Depends(get_database) + session: Annotated[AsyncSession, Depends(get_database)], code: str = "" ) -> None: """Verify a new user. diff --git a/app/resources/home.py b/app/resources/home.py index d7f3a035..d7bd7bb4 100644 --- a/app/resources/home.py +++ b/app/resources/home.py @@ -1,6 +1,6 @@ """Routes for the home screen and templates.""" -from typing import Union +from typing import Annotated, Union from fastapi import APIRouter, Header, Request from fastapi.templating import Jinja2Templates @@ -19,7 +19,8 @@ @router.get("/", include_in_schema=False, response_model=None) def root_path( - request: Request, accept: Union[str, None] = Header(default="text/html") + request: Request, + accept: Annotated[Union[str, None], Header()] = "text/html", ) -> RootResponse: """Display an HTML template for a browser, JSON response otherwise.""" if accept and accept.split(",")[0] == "text/html": diff --git a/app/resources/user.py b/app/resources/user.py index e3010291..d2c897aa 100644 --- a/app/resources/user.py +++ b/app/resources/user.py @@ -1,7 +1,7 @@ """Routes for User listing and control.""" from collections.abc import Sequence -from typing import Optional, Union +from typing import Annotated, Optional, Union from fastapi import APIRouter, Depends, Request, status from sqlalchemy.ext.asyncio import AsyncSession @@ -23,7 +23,8 @@ response_model=Union[UserResponse, list[UserResponse]], ) async def get_users( - user_id: Optional[int] = None, db: AsyncSession = Depends(get_database) + db: Annotated[AsyncSession, Depends(get_database)], + user_id: Optional[int] = None, ) -> Union[Sequence[User], User]: """Get all users or a specific user by their ID. @@ -43,7 +44,7 @@ async def get_users( name="get_my_user_data", ) async def get_my_user( - request: Request, db: AsyncSession = Depends(get_database) + request: Request, db: Annotated[AsyncSession, Depends(get_database)] ) -> User: """Get the current user's data only.""" my_user: int = request.state.user.id @@ -56,7 +57,7 @@ async def get_my_user( status_code=status.HTTP_204_NO_CONTENT, ) async def make_admin( - user_id: int, db: AsyncSession = Depends(get_database) + user_id: int, db: Annotated[AsyncSession, Depends(get_database)] ) -> None: """Make the User with this ID an Admin.""" await UserManager.change_role(RoleType.admin, user_id, db) @@ -70,7 +71,7 @@ async def make_admin( async def change_password( user_id: int, user_data: UserChangePasswordRequest, - db: AsyncSession = Depends(get_database), + db: Annotated[AsyncSession, Depends(get_database)], ) -> None: """Change the password for the specified user. @@ -85,7 +86,9 @@ async def change_password( status_code=status.HTTP_204_NO_CONTENT, ) async def ban_user( - request: Request, user_id: int, db: AsyncSession = Depends(get_database) + request: Request, + user_id: int, + db: Annotated[AsyncSession, Depends(get_database)], ) -> None: """Ban the specific user Id. @@ -100,7 +103,9 @@ async def ban_user( status_code=status.HTTP_204_NO_CONTENT, ) async def unban_user( - request: Request, user_id: int, db: AsyncSession = Depends(get_database) + request: Request, + user_id: int, + db: Annotated[AsyncSession, Depends(get_database)], ) -> None: """Ban the specific user Id. @@ -118,7 +123,7 @@ async def unban_user( async def edit_user( user_id: int, user_data: UserEditRequest, - db: AsyncSession = Depends(get_database), + db: Annotated[AsyncSession, Depends(get_database)], ) -> Union[User, None]: """Update the specified User's data. @@ -134,7 +139,7 @@ async def edit_user( status_code=status.HTTP_204_NO_CONTENT, ) async def delete_user( - user_id: int, db: AsyncSession = Depends(get_database) + user_id: int, db: Annotated[AsyncSession, Depends(get_database)] ) -> None: """Delete the specified User by user_id. diff --git a/pyproject.toml b/pyproject.toml index 680dff6e..098063d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "anyio>=4.6.2.post1", "asyncclick>=8.1.7.2", "asyncpg>=0.30.0", + "bcrypt==4.0.1", "email-validator>=2.2.0", "fastapi[standard]>=0.115.4", "psycopg2>=2.9.10", @@ -149,7 +150,6 @@ quote-style = "double" [tool.ruff.lint] select = ["ALL"] # we are being very strict! ignore = [ - "ANN101", "PGH003", "FBT002", "FBT003", diff --git a/requirements-dev.txt b/requirements-dev.txt index c846e9a3..02708508 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -123,7 +123,7 @@ requests==2.32.3 rfc3986==1.5.0 rich==13.9.4 rtoml==0.11.0 -ruff==0.7.2 +ruff==0.8.3 shellingham==1.5.4 simple-toml-settings==0.8.0 six==1.16.0 diff --git a/requirements.txt b/requirements.txt index 40a693f8..172c4f02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ bcrypt==4.0.1 blinker==1.8.2 certifi==2024.8.30 click==8.1.7 -colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' +colorama==0.4.6 ; platform_system == 'Windows' or sys_platform == 'win32' dnspython==2.7.0 email-validator==2.2.0 exceptiongroup==1.2.2 ; python_full_version < '3.11' diff --git a/uv.lock b/uv.lock index 1b9bd276..d1a0af8c 100644 --- a/uv.lock +++ b/uv.lock @@ -86,6 +86,7 @@ dependencies = [ { name = "anyio" }, { name = "asyncclick" }, { name = "asyncpg" }, + { name = "bcrypt" }, { name = "email-validator" }, { name = "fastapi", extra = ["standard"] }, { name = "fastapi-mail" }, @@ -146,6 +147,7 @@ requires-dist = [ { name = "anyio", specifier = ">=4.6.2.post1" }, { name = "asyncclick", specifier = ">=8.1.7.2" }, { name = "asyncpg", specifier = ">=0.30.0" }, + { name = "bcrypt", specifier = "==4.0.1" }, { name = "email-validator", specifier = ">=2.2.0" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.115.4" }, { name = "fastapi-mail", specifier = ">=1.4.1" }, @@ -2348,27 +2350,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/51/231bb3790e5b0b9fd4131f9a231d73d061b3667522e3f406fd9b63334d0e/ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f", size = 3210036 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/56/0caa2b5745d66a39aa239c01059f6918fc76ed8380033d2f44bf297d141d/ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8", size = 10373973 }, - { url = "https://files.pythonhosted.org/packages/1a/33/cad6ff306731f335d481c50caa155b69a286d5b388e87ff234cd2a4b3557/ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4", size = 10171140 }, - { url = "https://files.pythonhosted.org/packages/97/f5/6a2ca5c9ba416226eac9cf8121a1baa6f06655431937e85f38ffcb9d0d01/ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9", size = 9809333 }, - { url = "https://files.pythonhosted.org/packages/16/83/e3e87f13d1a1dc205713632978cd7bc287a59b08bc95780dbe359b9aefcb/ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2", size = 10622987 }, - { url = "https://files.pythonhosted.org/packages/22/16/97ccab194480e99a2e3c77ae132b3eebfa38c2112747570c403a4a13ba3a/ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba", size = 10184640 }, - { url = "https://files.pythonhosted.org/packages/97/1b/82ff05441b036f68817296c14f24da47c591cb27acfda473ee571a5651ac/ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859", size = 11210203 }, - { url = "https://files.pythonhosted.org/packages/a6/96/7ecb30a7ef7f942e2d8e0287ad4c1957dddc6c5097af4978c27cfc334f97/ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b", size = 11870894 }, - { url = "https://files.pythonhosted.org/packages/06/6a/c716bb126218227f8e604a9c484836257708a05ee3d2ebceb666ff3d3867/ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88", size = 11449533 }, - { url = "https://files.pythonhosted.org/packages/e6/2f/3a5f9f9478904e5ae9506ea699109070ead1e79aac041e872cbaad8a7458/ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80", size = 12607919 }, - { url = "https://files.pythonhosted.org/packages/a0/57/4642e57484d80d274750dcc872ea66655bbd7e66e986fede31e1865b463d/ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088", size = 11016915 }, - { url = "https://files.pythonhosted.org/packages/4d/6d/59be6680abee34c22296ae3f46b2a3b91662b8b18ab0bf388b5eb1355c97/ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748", size = 10625424 }, - { url = "https://files.pythonhosted.org/packages/82/e7/f6a643683354c9bc7879d2f228ee0324fea66d253de49273a0814fba1927/ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828", size = 10233692 }, - { url = "https://files.pythonhosted.org/packages/d7/48/b4e02fc835cd7ed1ee7318d9c53e48bcf6b66301f55925a7dcb920e45532/ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e", size = 10751825 }, - { url = "https://files.pythonhosted.org/packages/1e/06/6c5ee6ab7bb4cbad9e8bb9b2dd0d818c759c90c1c9e057c6ed70334b97f4/ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691", size = 11074811 }, - { url = "https://files.pythonhosted.org/packages/a1/16/8969304f25bcd0e4af1778342e63b715e91db8a2dbb51807acd858cba915/ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8", size = 8650268 }, - { url = "https://files.pythonhosted.org/packages/d9/18/c4b00d161def43fe5968e959039c8f6ce60dca762cec4a34e4e83a4210a0/ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88", size = 9433693 }, - { url = "https://files.pythonhosted.org/packages/7f/7b/c920673ac01c19814dd15fc617c02301c522f3d6812ca2024f4588ed4549/ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760", size = 8735845 }, +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/5e/683c7ef7a696923223e7d95ca06755d6e2acbc5fd8382b2912a28008137c/ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3", size = 3378522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/c4/bfdbb8b9c419ff3b52479af8581026eeaac3764946fdb463dec043441b7d/ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6", size = 10535860 }, + { url = "https://files.pythonhosted.org/packages/ef/c5/0aabdc9314b4b6f051168ac45227e2aa8e1c6d82718a547455e40c9c9faa/ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939", size = 10346327 }, + { url = "https://files.pythonhosted.org/packages/1a/78/4843a59e7e7b398d6019cf91ab06502fd95397b99b2b858798fbab9151f5/ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d", size = 9942585 }, + { url = "https://files.pythonhosted.org/packages/91/5a/642ed8f1ba23ffc2dd347697e01eef3c42fad6ac76603be4a8c3a9d6311e/ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13", size = 10797597 }, + { url = "https://files.pythonhosted.org/packages/30/25/2e654bc7226da09a49730a1a2ea6e89f843b362db80b4b2a7a4f948ac986/ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18", size = 10307244 }, + { url = "https://files.pythonhosted.org/packages/c0/2d/a224d56bcd4383583db53c2b8f410ebf1200866984aa6eb9b5a70f04e71f/ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502", size = 11362439 }, + { url = "https://files.pythonhosted.org/packages/82/01/03e2857f9c371b8767d3e909f06a33bbdac880df17f17f93d6f6951c3381/ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d", size = 12078538 }, + { url = "https://files.pythonhosted.org/packages/af/ae/ff7f97b355da16d748ceec50e1604a8215d3659b36b38025a922e0612e9b/ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82", size = 11616172 }, + { url = "https://files.pythonhosted.org/packages/6a/d0/6156d4d1e53ebd17747049afe801c5d7e3014d9b2f398b9236fe36ba4320/ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452", size = 12919886 }, + { url = "https://files.pythonhosted.org/packages/4e/84/affcb30bacb94f6036a128ad5de0e29f543d3f67ee42b490b17d68e44b8a/ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd", size = 11212599 }, + { url = "https://files.pythonhosted.org/packages/60/b9/5694716bdefd8f73df7c0104334156c38fb0f77673d2966a5a1345bab94d/ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20", size = 10784637 }, + { url = "https://files.pythonhosted.org/packages/24/7e/0e8f835103ac7da81c3663eedf79dec8359e9ae9a3b0d704bae50be59176/ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc", size = 10390591 }, + { url = "https://files.pythonhosted.org/packages/27/da/180ec771fc01c004045962ce017ca419a0281f4bfaf867ed0020f555b56e/ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060", size = 10894298 }, + { url = "https://files.pythonhosted.org/packages/6d/f8/29f241742ed3954eb2222314b02db29f531a15cab3238d1295e8657c5f18/ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea", size = 11275965 }, + { url = "https://files.pythonhosted.org/packages/79/e9/5b81dc9afc8a80884405b230b9429efeef76d04caead904bd213f453b973/ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964", size = 8807651 }, + { url = "https://files.pythonhosted.org/packages/ea/67/7291461066007617b59a707887b90e319b6a043c79b4d19979f86b7a20e7/ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9", size = 9625289 }, + { url = "https://files.pythonhosted.org/packages/03/8f/e4fa95288b81233356d9a9dcaed057e5b0adc6399aa8fd0f6d784041c9c3/ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936", size = 9078754 }, ] [[package]]