12
12
"""Server-side model definitions"""
13
13
14
14
from datetime import datetime
15
- from typing import Optional , TypeVar
15
+ from typing import Optional , TypeVar , Dict , Any , List
16
16
from pydantic import (
17
17
BaseModel ,
18
- conlist ,
19
18
Field ,
19
+ model_serializer ,
20
+ field_validator ,
20
21
)
22
+ from typing_extensions import Annotated
21
23
from fastapi import Query
22
24
from fastapi_pagination import LimitOffsetPage , LimitOffsetParams
23
25
from fastapi_users .db import BeanieBaseUser
27
29
Document ,
28
30
PydanticObjectId ,
29
31
)
30
- from bson import ObjectId
31
32
from kernelci .api .models_base import DatabaseModel , ModelId
32
33
33
34
@@ -56,6 +57,7 @@ class SubscriptionStats(Subscription):
56
57
description = 'Timestamp of connection creation'
57
58
)
58
59
last_poll : Optional [datetime ] = Field (
60
+ default = None ,
59
61
description = 'Timestamp when connection last polled for data'
60
62
)
61
63
@@ -79,12 +81,20 @@ def get_indexes(cls):
79
81
class User (BeanieBaseUser , Document , # pylint: disable=too-many-ancestors
80
82
DatabaseModel ):
81
83
"""API User model"""
82
- username : Indexed ( str , unique = True )
83
- groups : conlist ( UserGroup , unique_items = True ) = Field (
84
+ username : Annotated [ str , Indexed ( unique = True )]
85
+ groups : List [ UserGroup ] = Field (
84
86
default = [],
85
- description = "A list of groups that user belongs to"
87
+ description = "A list of groups that the user belongs to"
86
88
)
87
89
90
+ @field_validator ('groups' )
91
+ def validate_groups (cls , groups ): # pylint: disable=no-self-argument
92
+ """Unique group constraint"""
93
+ unique_names = {group .name for group in groups }
94
+ if len (unique_names ) != len (groups ):
95
+ raise ValueError ("Groups must have unique names." )
96
+ return groups
97
+
88
98
class Settings (BeanieBaseUser .Settings ):
89
99
"""Configurations"""
90
100
# MongoDB collection name for model
@@ -97,23 +107,66 @@ def get_indexes(cls):
97
107
cls .Index ('email' , {'unique' : True }),
98
108
]
99
109
110
+ @model_serializer (when_used = 'json' )
111
+ def serialize_model (self ) -> Dict [str , Any ]:
112
+ """Serialize model by converting PyObjectId to string"""
113
+ values = self .__dict__ .copy ()
114
+ for field_name , value in values .items ():
115
+ if isinstance (value , PydanticObjectId ):
116
+ values [field_name ] = str (value )
117
+ return values
118
+
100
119
101
120
class UserRead (schemas .BaseUser [PydanticObjectId ], ModelId ):
102
121
"""Schema for reading a user"""
103
- username : Indexed (str , unique = True )
104
- groups : conlist (UserGroup , unique_items = True )
122
+ username : Annotated [str , Indexed (unique = True )]
123
+ groups : List [UserGroup ] = Field (default = [])
124
+
125
+ @field_validator ('groups' )
126
+ def validate_groups (cls , groups ): # pylint: disable=no-self-argument
127
+ """Unique group constraint"""
128
+ unique_names = {group .name for group in groups }
129
+ if len (unique_names ) != len (groups ):
130
+ raise ValueError ("Groups must have unique names." )
131
+ return groups
132
+
133
+ @model_serializer (when_used = 'json' )
134
+ def serialize_model (self ) -> Dict [str , Any ]:
135
+ """Serialize model by converting PyObjectId to string"""
136
+ values = self .__dict__ .copy ()
137
+ for field_name , value in values .items ():
138
+ if isinstance (value , PydanticObjectId ):
139
+ values [field_name ] = str (value )
140
+ return values
105
141
106
142
107
143
class UserCreate (schemas .BaseUserCreate ):
108
144
"""Schema for creating a user"""
109
- username : Indexed (str , unique = True )
110
- groups : Optional [conlist (str , unique_items = True )]
145
+ username : Annotated [str , Indexed (unique = True )]
146
+ groups : List [str ] = Field (default = [])
147
+
148
+ @field_validator ('groups' )
149
+ def validate_groups (cls , groups ): # pylint: disable=no-self-argument
150
+ """Unique group constraint"""
151
+ unique_names = set (groups )
152
+ if len (unique_names ) != len (groups ):
153
+ raise ValueError ("Groups must have unique names." )
154
+ return groups
111
155
112
156
113
157
class UserUpdate (schemas .BaseUserUpdate ):
114
158
"""Schema for updating a user"""
115
- username : Optional [Indexed (str , unique = True )]
116
- groups : Optional [conlist (str , unique_items = True )]
159
+ username : Annotated [Optional [str ], Indexed (unique = True ),
160
+ Field (default = None )]
161
+ groups : List [str ] = Field (default = [])
162
+
163
+ @field_validator ('groups' )
164
+ def validate_groups (cls , groups ): # pylint: disable=no-self-argument
165
+ """Unique group constraint"""
166
+ unique_names = set (groups )
167
+ if len (unique_names ) != len (groups ):
168
+ raise ValueError ("Groups must have unique names." )
169
+ return groups
117
170
118
171
119
172
# Pagination models
@@ -133,9 +186,3 @@ class PageModel(LimitOffsetPage[TypeVar("T")]):
133
186
This model is required to serialize paginated model data response"""
134
187
135
188
__params_type__ = CustomLimitOffsetParams
136
-
137
- class Config :
138
- """Configuration attributes for PageNode"""
139
- json_encoders = {
140
- ObjectId : str ,
141
- }
0 commit comments