Skip to content

Commit

Permalink
feat: Add descriptions, examples, and validations to pydantic fields
Browse files Browse the repository at this point in the history
  • Loading branch information
romeonicholas committed Feb 13, 2024
1 parent d6d256e commit 21442e3
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 126 deletions.
45 changes: 33 additions & 12 deletions backend/capellacollab/core/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import typing as t

import fastapi
import pydantic
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy import orm

import capellacollab
Expand All @@ -14,19 +14,40 @@
from capellacollab.settings.configuration import models as config_models


class Metadata(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)
class Metadata(BaseModel):
model_config = ConfigDict(from_attributes=True)

version: str
privacy_policy_url: str | None
imprint_url: str | None
provider: str | None
authentication_provider: str | None
environment: str | None
version: str = Field(
description="The version of the application", examples=["1.0.0"]
)
privacy_policy_url: str | None = Field(
description="The URL to the privacy policy",
examples=["https://example.com/privacy-policy"],
)
imprint_url: str | None = Field(
description="The URL to the imprint",
examples=["https://example.com/imprint"],
)
provider: str | None = Field(
description="The application provider",
examples=["DB InfraGO AG"],
)
authentication_provider: str | None = Field(
description="The authentication provider", examples=["OAuth2"]
)
environment: str | None = Field(
description="The application environment", examples=["test"]
)

host: str | None
port: str | None
protocol: str | None
host: str | None = Field(
description="The host of the application", examples=["localhost"]
)
port: str | None = Field(
description="The port of the application", examples=["4200"]
)
protocol: str | None = Field(
description="The protocol of the application", examples=["https"]
)


router = fastapi.APIRouter()
Expand Down
62 changes: 53 additions & 9 deletions backend/capellacollab/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,65 @@

import typing as t

import pydantic
from pydantic import BaseModel, Field

T = t.TypeVar("T")


class Message(pydantic.BaseModel):
err_code: str | None = None
title: str | None = None
reason: str | tuple | None = None
technical: str | None = None
class Message(BaseModel):
err_code: str | None = Field(
default=None,
description="The HTTP response status code",
examples=[
"422 Unprocessable Content",
],
)
title: str | None = Field(
default=None,
description="The error title",
examples=["Repository deletion failed partially."],
)
reason: str | tuple | None = Field(
default=None,
description="The user friendly error description",
examples=["The TeamForCapella server is not reachable."],
)
technical: str | None = Field(
default=None,
description="The technical developer error description",
examples=["TeamForCapella returned status code {e.status_code}"],
)


class ResponseModel(pydantic.BaseModel):
warnings: list[Message] | None = None
errors: list[Message] | None = None
class ResponseModel(BaseModel):
warnings: list[Message] | None = Field(
default=None,
description="The list of warning message objects",
examples=[
[
{
"err_code": "422 Unprocessable Content",
"title": "Repository deletion failed partially.",
"reason": "The TeamForCapella server is not reachable.",
"technical": "TeamForCapella returned status code {e.status_code}",
}
]
],
)
errors: list[Message] | None = Field(
default=None,
description="The list of error message objects",
examples=[
[
{
"err_code": "422 Unprocessable Content",
"title": "Repository deletion failed partially.",
"reason": "TeamForCapella returned an error when deleting the repository.",
"technical": "TeamForCapella returned status code {e.status_code}",
}
]
],
)


class PayloadResponseModel(ResponseModel, t.Generic[T]):
Expand Down
41 changes: 31 additions & 10 deletions backend/capellacollab/events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import enum
import typing as t

import pydantic
import sqlalchemy as sa
from pydantic import BaseModel, ConfigDict, Field, field_serializer
from sqlalchemy import orm

from capellacollab.core import database
Expand All @@ -33,17 +33,38 @@ class EventType(enum.Enum):
ASSIGNED_ROLE_USER = "AssignedRoleUser"


class BaseHistoryEvent(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)
class BaseHistoryEvent(BaseModel):
model_config = ConfigDict(from_attributes=True)

user: users_models.User
executor: users_models.User | None = None
project: projects_models.Project | None = None
execution_time: datetime.datetime
event_type: EventType
reason: str | None = None
user: users_models.User = Field(
description="The user affected by an event",
examples=[{"id": 2, "name": "John Doe", "role": "user"}],
)
executor: users_models.User | None = Field(
default=None,
description="The user who executed an event",
examples=[{"id": 1, "name": "Joe Manager", "role": "admin"}],
)
project: projects_models.Project | None = Field(
default=None,
description="The project affected by an event",
examples=[{"id": 1, "name": "Project A"}],
)
execution_time: datetime.datetime = Field(
default=None,
description="The time an event was executed",
examples=["2021-01-01T12:00:00Z"],
)
event_type: EventType = Field(
description="The type of event executed", examples=["CreatedUser"]
)
reason: str | None = Field(
description="The rationale provided by the executor of an event",
examples=["New hire"],
max_length=255,
)

