sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #08142
[Merge] ~ack/maas-site-manager:schema-models-package into maas-site-manager:main
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@xxxxxxxxxxx")
+ 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@xxxxxxxxxxx")
- 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
Follow ups