Skip to content

Commit

Permalink
WIP continue clearing linting issues
Browse files Browse the repository at this point in the history
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
  • Loading branch information
seapagan committed Nov 12, 2023
1 parent c4944ee commit 28c623d
Show file tree
Hide file tree
Showing 25 changed files with 192 additions and 113 deletions.
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""This is the main module for the application.
It is usually ran from 'uvicorn' as a 'FastAPI' application.
"""
1 change: 1 addition & 0 deletions app/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""This module contains all the CLI commands for the 'api-admin' command."""
29 changes: 18 additions & 11 deletions app/commands/custom.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""CLI functionality to customize the template."""
from __future__ import annotations

import datetime
import sys
from datetime import date
from typing import Literal

import asyncclick as click
Expand All @@ -20,10 +20,12 @@
get_toml_path,
)

LicenceType = dict[str, str] | Literal["Unknown"]

app = typer.Typer(no_args_is_help=True)


def init():
def init() -> None:
"""Create a default metadata file, overwrite any existing."""
data = {
"title": "API Template",
Expand All @@ -36,12 +38,15 @@ def init():
"author": "Grant Ramsay (seapagan)",
"website": "https://www.gnramsay.com",
"email": "seapagan@gmail.com",
"this_year": date.today().year,
"this_year": datetime.datetime.now(tz=datetime.timezone.utc)
.date()
.today()
.year,
}

out = Template(TEMPLATE).render(data)
try:
with open(get_config_path(), "w", encoding="UTF-8") as file:
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}")
Expand All @@ -63,7 +68,7 @@ def get_licenses() -> list[str]:
return [licence["name"] for licence in LICENCES]


def get_case_insensitive_dict(choice) -> dict[str, str] | Literal["Unknown"]:
def get_case_insensitive_dict(choice: str) -> LicenceType:
"""Return the dictionary with specified key, case insensitive.
We already know the key exists, however it may have wrong case.
Expand All @@ -74,7 +79,7 @@ def get_case_insensitive_dict(choice) -> dict[str, str] | Literal["Unknown"]:
return "Unknown"


def choose_license():
def choose_license() -> LicenceType:
"""Select a licence from a fixed list."""
license_list = get_licenses()
license_strings = ", ".join(license_list)
Expand Down Expand Up @@ -104,7 +109,7 @@ def choose_version(current_version: str) -> str:


@app.command()
def metadata():
def metadata() -> None:
"""Customize the Application Metadata.
This includes the title and description displayed on the root route and
Expand Down Expand Up @@ -143,7 +148,9 @@ def metadata():
),
}

data["this_year"] = date.today().year
data["this_year"] = datetime.datetime.now(
tz=datetime.timezone.utc.date().today().year
)

print("\nYou have entered the following data:")
print(f"[green]Title : [/green]{data['title']}")
Expand All @@ -161,23 +168,23 @@ def metadata():
print("\n[green]-> Writing out Metadata .... ", end="")
out = Template(TEMPLATE).render(data)
try:
with open(get_config_path(), "w", encoding="UTF-8") as file:
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}")
sys.exit(2)

# update the pyproject.toml file
try:
with open(get_toml_path(), "rb") as file:
with get_toml_path().open(mode="rb") as file:
config = tomli.load(file)
config["tool"]["poetry"]["name"] = data["title"]
config["tool"]["poetry"]["version"] = data["version"]
config["tool"]["poetry"]["description"] = data["desc"]
config["tool"]["poetry"]["authors"] = [
f"{data['author']} <{data['email']}>"
]
with open(get_toml_path(), "wb") as file:
with get_toml_path().open(mode="wb") as file:
tomli_w.dump(config, file)
except OSError as err:
print(f"Cannot update the pyproject.toml file : {err}")
Expand Down
7 changes: 4 additions & 3 deletions app/commands/dev.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""CLI command to run a dev server."""
import subprocess # nosec
import subprocess
from typing import Optional # nosec

