Alberto Donato has proposed merging ~ack/maas-site-manager:schema-models-package into maas-site-manager:main.
Commit message: move db-related models to the db package, and API-related ones to user_api This also drops some unused schemas Requested reviews: MAAS Committers (maas-committers) For more details, see: https://code.launchpad.net/~ack/maas-site-manager/+git/site-manager/+merge/442380 -- Your team MAAS Committers is requested to review the proposed merge of ~ack/maas-site-manager:schema-models-package into maas-site-manager:main.
diff --git a/backend/msm/db/models.py b/backend/msm/db/models.py new file mode 100644 index 0000000..a04bc3e --- /dev/null +++ b/backend/msm/db/models.py @@ -0,0 +1,63 @@ +from datetime import datetime +from uuid import UUID + +from pydantic import ( + BaseModel, + EmailStr, + Field, + SecretStr, +) + +from ..schema import TimeZone + + +class SiteData(BaseModel): + """Data for a site.""" + + allocated_machines: int + deployed_machines: int + ready_machines: int + error_machines: int + last_seen: datetime + + +class Site(BaseModel): + """A MAAS installation.""" + + id: int + name: str + city: str | None + country: str | None = Field(min_length=2, max_length=2) + latitude: str | None + longitude: str | None + note: str | None + region: str | None + street: str | None + timezone: TimeZone | None + url: str + stats: SiteData | None + + +class Token(BaseModel): + """A registration token for a site.""" + + id: int + value: UUID + site_id: int | None + expired: datetime + created: datetime + + +class User(BaseModel): + """A user.""" + + id: int + email: EmailStr = Field(title="[email protected]") + full_name: str + + +class UserWithPassword(User): + """A user with its password.""" + + # use password.get_secret_value() to retrieve the value + password: SecretStr = Field(min_length=8, max_length=100) diff --git a/backend/msm/db/queries.py b/backend/msm/db/queries.py index ff5df47..9248559 100644 --- a/backend/msm/db/queries.py +++ b/backend/msm/db/queries.py @@ -20,21 +20,23 @@ from sqlalchemy import ( ) from sqlalchemy.ext.asyncio import AsyncSession -from ..schema import ( - MAX_PAGE_SIZE, - Site as SiteSchema, - Token as TokenSchema, - UserWithPassword as UserPWSchema, -) +from ..schema import MAX_PAGE_SIZE from ._tables import ( Site, SiteData, Token, User, ) +from .models import ( + Site as SiteSchema, + Token as TokenSchema, + UserWithPassword as UserWithPasswordSchema, +) -async def get_user(session: AsyncSession, email: str) -> UserPWSchema | None: +async def get_user( + session: AsyncSession, email: str +) -> UserWithPasswordSchema | None: """ Gets a user by its unique identifier: their email """ @@ -50,7 +52,7 @@ async def get_user(session: AsyncSession, email: str) -> UserPWSchema | None: ) if result := await session.execute(stmt): if user := result.one_or_none(): - return UserPWSchema(**user._asdict()) + return UserWithPasswordSchema(**user._asdict()) return None diff --git a/backend/msm/schema/__init__.py b/backend/msm/schema/__init__.py index 21e6316..114792b 100644 --- a/backend/msm/schema/__init__.py +++ b/backend/msm/schema/__init__.py @@ -1,18 +1,6 @@ """API schema definitions.""" -from ._models import ( - CreateTokensRequest, - CreateTokensResponse, - JSONWebToken, - JSONWebTokenData, - PaginatedSites, - PaginatedTokens, - Site, - Token, - User, - UserLoginRequest, - UserWithPassword, -) +from ._fields import TimeZone from ._pagination import ( MAX_PAGE_SIZE, PaginatedResults, @@ -21,19 +9,9 @@ from ._pagination import ( ) __all__ = [ - "CreateTokensRequest", - "CreateTokensResponse", - "JSONWebToken", - "JSONWebTokenData", "MAX_PAGE_SIZE", "PaginatedResults", - "PaginatedSites", - "PaginatedTokens", "PaginationParams", - "Site", - "Token", - "User", - "UserLoginRequest", - "UserWithPassword", "pagination_params", + "TimeZone", ] diff --git a/backend/msm/schema/_fields.py b/backend/msm/schema/_fields.py new file mode 100644 index 0000000..9e0e830 --- /dev/null +++ b/backend/msm/schema/_fields.py @@ -0,0 +1,5 @@ +import pytz +from strenum import StrEnum + +# Enum with timezones accepted by pytz. +TimeZone = StrEnum("TimeZone", pytz.all_timezones) diff --git a/backend/msm/schema/_models.py b/backend/msm/schema/_models.py deleted file mode 100644 index 1362f63..0000000 --- a/backend/msm/schema/_models.py +++ /dev/null @@ -1,168 +0,0 @@ -from datetime import ( - datetime, - timedelta, -) -from uuid import UUID - -from pydantic import ( - BaseModel, - EmailStr, - SecretStr, -) -from pydantic.fields import Field -import pytz -from strenum import StrEnum - -from ._pagination import PaginatedResults - -# Enum with timezones accepted by pytz. -TimeZone = StrEnum("TimeZone", pytz.all_timezones) - - -class ReadUser(BaseModel): - """ - A MAAS Site Manager User - We never want to sent the password (hash) around - """ - - email: EmailStr = Field(title="[email protected]") - full_name: str - - -class User(ReadUser): - """ - To read a user from the DB it comes with an ID - """ - - id: int - - -class UserWithPassword(User): - """ - To create a user we need a password as well. - """ - - # use password.get_secret_value() to retrieve the value - password: SecretStr = Field(min_length=8, max_length=100) - - -class UserLoginRequest(BaseModel): - """User login details.""" - - username: str - password: str - - -class CreateSite(BaseModel): - """ - A MAAS installation - """ - - name: str - city: str | None - country: str | None = Field(min_length=2, max_length=2) - latitude: str | None - longitude: str | None - note: str | None - region: str | None - street: str | None - timezone: TimeZone | None # type: ignore - url: str - # TODO: we will need to add tags - - -class SiteData(BaseModel): - """Data for a site""" - - allocated_machines: int - deployed_machines: int - ready_machines: int - error_machines: int - last_seen: datetime - - -class Site(CreateSite): - """ - Site persisted to the database - """ - - id: int - stats: SiteData | None - - -class CreateSiteData(SiteData): - """Site data""" - - site_id: int - - -class PaginatedSites(PaginatedResults): - items: list[Site] - - -class SiteWithData(Site): - - """ - A site, together with its SiteData - """ - - id: int - site_data: SiteData - - -class CreateToken(BaseModel): - """ - To create a token a value and an expiration - time need to be generated - """ - - site_id: int | None - value: UUID - expired: datetime - created: datetime - - -class Token(CreateToken): - """ - A token persisted to the database - """ - - id: int - - -class JSONWebToken(BaseModel): - """ - A JSON Web Token for authenticating users. - """ - - access_token: str - token_type: str - - -class JSONWebTokenData(BaseModel): - """ - The payload data for a JWT Token - """ - - email: str - - -class PaginatedTokens(PaginatedResults): - items: list[Token] - - -class CreateTokensRequest(BaseModel): - """ - Request to create one or more tokens, with a certain validity, - expressed in seconds. - """ - - count: int = 1 - duration: timedelta - - -class CreateTokensResponse(BaseModel): - """List of created tokens, along with their duration.""" - - expired: datetime - tokens: list[UUID] diff --git a/backend/msm/user_api/_base.py b/backend/msm/user_api/_handlers.py similarity index 98% rename from backend/msm/user_api/_base.py rename to backend/msm/user_api/_handlers.py index 1f4d1f1..c1dcd0a 100644 --- a/backend/msm/user_api/_base.py +++ b/backend/msm/user_api/_handlers.py @@ -13,16 +13,10 @@ from ..db import ( db_session, queries, ) +from ..db.models import User from ..schema import ( - CreateTokensRequest, - CreateTokensResponse, - JSONWebToken, - PaginatedSites, - PaginatedTokens, pagination_params, PaginationParams, - User, - UserLoginRequest, ) from ..settings import SETTINGS from ._forms import ( @@ -34,6 +28,14 @@ from ._jwt import ( create_access_token, get_authenticated_user, ) +from ._schema import ( + CreateTokensRequest, + CreateTokensResponse, + JSONWebToken, + PaginatedSites, + PaginatedTokens, + UserLoginRequest, +) async def root() -> dict[str, str]: diff --git a/backend/msm/user_api/_jwt.py b/backend/msm/user_api/_jwt.py index 8f7472e..ecf2a26 100644 --- a/backend/msm/user_api/_jwt.py +++ b/backend/msm/user_api/_jwt.py @@ -21,14 +21,10 @@ from passlib.context import CryptContext from sqlalchemy.ext.asyncio import AsyncSession from ..db import db_session - -# from ..db import db_session +from ..db.models import UserWithPassword from ..db.queries import get_user -from ..schema import ( - JSONWebTokenData, - UserWithPassword, -) from ..settings import SETTINGS +from ._schema import JSONWebTokenData pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") diff --git a/backend/msm/user_api/_schema.py b/backend/msm/user_api/_schema.py new file mode 100644 index 0000000..ee6c3f8 --- /dev/null +++ b/backend/msm/user_api/_schema.py @@ -0,0 +1,62 @@ +from datetime import ( + datetime, + timedelta, +) +from uuid import UUID + +from pydantic import BaseModel + +from ..db.models import ( + Site, + Token, +) +from ..schema import PaginatedResults + + +class CreateTokensRequest(BaseModel): + """ + Request to create one or more tokens, with a certain validity, + expressed in seconds. + """ + + count: int = 1 + duration: timedelta + + +class CreateTokensResponse(BaseModel): + """List of created tokens, along with their duration.""" + + expired: datetime + tokens: list[UUID] + + +class PaginatedSites(PaginatedResults): + items: list[Site] + + +class PaginatedTokens(PaginatedResults): + items: list[Token] + + +class UserLoginRequest(BaseModel): + """User login request schema.""" + + username: str + password: str + + +class JSONWebToken(BaseModel): + """ + A JSON Web Token for authenticating users. + """ + + 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 e058f20..6efd588 100644 --- a/backend/msm/user_api/_setup.py +++ b/backend/msm/user_api/_setup.py @@ -4,7 +4,7 @@ from typing import AsyncGenerator from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from . import _base +from . import _handlers from .. import PACKAGE from ..db import Database from ..settings import SETTINGS @@ -36,12 +36,16 @@ def create_app(db_dsn: str | None = None) -> FastAPI: allow_headers=["*"], ) app.state.db = db - app.router.add_api_route("/", _base.root, methods=["GET"]) - app.router.add_api_route("/sites", _base.sites, methods=["GET"]) - app.router.add_api_route("/tokens", _base.tokens, methods=["GET"]) - app.router.add_api_route("/tokens", _base.tokens_post, methods=["POST"]) + app.router.add_api_route("/", _handlers.root, methods=["GET"]) + 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( - "/login", _base.login_for_access_token, methods=["POST"] + "/tokens", _handlers.tokens_post, methods=["POST"] + ) + app.router.add_api_route( + "/login", _handlers.login_for_access_token, methods=["POST"] + ) + app.router.add_api_route( + "/users/me", _handlers.read_users_me, methods=["GET"] ) - app.router.add_api_route("/users/me", _base.read_users_me, methods=["GET"]) return app
-- Mailing list: https://launchpad.net/~sts-sponsors Post to : [email protected] Unsubscribe : https://launchpad.net/~sts-sponsors More help : https://help.launchpad.net/ListHelp

