|
2 | 2 | from typing import Annotated, Any |
3 | 3 | from uuid import NAMESPACE_DNS, UUID, uuid5 |
4 | 4 |
|
5 | | -from pydantic import PlainSerializer, PlainValidator, ValidationInfo, model_validator |
| 5 | +from pydantic import AliasChoices, PlainSerializer, PlainValidator, ValidationInfo, model_validator |
6 | 6 | from sqlalchemy.dialects import postgresql |
7 | 7 | from sqlalchemy.ext.mutable import MutableDict |
8 | 8 | from sqlalchemy.types import PickleType |
|
15 | 15 | """Default UUID5 namespace for campaigns""" |
16 | 16 |
|
17 | 17 |
|
18 | | -def jsonb_column(name: str) -> Any: |
19 | | - """Constructor for a Field based on a JSONB database column.""" |
| 18 | +def jsonb_column(name: str, aliases: list[str] | None = None) -> Any: |
| 19 | + """Constructor for a Field based on a JSONB database column. |
| 20 | +
|
| 21 | + If provided, a list of aliases will be used to construct a pydantic |
| 22 | + ``AliasChoices`` object for the field's validation alias, which improves |
| 23 | + usability by making model validation more flexible (e.g., having "metadata" |
| 24 | + and "metadata_" refer to the same field). |
| 25 | +
|
| 26 | + Additionally, the first alias in the list will be used for the model's |
| 27 | + serialization alias. |
| 28 | + """ |
| 29 | + schema_extra = {} |
| 30 | + if aliases: |
| 31 | + schema_extra = { |
| 32 | + "validation_alias": AliasChoices(*aliases), |
| 33 | + "serialization_alias": aliases[0], |
| 34 | + } |
20 | 35 | return Field( |
21 | 36 | sa_column=Column(name, MutableDict.as_mutable(postgresql.JSONB)), |
22 | 37 | default_factory=dict, |
| 38 | + schema_extra=schema_extra, |
23 | 39 | ) |
24 | 40 |
|
25 | 41 |
|
@@ -68,8 +84,8 @@ class CampaignBase(BaseSQLModel): |
68 | 84 | default=StatusEnum.waiting, |
69 | 85 | sa_column=Column("status", Enum(StatusEnum, length=20, native_enum=False, create_constraint=False)), |
70 | 86 | ) |
71 | | - metadata_: dict = jsonb_column("metadata") |
72 | | - configuration: dict = jsonb_column("configuration") |
| 87 | + metadata_: dict = jsonb_column("metadata", aliases=["metadata", "metadata_"]) |
| 88 | + configuration: dict = jsonb_column("configuration", aliases=["configuration", "data", "spec"]) |
73 | 89 |
|
74 | 90 |
|
75 | 91 | class CampaignModel(CampaignBase): |
@@ -114,8 +130,8 @@ class NodeBase(BaseSQLModel): |
114 | 130 | default=StatusEnum.waiting, |
115 | 131 | sa_column=Column("status", Enum(StatusEnum, length=20, native_enum=False, create_constraint=False)), |
116 | 132 | ) |
117 | | - metadata_: dict = jsonb_column("metadata") |
118 | | - configuration: dict = jsonb_column("configuration") |
| 133 | + metadata_: dict = jsonb_column("metadata", aliases=["metadata", "metadata_"]) |
| 134 | + configuration: dict = jsonb_column("configuration", aliases=["configuration", "data", "spec"]) |
119 | 135 |
|
120 | 136 |
|
121 | 137 | class NodeModel(NodeBase): |
@@ -150,8 +166,8 @@ class EdgeBase(BaseSQLModel): |
150 | 166 | namespace: UUID = Field(foreign_key="campaigns_v2.id") |
151 | 167 | source: UUID = Field(foreign_key="nodes_v2.id") |
152 | 168 | target: UUID = Field(foreign_key="nodes_v2.id") |
153 | | - metadata_: dict = jsonb_column("metadata") |
154 | | - configuration: dict = jsonb_column("configuration") |
| 169 | + metadata_: dict = jsonb_column("metadata", aliases=["metadata", "metadata_"]) |
| 170 | + configuration: dict = jsonb_column("configuration", aliases=["configuration", "data", "spec"]) |
155 | 171 |
|
156 | 172 |
|
157 | 173 | class EdgeModel(EdgeBase): |
@@ -197,8 +213,8 @@ class ManifestBase(BaseSQLModel): |
197 | 213 | default=ManifestKind.other, |
198 | 214 | sa_column=Column("kind", Enum(ManifestKind, length=20, native_enum=False, create_constraint=False)), |
199 | 215 | ) |
200 | | - metadata_: dict = jsonb_column("metadata") |
201 | | - spec: dict = jsonb_column("spec") |
| 216 | + metadata_: dict = jsonb_column("metadata", aliases=["metadata", "metadata_"]) |
| 217 | + spec: dict = jsonb_column("spec", aliases=["spec", "configuration", "data"]) |
202 | 218 |
|
203 | 219 |
|
204 | 220 | class ManifestModel(ManifestBase): |
@@ -236,10 +252,17 @@ class Task(SQLModel, table=True): |
236 | 252 | last_processed_at: datetime |
237 | 253 | finished_at: datetime |
238 | 254 | wms_id: str |
239 | | - # TODO site_affinity should probably be a mutable array |
240 | | - site_affinity: list[str] = Field(sa_column=Column(postgresql.ARRAY(String()))) |
241 | | - status: int # TODO make enum |
242 | | - previous_status: int # TODO make enum |
| 255 | + site_affinity: list[str] = Field( |
| 256 | + sa_column=Column("site_affinity", MutableList.as_mutable(postgresql.ARRAY(String()))) |
| 257 | + ) |
| 258 | + status: StatusField = Field( |
| 259 | + sa_column=Column("status", Enum(StatusEnum, length=20, native_enum=False, create_constraint=False)), |
| 260 | + ) |
| 261 | + previous_status: StatusField = Field( |
| 262 | + sa_column=Column( |
| 263 | + "previous_status", Enum(StatusEnum, length=20, native_enum=False, create_constraint=False) |
| 264 | + ), |
| 265 | + ) |
243 | 266 |
|
244 | 267 |
|
245 | 268 | class ActivityLogBase(BaseSQLModel): |
|
0 commit comments