_validate_execution_time = pydantic.field_serializer("execution_time")(
_validate_execution_time = field_serializer("execution_time")(
core_pydantic.datetime_serializer
)

Expand Down
25 changes: 18 additions & 7 deletions backend/capellacollab/notices/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import enum

import pydantic
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy import orm

from capellacollab.core import database
Expand All @@ -19,12 +19,23 @@ class NoticeLevel(enum.Enum):
ALERT = "alert"


class CreateNoticeRequest(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)

level: NoticeLevel
title: str
message: str
class CreateNoticeRequest(BaseModel):
model_config = ConfigDict(from_attributes=True)

level: NoticeLevel = Field(
description="The severity or indication level of a notice",
examples=["info"],
)
title: str = Field(
description="The title of a notice",
examples=["Planned Maintenance 13.09.2021"],
)
message: str = Field(
description="The message body of a notice",
examples=[
"The site will be unavailable from 7:00 until 14:00 on 13.09.2021."
],
)


class NoticeResponse(CreateNoticeRequest):
Expand Down
113 changes: 88 additions & 25 deletions backend/capellacollab/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import enum
import typing as t

import pydantic
from pydantic import BaseModel, ConfigDict, Field, field_validator
from sqlalchemy import orm

# Import required for sqlalchemy
Expand All @@ -18,10 +18,16 @@
from capellacollab.projects.users.models import ProjectUserAssociation


class UserMetadata(pydantic.BaseModel):
leads: int
contributors: int
subscribers: int
class UserMetadata(BaseModel):
leads: int = Field(
description="The number of users with the manager role in a project"
)
contributors: int = Field(
description="The number of non-manager users with write access in a project"
)
subscribers: int = Field(
description="The number of non-manager users with read access in a project"
)


class Visibility(enum.Enum):
Expand All @@ -34,18 +40,40 @@ class ProjectType(enum.Enum):
TRAINING = "training"


class Project(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)
class Project(BaseModel):
model_config = ConfigDict(from_attributes=True)

name: str
slug: str
description: str | None = None
visibility: Visibility
type: ProjectType
users: UserMetadata
is_archived: bool
name: str = Field(
description="The name of a project",
examples=["Automated Coffee Experiences"],
max_length=255,
)
slug: str = Field(
description="The slug derived from the name of a project",
examples=["automated-coffee-experiences"],
)
description: str | None = Field(
default=None,
description="The description of a project",
examples=["Models for exploring automated coffee experiences."],
)
visibility: Visibility = Field(
description="The visibility of a project to users within the Collab Manager",
examples=["private"],
)
type: ProjectType = Field(
description="The type of project (general or training)",
examples=["general"],
)
users: UserMetadata = Field(
description="The metadata of users in a project",
examples=[{"leads": 1, "contributors": 2, "subscribers": 3}],
)
is_archived: bool = Field(
description="The archive status of a project", examples=[False]
)

@pydantic.field_validator("users", mode="before")
@field_validator("users", mode="before")
@classmethod
def transform_users(cls, data: t.Any):
if isinstance(data, UserMetadata):
Expand Down Expand Up @@ -87,18 +115,53 @@ def transform_users(cls, data: t.Any):
return data


class PatchProject(pydantic.BaseModel):
name: str | None = None
description: str | None = None
visibility: Visibility | None = None
type: ProjectType | None = None
is_archived: bool | None = None
class PatchProject(BaseModel):
name: str | None = Field(
default=None,
description="The name of a project provided for patching",
examples=["Robotic Coffee Experiences"],
max_length=255,
)
description: str | None = Field(
default=None,
description="The description of a project provided for patching",
examples=["Models for exploring robotic coffee experiences."],
max_length=1500,
)
visibility: Visibility | None = Field(
default=None,
description="The visibility of a project provided for patching",
examples=["private"],
)
type: ProjectType | None = Field(
default=None,
description="The type of project (general or training) provided for patching",
examples=["private"],
)
is_archived: bool | None = Field(
default=None,
description="The archive status of a project provided for patching",
examples=[True],
)


class PostProjectRequest(pydantic.BaseModel):
name: str
description: str | None = None
visibility: Visibility = Visibility.PRIVATE
class PostProjectRequest(BaseModel):
name: str = Field(
description="The name of a project provided at creation",
examples=["Automated Coffee Experiences"],
max_length=255,
)
description: str | None = Field(
default=None,
description="The description of a project provided at creation",
examples=["Models for exploring automated coffee experiences."],
max_length=1500,
)
visibility: Visibility = Field(
default=Visibility.PRIVATE,
description="The visibility of a project provided at creation",
examples=["private"],
)


class DatabaseProject(database.Base):
Expand Down
Loading

0 comments on commit 21442e3

Please sign in to comment.