sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #08192
[Merge] ~ack/maas-site-manager:api-naming-consistency into maas-site-manager:main
Alberto Donato has proposed merging ~ack/maas-site-manager:api-naming-consistency into maas-site-manager:main.
Commit message:
consistent naming in API handlers and their schemas
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~ack/maas-site-manager/+git/site-manager/+merge/442480
--
Your team MAAS Committers is requested to review the proposed merge of ~ack/maas-site-manager:api-naming-consistency into maas-site-manager:main.
diff --git a/backend/msm/user_api/_handlers.py b/backend/msm/user_api/_handlers.py
index 04d1e4d..f7835dc 100644
--- a/backend/msm/user_api/_handlers.py
+++ b/backend/msm/user_api/_handlers.py
@@ -4,11 +4,11 @@ from typing import Annotated
from fastapi import (
Depends,
HTTPException,
+ Request,
status,
)
from sqlalchemy.ext.asyncio import AsyncSession
-from .. import __version__
from ..db import (
db_session,
queries,
@@ -30,28 +30,29 @@ from ._jwt import (
get_authenticated_user,
)
from ._schema import (
- CreateTokensRequest,
- CreateTokensResponse,
- JSONWebToken,
- PaginatedPendingSites,
- PaginatedSites,
- PaginatedTokens,
- PendingSitesActionRequest,
- UserLoginRequest,
+ LoginPostRequest,
+ LoginPostResponse,
+ PendingSitesGetResponse,
+ PendingSitesPostRequest,
+ RootGetResponse,
+ SitesGetResponse,
+ TokensGetResponse,
+ TokensPostRequest,
+ TokensPostResponse,
)
-async def root() -> dict[str, str]:
+async def root_get(request: Request) -> RootGetResponse:
"""Root endpoint."""
- return {"version": __version__}
+ return RootGetResponse(version=request.app.version)
-async def sites(
+async def sites_get(
session: Annotated[AsyncSession, Depends(db_session)],
authenticated_user: Annotated[User, Depends(get_authenticated_user)],
pagination_params: PaginationParams = Depends(pagination_params),
filter_params: SiteFilterParams = Depends(site_filter_parameters),
-) -> PaginatedSites:
+) -> SitesGetResponse:
"""Return accepted sites."""
total, results = await queries.get_sites(
session,
@@ -59,7 +60,7 @@ async def sites(
limit=pagination_params.size,
**filter_params._asdict(),
)
- return PaginatedSites(
+ return SitesGetResponse(
total=total,
page=pagination_params.page,
size=pagination_params.size,
@@ -67,18 +68,18 @@ async def sites(
)
-async def pending_sites(
+async def pending_sites_get(
session: Annotated[AsyncSession, Depends(db_session)],
authenticated_user: Annotated[User, Depends(get_authenticated_user)],
pagination_params: PaginationParams = Depends(pagination_params),
-) -> PaginatedPendingSites:
+) -> PendingSitesGetResponse:
"""Return pending sites."""
total, results = await queries.get_pending_sites(
session,
offset=pagination_params.offset,
limit=pagination_params.size,
)
- return PaginatedPendingSites(
+ return PendingSitesGetResponse(
total=total,
page=pagination_params.page,
size=pagination_params.size,
@@ -89,7 +90,7 @@ async def pending_sites(
async def pending_sites_post(
session: Annotated[AsyncSession, Depends(db_session)],
authenticated_user: Annotated[User, Depends(get_authenticated_user)],
- action: PendingSitesActionRequest,
+ action: PendingSitesPostRequest,
) -> None:
"""Accept or reject pending sites."""
try:
@@ -107,16 +108,16 @@ async def pending_sites_post(
return None
-async def tokens(
+async def tokens_get(
session: Annotated[AsyncSession, Depends(db_session)],
authenticated_user: Annotated[User, Depends(get_authenticated_user)],
pagination_params: PaginationParams = Depends(pagination_params),
-) -> PaginatedTokens:
+) -> TokensGetResponse:
"""Return all tokens"""
total, results = await queries.get_tokens(
session, pagination_params.offset, pagination_params.size
)
- return PaginatedTokens(
+ return TokensGetResponse(
total=total,
page=pagination_params.page,
size=pagination_params.size,
@@ -127,8 +128,8 @@ async def tokens(
async def tokens_post(
session: Annotated[AsyncSession, Depends(db_session)],
authenticated_user: Annotated[User, Depends(get_authenticated_user)],
- create_request: CreateTokensRequest,
-) -> CreateTokensResponse:
+ create_request: TokensPostRequest,
+) -> TokensPostResponse:
"""
Create one or more tokens.
Token duration (TTL) is expressed in seconds.
@@ -138,13 +139,13 @@ async def tokens_post(
create_request.duration,
count=create_request.count,
)
- return CreateTokensResponse(expired=expired, tokens=tokens)
+ return TokensPostResponse(expired=expired, tokens=tokens)
-async def login_for_access_token(
+async def login_post(
session: Annotated[AsyncSession, Depends(db_session)],
- user_login: UserLoginRequest,
-) -> JSONWebToken:
+ user_login: LoginPostRequest,
+) -> LoginPostResponse:
user = await authenticate_user(
session, user_login.username, user_login.password
)
@@ -160,11 +161,12 @@ async def login_for_access_token(
access_token = create_access_token(
data={"sub": user.email}, expires_delta=access_token_expires
)
- return JSONWebToken(access_token=access_token, token_type="bearer")
+ return LoginPostResponse(access_token=access_token, token_type="bearer")
-async def read_users_me(
+async def users_me_get(
session: Annotated[AsyncSession, Depends(db_session)],
authenticated_user: Annotated[User, Depends(get_authenticated_user)],
) -> User:
+ """Render info about the authenticated user."""
return authenticated_user
diff --git a/backend/msm/user_api/_jwt.py b/backend/msm/user_api/_jwt.py
index ecf2a26..44284c3 100644
--- a/backend/msm/user_api/_jwt.py
+++ b/backend/msm/user_api/_jwt.py
@@ -24,7 +24,6 @@ from ..db import db_session
from ..db.models import UserWithPassword
from ..db.queries import get_user
from ..settings import SETTINGS
-from ._schema import JSONWebTokenData
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@@ -80,13 +79,12 @@ async def get_authenticated_user(
payload = jwt.decode(
token, SETTINGS.secret_key, algorithms=[SETTINGS.algorithm]
)
- email: str | None = payload.get("sub")
+ email = payload.get("sub")
if email is None:
raise credentials_exception
- token_data = JSONWebTokenData(email=email)
except JWTError:
raise credentials_exception
- user = await get_user(session, email=token_data.email)
+ user = await get_user(session, email=email)
if user is None:
raise credentials_exception
return user
diff --git a/backend/msm/user_api/_schema.py b/backend/msm/user_api/_schema.py
index f481522..f513eb5 100644
--- a/backend/msm/user_api/_schema.py
+++ b/backend/msm/user_api/_schema.py
@@ -14,7 +14,19 @@ from ..db.models import (
from ..schema import PaginatedResults
-class CreateTokensRequest(BaseModel):
+class RootGetResponse(BaseModel):
+ """Root handler response."""
+
+ version: str
+
+
+class TokensGetResponse(PaginatedResults):
+ """List of existing tokens."""
+
+ items: list[Token]
+
+
+class TokensPostRequest(BaseModel):
"""
Request to create one or more tokens, with a certain validity,
expressed in seconds.
@@ -24,51 +36,37 @@ class CreateTokensRequest(BaseModel):
duration: timedelta
-class CreateTokensResponse(BaseModel):
+class TokensPostResponse(BaseModel):
"""List of created tokens, along with their duration."""
expired: datetime
tokens: list[UUID]
-class PendingSitesActionRequest(BaseModel):
- """Request to accept/reject sites."""
-
- ids: list[int]
- accept: bool
-
-
-class PaginatedSites(PaginatedResults):
+class SitesGetResponse(PaginatedResults):
items: list[Site]
-class PaginatedPendingSites(PaginatedResults):
+class PendingSitesGetResponse(PaginatedResults):
items: list[PendingSite]
-class PaginatedTokens(PaginatedResults):
- items: list[Token]
+class PendingSitesPostRequest(BaseModel):
+ """Request to accept/reject sites."""
+
+ ids: list[int]
+ accept: bool
-class UserLoginRequest(BaseModel):
+class LoginPostRequest(BaseModel):
"""User login request schema."""
username: str
password: str
-class JSONWebToken(BaseModel):
- """
- A JSON Web Token for authenticating users.
- """
+class LoginPostResponse(BaseModel):
+ """User login response with JSON Web Token."""
access_token: str
token_type: str
-
-
-class JSONWebTokenData(BaseModel):
- """
- The payload data for a JWT Token
- """
-
- email: str
diff --git a/backend/msm/user_api/_setup.py b/backend/msm/user_api/_setup.py
index 8a7f332..02aee09 100644
--- a/backend/msm/user_api/_setup.py
+++ b/backend/msm/user_api/_setup.py
@@ -36,12 +36,10 @@ def create_app(db_dsn: str | None = None) -> FastAPI:
allow_headers=["*"],
)
app.state.db = db
- app.router.add_api_route("/", _handlers.root, methods=["GET"])
+ app.router.add_api_route("/", _handlers.root_get, methods=["GET"])
+ app.router.add_api_route("/login", _handlers.login_post, methods=["POST"])
app.router.add_api_route(
- "/login", _handlers.login_for_access_token, methods=["POST"]
- )
- app.router.add_api_route(
- "/requests", _handlers.pending_sites, methods=["GET"]
+ "/requests", _handlers.pending_sites_get, methods=["GET"]
)
app.router.add_api_route(
"/requests",
@@ -49,12 +47,12 @@ def create_app(db_dsn: str | None = None) -> FastAPI:
methods=["POST"],
status_code=204,
)
- app.router.add_api_route("/sites", _handlers.sites, methods=["GET"])
- app.router.add_api_route("/tokens", _handlers.tokens, methods=["GET"])
+ app.router.add_api_route("/sites", _handlers.sites_get, methods=["GET"])
+ app.router.add_api_route("/tokens", _handlers.tokens_get, methods=["GET"])
app.router.add_api_route(
"/tokens", _handlers.tokens_post, methods=["POST"]
)
app.router.add_api_route(
- "/users/me", _handlers.read_users_me, methods=["GET"]
+ "/users/me", _handlers.users_me_get, methods=["GET"]
)
return app
diff --git a/backend/tests/user_api/test_handlers.py b/backend/tests/user_api/test_handlers.py
index 4ed19a1..bf62306 100644
--- a/backend/tests/user_api/test_handlers.py
+++ b/backend/tests/user_api/test_handlers.py
@@ -48,7 +48,7 @@ async def test_root_as_auth(
@pytest.mark.asyncio
-async def test_list_sites(
+async def test_sites_get(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site1 = {
@@ -108,7 +108,7 @@ async def test_list_sites(
@pytest.mark.asyncio
-async def test_list_sites_only_accepted(
+async def test_sites_get_only_accepted(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site1 = {
@@ -150,7 +150,7 @@ async def test_list_sites_only_accepted(
@pytest.mark.asyncio
-async def test_list_sites_filter_timezone(
+async def test_sites_get_filter_timezone(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site1 = {
@@ -192,7 +192,7 @@ async def test_list_sites_filter_timezone(
@pytest.mark.asyncio
-async def test_list_sites_with_stats(
+async def test_sites_get_with_stats(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
[site] = await fixture.create(
@@ -244,7 +244,7 @@ async def test_list_sites_with_stats(
@pytest.mark.asyncio
-async def test_list_pending_sites(
+async def test_pending_sites_get(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site1 = {
@@ -290,7 +290,7 @@ async def test_list_pending_sites(
@pytest.mark.asyncio
-async def test_accept_pending_sites(
+async def test_pending_sites_post_accept(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site = {
@@ -310,7 +310,7 @@ async def test_accept_pending_sites(
@pytest.mark.asyncio
-async def test_reject_pending_sites(
+async def test_pending_sites_post_reject(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site = {
@@ -329,7 +329,7 @@ async def test_reject_pending_sites(
@pytest.mark.asyncio
-async def test_post_pending_sites_invalid_ids(
+async def test_pending_sites_post_invalid_ids(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
site = {
@@ -371,7 +371,7 @@ async def test_token_time_format(
@pytest.mark.asyncio
-async def test_list_tokens(
+async def test_tokens_get(
authenticated_user_app_client: AuthAsyncClient, fixture: Fixture
) -> None:
tokens = await fixture.create(
@@ -410,7 +410,7 @@ async def test_list_tokens(
@pytest.mark.asyncio
-async def test_login_fails_with_wrong_password(
+async def test_login_post_fails_with_wrong_password(
user_app_client: AsyncClient, fixture: Fixture
) -> None:
phash = "$2b$12$F5sgrhRNtWAOehcoVO.XK.oSvupmcg8.0T2jCHOTg15M8N8LrpRwS"
@@ -435,7 +435,7 @@ async def test_login_fails_with_wrong_password(
@pytest.mark.asyncio
-async def test_sites_fails_without_login(
+async def test_sites_get_unauthenticated(
user_app_client: AsyncClient, fixture: Fixture
) -> None:
site = [
@@ -459,7 +459,7 @@ async def test_sites_fails_without_login(
@pytest.mark.asyncio
-async def test_token_fails_without_login(user_app_client: AsyncClient) -> None:
+async def test_token_get_unauthenticated(user_app_client: AsyncClient) -> None:
seconds = 100
response = await user_app_client.post(
Follow ups