import typer
from rich import print # pylint: disable=W0622
Expand All @@ -18,7 +19,7 @@ def serve(
"-h",
help="Define the interface to run the server on.",
),
reload: bool = typer.Option(
reload: Optional[bool] = typer.Option(
True,
help="Enable auto-reload on code changes",
),
Expand All @@ -32,4 +33,4 @@ def serve(
f"uvicorn app.main:app --port={port} --host={host} "
f"{'--reload' if reload else ''}"
)
subprocess.call(cmd_line, shell=True) # nosec
subprocess.call(cmd_line, shell=True) # noqa: S602
7 changes: 3 additions & 4 deletions app/commands/docs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""CLI commands for generating documentation."""
import json
import os
from pathlib import Path

import typer
Expand All @@ -18,7 +17,7 @@ def openapi(
filename: str = typer.Option(
"openapi.json", help="Filename for the OpenAPI schema"
),
):
) -> None:
"""Generate an OpenAPI schema from the current routes.
By default this will be stored in the project root as `openapi.json`,
Expand All @@ -29,9 +28,9 @@ def openapi(
openapi_file = Path(prefix, filename)
print(
"Generating OpenAPI schema at [bold]"
f"{os.path.abspath(openapi_file)}[/bold]\n"
f"{openapi_file.resolve()}[/bold]\n"
)
with open(openapi_file, "w") as f:
with openapi_file.open(mode="w") as f:
json.dump(
get_openapi(
title=main_app.title,
Expand Down
4 changes: 2 additions & 2 deletions app/commands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
def setup() -> None:
"""Populate the test databases."""

async def prepare_database():
async def prepare_database() -> None:
"""Drop and recreate the database."""
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
Expand All @@ -42,4 +42,4 @@ async def prepare_database():
) as exc:
print(f"\n[red] -> Error: {exc}")
print("Failed to migrate the test database.")
raise typer.Exit(code=1)
raise typer.Exit(1) from exc
67 changes: 34 additions & 33 deletions app/commands/user.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Add a user from the command line, optionally make superuser."""
from asyncio import run as aiorun
from typing import Optional

import typer
from fastapi import HTTPException
Expand All @@ -15,7 +16,7 @@
app = typer.Typer(no_args_is_help=True)


def show_table(title: str, user_list):
def show_table(title: str, user_list: list[User]) -> None:
"""Show User data in a tabulated format."""
console = Console()
table = Table(
Expand Down Expand Up @@ -82,22 +83,22 @@ def create(
show_default=False,
hide_input=True,
),
admin: bool = typer.Option(
admin: Optional[bool] = typer.Option(
False,
"--admin",
"-a",
flag_value=True,
prompt="Should this user be an Admin?",
help="Make this user an Admin",
),
):
) -> None:
"""Create a new user. This can optionally be an admin user.
Values are either taken from the command line options, or interactively for
any that are missing.
"""

async def create_user(user_data: dict):
async def create_user(user_data: dict[str, str]) -> None:
"""Asny function to create a new user."""
try:
async with async_session() as session:
Expand All @@ -112,10 +113,7 @@ async def create_user(user_data: dict):
except Exception as err:
print(f"\n[red]-> ERROR adding User : [bold]{err}\n")

if admin:
role_type = RoleType.admin
else:
role_type = RoleType.user
role_type = RoleType.admin if admin else RoleType.user

user_data = {
"email": email,
Expand All @@ -128,24 +126,26 @@ async def create_user(user_data: dict):
aiorun(create_user(user_data))


@app.command()
def list():
@app.command(name="list")
def list_all_users() -> None:
"""List all users in the database.
Show one line per user with Id, Email, First Name, Last Name and Role.
Also include verified/banned status and a total count.
"""

async def list_users():
async def _list_users() -> list[User]:
"""Async function to list all users in the database."""
try:
async with async_session() as session:
user_list = await UserManager.get_all_users(session)
return user_list

except Exception as exc:
print(f"\n[red]-> ERROR listing Users : [bold]{exc}\n")
else:
return user_list

user_list = aiorun(list_users())
user_list = aiorun(_list_users())
if user_list:
show_table("Registered Users", user_list)
else:
Expand All @@ -159,23 +159,24 @@ def show(
help="The user's id",
show_default=False,
),
):
) -> None:
"""Show details for a single user."""

async def show_user():
async def _show_user() -> User:
"""Async function to show details for a single user."""
try:
async with async_session() as session:
user = await UserManager.get_user_by_id(user_id, session)
except HTTPException as exc:
print(
f"\n[red]-> ERROR getting User details : [bold]{exc.detail}\n"
)
else:
return user
except Exception as exc:
print(f"\n[red]-> ERROR getting User details : [bold]{exc}\n")

user = aiorun(show_user())
user = aiorun(_show_user())
if user:
show_table(f"Showing details for User {user_id}", [user])
else:
print("\n[red]-> ERROR getting User details : [bold]User not found\n")


@app.command()
Expand All @@ -185,22 +186,22 @@ def verify(
help="The user's id",
show_default=False,
),
):
) -> None:
"""Manually verify a user by id."""

async def verify_user(user_id: int):
async def _verify_user(user_id: int) -> User:
"""Async function to verify a user by id."""
try:
async with async_session() as session:
user = await session.get(User, user_id)
if user:
user.verified = True # type: ignore
user.verified = True
await session.commit()
return user
except Exception as exc:
print(f"\n[red]-> ERROR verifying User : [bold]{exc}\n")

user = aiorun(verify_user(user_id))
user = aiorun(_verify_user(user_id))
if user:
print(
f"\n[green]-> User [bold]{user_id}[/bold] verified succesfully.\n"
Expand All @@ -216,29 +217,29 @@ def ban(
help="The user's id",
show_default=False,
),
unban: bool = typer.Option(
unban: Optional[bool] = typer.Option(
False,
"--unban",
"-u",
flag_value=True,
help="Unban this user instead of banning them",
),
):
) -> None:
"""Ban or Unban a user by id."""

async def ban_user(user_id: int, unban: bool):
async def _ban_user(user_id: int, unban: Optional[bool]) -> User:
"""Async function to ban or unban a user."""
try:
async with async_session() as session:
user = await session.get(User, user_id)
if user:
user.banned = not unban # type: ignore
user.banned = not unban
await session.commit()
return user
except Exception as exc:
print(f"\n[RED]-> ERROR banning or unbanning User : [bold]{exc}\n")
print(f"\n[RED]-> ERROR banning or unbanning User : [bold]{exc}\n")

user = aiorun(ban_user(user_id, unban))
user = aiorun(_ban_user(user_id, unban))
if user:
print(
f"\n[green]-> User [bold]{user_id}[/bold] "
Expand All @@ -258,10 +259,10 @@ def delete(
help="The user's id",
show_default=False,
),
):
) -> None:
"""Delete the user with the given id."""

async def delete_user(user_id: int):
async def _delete_user(user_id: int) -> User:
"""Async function to delete a user."""
try:
async with async_session() as session:
Expand All @@ -273,7 +274,7 @@ async def delete_user(user_id: int):
except Exception as exc:
print(f"\n[RED]-> ERROR deleting that User : [bold]{exc}\n")

user = aiorun(delete_user(user_id))
user = aiorun(_delete_user(user_id))

if user:
print(
Expand Down
Loading

0 comments on commit 28c623d

Please sign in to comment.