Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repos:
# files: ^renovate\.json$

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.11
rev: v0.12.2
hooks:
- id: ruff
args: ["--output-format=concise"]
Expand All @@ -27,7 +27,7 @@ repos:
name: "format with ruff"

- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.15.0" # Use the sha / tag you want to point at
rev: "v1.16.1" # Use the sha / tag you want to point at
hooks:
- id: mypy
name: "run mypy"
Expand All @@ -37,7 +37,7 @@ repos:

- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.7.8
rev: 0.7.19
hooks:
# Update the uv lockfile
- id: uv-lock
Expand Down
1 change: 1 addition & 0 deletions app/api_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def cli_header() -> None:

@app.callback(invoke_without_command=True)
def main(
*,
version: Optional[bool] = typer.Option(
False,
"--version",
Expand Down
3 changes: 3 additions & 0 deletions app/commands/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

@app.command()
def init(
*,
force: Optional[bool] = typer.Option(
False,
"--force",
Expand Down Expand Up @@ -58,6 +59,7 @@ def init(

@app.command()
def drop(
*,
force: Optional[bool] = typer.Option(
False,
"--force",
Expand Down Expand Up @@ -307,6 +309,7 @@ def seed(
help="Path to the CSV file containing user data",
),
] = Path("users.seed"),
*,
force: Optional[bool] = typer.Option(
False,
"--force",
Expand Down
1 change: 1 addition & 0 deletions app/commands/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def serve(
"-h",
help="Define the interface to run the server on.",
),
*,
reload: Optional[bool] = typer.Option(
True,
help="Enable auto-reload on code changes",
Expand Down
11 changes: 7 additions & 4 deletions app/commands/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def create(
show_default=False,
hide_input=True,
),
*,
admin: Optional[bool] = typer.Option(
False,
"--admin",
Expand Down Expand Up @@ -235,6 +236,7 @@ def ban(
help="The user's id",
show_default=False,
),
*,
unban: Optional[bool] = typer.Option(
False,
"--unban",
Expand All @@ -245,7 +247,7 @@ def ban(
) -> None:
"""Ban or Unban a user by id."""

async def _ban_user(user_id: int, unban: Optional[bool]) -> User | None:
async def _ban_user(user_id: int, *, unban: Optional[bool]) -> User | None:
"""Async function to ban or unban a user."""
try:
async with async_session() as session:
Expand All @@ -259,7 +261,7 @@ async def _ban_user(user_id: int, unban: Optional[bool]) -> User | None:
else:
return user

user = aiorun(_ban_user(user_id, unban))
user = aiorun(_ban_user(user_id, unban=unban))
if user:
rprint(
f"\n[green]-> User [bold]{user_id}[/bold] "
Expand All @@ -280,6 +282,7 @@ def admin(
help="The user's id",
show_default=False,
),
*,
remove: Optional[bool] = typer.Option(
False,
"--remove",
Expand All @@ -291,7 +294,7 @@ def admin(
"""Make a user an admin or remove admin status."""

async def _toggle_admin(
user_id: int, remove: Optional[bool]
user_id: int, *, remove: Optional[bool]
) -> User | None:
"""Async function to toggle admin status for a user."""
try:
Expand All @@ -306,7 +309,7 @@ async def _toggle_admin(
else:
return user

user = aiorun(_toggle_admin(user_id, remove))
user = aiorun(_toggle_admin(user_id, remove=remove))
if user:
status = "removed from" if remove else "granted to"
rprint(
Expand Down
2 changes: 1 addition & 1 deletion app/managers/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class EmailManager:
"""Class to manage all Email operations."""

def __init__(self, suppress_send: Optional[bool] = False) -> None:
def __init__(self, *, suppress_send: Optional[bool] = False) -> None:
"""Initialize the EmailManager.

Define the configuration instance.
Expand Down
10 changes: 7 additions & 3 deletions app/managers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,11 @@ async def change_password(

@staticmethod
async def set_ban_status(
user_id: int, state: Optional[bool], my_id: int, session: AsyncSession
user_id: int,
my_id: int,
session: AsyncSession,
*,
banned: Optional[bool],
) -> None:
"""Ban or un-ban the specified user based on supplied status."""
if my_id == user_id:
Expand All @@ -276,13 +280,13 @@ async def set_ban_status(
raise HTTPException(
status.HTTP_404_NOT_FOUND, ErrorMessages.USER_INVALID
)
if bool(check_user.banned) == state:
if bool(check_user.banned) == banned:
raise HTTPException(
status.HTTP_400_BAD_REQUEST,
ErrorMessages.ALREADY_BANNED_OR_UNBANNED,
)
await session.execute(
update(User).where(User.id == user_id).values(banned=state)
update(User).where(User.id == user_id).values(banned=banned)
)

@staticmethod
Expand Down
8 changes: 6 additions & 2 deletions app/resources/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ async def ban_user(

Admins only. The Admin cannot ban their own ID!
"""
await UserManager.set_ban_status(user_id, True, request.state.user.id, db)
await UserManager.set_ban_status(
user_id, request.state.user.id, db, banned=True
)


@router.post(
Expand All @@ -118,7 +120,9 @@ async def unban_user(

Admins only.
"""
await UserManager.set_ban_status(user_id, False, request.state.user.id, db)
await UserManager.set_ban_status(
user_id, request.state.user.id, db, banned=False
)


@router.put(
Expand Down
8 changes: 5 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ mkdocstrings==0.28.1
mkdocstrings-python==1.16.0
# via mkdocstrings
mock==5.1.0
mypy==1.15.0
mypy==1.16.1
mypy-extensions==1.0.0
# via mypy
nodeenv==1.9.1
Expand All @@ -241,7 +241,9 @@ paginate==0.5.7
pastel==0.2.1
# via poethepoet
pathspec==0.12.1
# via mkdocs
# via
# mkdocs
# mypy
platformdirs==4.3.6
# via
# mkdocs-get-deps
Expand Down Expand Up @@ -354,7 +356,7 @@ rtoml==0.12.0
# api-template
# github-changelog-md
# simple-toml-settings
ruff==0.11.11
ruff==0.12.2
shellingham==1.5.4
# via typer
simple-toml-settings==0.9.0
Expand Down
4 changes: 2 additions & 2 deletions tests/cli/test_cli_custom_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,11 +378,11 @@ def reimport_custom_module() -> None:
the 'pytest.raises' context manager and upsetting 'ruff' linter.
"""
if "app.commands.custom" in sys.modules:
import importlib
import importlib # noqa: PLC0415

importlib.reload(sys.modules["app.commands.custom"])
else:
import app.commands.custom # noqa: F401
import app.commands.custom # noqa: F401, PLC0415

# Store and patch the import mechanism
original_import = __import__
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ async def test_refresh_no_user(self, test_db) -> None:
async def test_refresh_banned_user(self, test_db) -> None:
"""Test the refresh method with a banned user."""
await UserManager.register(self.test_user, test_db)
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)
banned_user_refresh = AuthManager.encode_refresh_token(User(id=1))
new_token = None
with pytest.raises(HTTPException) as exc_info:
Expand Down Expand Up @@ -252,7 +252,7 @@ async def test_verify_banned_user(self, test_db) -> None:
"""Test the verify method with a banned user."""
background_tasks = BackgroundTasks()
await UserManager.register(self.test_user, test_db, background_tasks)
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)
verify_token = get_token(
sub=1,
exp=datetime.now(tz=timezone.utc).timestamp() + 10000,
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_jwt_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def test_jwt_auth_no_auth_header(self, test_db, mocker) -> None:
async def test_jwt_auth_banned_user(self, test_db, mocker) -> None:
"""Test with a banned user."""
token, _ = await UserManager.register(self.test_user, test_db)
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)

mock_req = mocker.patch(self.mock_request_path)
mock_req.headers = {"Authorization": f"Bearer {token}"}
Expand All @@ -94,7 +94,7 @@ async def test_jwt_auth_unverified_user(self, test_db, mocker) -> None:
token, _ = await UserManager.register(
self.test_user, test_db, background_tasks=background_tasks
)
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)

mock_req = mocker.patch(self.mock_request_path)
mock_req.headers = {"Authorization": f"Bearer {token}"}
Expand Down
16 changes: 8 additions & 8 deletions tests/unit/test_user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ async def test_login_user_not_verified(self, test_db) -> None:
async def test_login_user_banned(self, test_db) -> None:
"""Test logging in a user that is banned."""
await UserManager.register(self.test_user, test_db)
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)
with pytest.raises(HTTPException, match=ErrorMessages.AUTH_INVALID):
await UserManager.login(self.test_user, test_db)

Expand Down Expand Up @@ -292,7 +292,7 @@ async def test_change_password_not_found(self, test_db) -> None:
async def test_ban_user(self, test_db) -> None:
"""Test we can ban or unban a user."""
await UserManager.register(self.test_user, test_db)
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)

banned_user = await test_db.get(User, 1)
assert banned_user.banned is True
Expand All @@ -301,35 +301,35 @@ async def test_unban_user(self, test_db) -> None:
"""Test we can ban or unban a user."""
await UserManager.register(self.test_user, test_db)
# set this user as banned
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)

await UserManager.set_ban_status(1, False, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=False)

banned_user = await test_db.get(User, 1)
assert banned_user.banned is False

async def test_ban_user_not_found(self, test_db) -> None:
"""Test we can't ban a user that doesn't exist."""
with pytest.raises(HTTPException, match=ErrorMessages.USER_INVALID):
await UserManager.set_ban_status(1, True, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=True)

@pytest.mark.parametrize("state", [True, False])
async def test_cant_ban_user_already_banned(self, test_db, state) -> None:
"""Test we can't ban a user that is already banned/unbanned."""
await UserManager.register(self.test_user, test_db)
if state:
await UserManager.set_ban_status(1, state, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=state)

with pytest.raises(
HTTPException, match=ErrorMessages.ALREADY_BANNED_OR_UNBANNED
):
await UserManager.set_ban_status(1, state, 666, test_db)
await UserManager.set_ban_status(1, 666, test_db, banned=state)

async def test_cant_ban_self(self, test_db) -> None:
"""Test we can't ban ourselves."""
await UserManager.register(self.test_user, test_db)
with pytest.raises(HTTPException, match=ErrorMessages.CANT_SELF_BAN):
await UserManager.set_ban_status(1, True, 1, test_db)
await UserManager.set_ban_status(1, 1, test_db, banned=True)

# ------------------------- test change user role ------------------------ #
async def test_change_user_role_to_admin(self, test_db) -> None:
Expand Down
Loading
Loading