Skip to content

Commit 396eb7a

Browse files
committed
moves users
1 parent ed59622 commit 396eb7a

File tree

6 files changed

+115
-115
lines changed

6 files changed

+115
-115
lines changed

api/specs/web-server/_users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from models_library.api_schemas_webserver.users import (
1111
MyProfileGet,
1212
MyProfilePatch,
13-
PreRegisteredUserGet,
1413
SearchQueryParams,
1514
UserGet,
1615
)
@@ -26,6 +25,7 @@
2625
from simcore_service_webserver.users._notifications_handlers import (
2726
_NotificationPathParams,
2827
)
28+
from simcore_service_webserver.users._schemas import PreRegisteredUserGet
2929
from simcore_service_webserver.users._tokens_handlers import _TokenPathParams
3030
from simcore_service_webserver.users.schemas import (
3131
PermissionGet,

packages/models-library/src/models_library/api_schemas_webserver/groups.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import Annotated, Any, Self, TypeVar
33

44
from common_library.basic_types import DEFAULT_FACTORY
5-
from models_library.groups import EVERYONE_GROUP_ID
65
from pydantic import (
76
AnyHttpUrl,
87
AnyUrl,
@@ -17,6 +16,7 @@
1716

1817
from ..emails import LowerCaseEmailStr
1918
from ..groups import (
19+
EVERYONE_GROUP_ID,
2020
AccessRightsDict,
2121
Group,
2222
GroupID,

packages/models-library/src/models_library/api_schemas_webserver/users.py

Lines changed: 9 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
11
import re
2-
import sys
3-
from contextlib import suppress
42
from datetime import date
53
from enum import Enum
6-
from typing import Annotated, Any, Final, Literal
4+
from typing import Annotated, Any, Literal
75

8-
import pycountry
9-
from models_library.api_schemas_webserver._base import InputSchema, OutputSchema
10-
from models_library.api_schemas_webserver.groups import MyGroupsGet
11-
from models_library.api_schemas_webserver.users_preferences import AggregatedPreferences
12-
from models_library.basic_types import IDStr
13-
from models_library.emails import LowerCaseEmailStr
14-
from models_library.products import ProductName
15-
from models_library.users import FirstNameStr, LastNameStr, UserID
16-
from pydantic import (
17-
BaseModel,
18-
ConfigDict,
19-
Field,
20-
ValidationInfo,
21-
field_validator,
22-
model_validator,
23-
)
24-
from simcore_postgres_database.models.users import UserStatus
6+
from common_library.users_enums import UserStatus
7+
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator
258

9+
from ..basic_types import IDStr
10+
from ..emails import LowerCaseEmailStr
11+
from ..products import ProductName
12+
from ..users import FirstNameStr, LastNameStr, UserID
2613
from ._base import InputSchema, OutputSchema
14+
from .groups import MyGroupsGet
15+
from .users_preferences import AggregatedPreferences
2716

2817

2918
class MyProfilePrivacyGet(OutputSchema):
@@ -187,86 +176,3 @@ def _consistency_check(cls, v, info: ValidationInfo):
187176
msg = f"{registered=} and {status=} is not allowed"
188177
raise ValueError(msg)
189178
return v
190-
191-
192-
MAX_BYTES_SIZE_EXTRAS: Final[int] = 512
193-
194-
195-
class PreRegisteredUserGet(InputSchema):
196-
first_name: str
197-
last_name: str
198-
email: LowerCaseEmailStr
199-
institution: str | None = Field(
200-
default=None, description="company, university, ..."
201-
)
202-
phone: str | None
203-
# billing details
204-
address: str
205-
city: str
206-
state: str | None = Field(default=None)
207-
postal_code: str
208-
country: str
209-
extras: Annotated[
210-
dict[str, Any],
211-
Field(
212-
default_factory=dict,
213-
description="Keeps extra information provided in the request form. At most MAX_NUM_EXTRAS fields",
214-
),
215-
]
216-
217-
model_config = ConfigDict(str_strip_whitespace=True, str_max_length=200)
218-
219-
@model_validator(mode="before")
220-
@classmethod
221-
def _preprocess_aliases_and_extras(cls, values):
222-
# multiple aliases for "institution"
223-
alias_by_priority = ("companyName", "company", "university", "universityName")
224-
if "institution" not in values:
225-
226-
for alias in alias_by_priority:
227-
if alias in values:
228-
values["institution"] = values.pop(alias)
229-
230-
# collect extras
231-
extra_fields = {}
232-
field_names_and_aliases = (
233-
set(cls.model_fields.keys())
234-
| {f.alias for f in cls.model_fields.values() if f.alias}
235-
| set(alias_by_priority)
236-
)
237-
for key, value in values.items():
238-
if key not in field_names_and_aliases:
239-
extra_fields[key] = value
240-
if sys.getsizeof(extra_fields) > MAX_BYTES_SIZE_EXTRAS:
241-
extra_fields.pop(key)
242-
break
243-
244-
for key in extra_fields:
245-
values.pop(key)
246-
247-
values.setdefault("extras", {})
248-
values["extras"].update(extra_fields)
249-
250-
return values
251-
252-
@field_validator("first_name", "last_name", "institution", mode="before")
253-
@classmethod
254-
def _pre_normalize_given_names(cls, v):
255-
if v:
256-
with suppress(Exception): # skip if funny characters
257-
name = re.sub(r"\s+", " ", v)
258-
return re.sub(r"\b\w+\b", lambda m: m.group(0).capitalize(), name)
259-
return v
260-
261-
@field_validator("country", mode="before")
262-
@classmethod
263-
def _pre_check_and_normalize_country(cls, v):
264-
if v:
265-
try:
266-
return pycountry.countries.lookup(v).name
267-
except LookupError as err:
268-
raise ValueError(v) from err
269-
return v
270-
271-
272-
assert set(PreRegisteredUserGet.model_fields).issubset(UserGet.model_fields) # nosec
Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
""" models for rest api schemas, i.e. those defined in openapi.json
1+
""" input/output datasets used in the rest-API
22
3+
NOTE: Most of the model schemas are in `models_library.api_schemas_webserver.users`, the rest (hidden or needs a dependency) is here
34
"""
45

56

7+
import re
8+
import sys
9+
from contextlib import suppress
10+
from typing import Annotated, Any, Final
11+
12+
import pycountry
13+
from models_library.api_schemas_webserver._base import InputSchema
14+
from models_library.api_schemas_webserver.users import UserGet
15+
from models_library.emails import LowerCaseEmailStr
616
from models_library.users import UserID
7-
from pydantic import BaseModel, Field
17+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
818
from servicelib.request_keys import RQT_USERID_KEY
919

1020
from .._constants import RQ_PRODUCT_KEY
@@ -13,3 +23,89 @@
1323
class UsersRequestContext(BaseModel):
1424
user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required]
1525
product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required]
26+
27+
28+
MAX_BYTES_SIZE_EXTRAS: Final[int] = 512
29+
30+
31+
class PreRegisteredUserGet(InputSchema):
32+
# NOTE: validators need pycountry!
33+
34+
first_name: str
35+
last_name: str
36+
email: LowerCaseEmailStr
37+
institution: str | None = Field(
38+
default=None, description="company, university, ..."
39+
)
40+
phone: str | None
41+
# billing details
42+
address: str
43+
city: str
44+
state: str | None = Field(default=None)
45+
postal_code: str
46+
country: str
47+
extras: Annotated[
48+
dict[str, Any],
49+
Field(
50+
default_factory=dict,
51+
description="Keeps extra information provided in the request form. At most MAX_NUM_EXTRAS fields",
52+
),
53+
]
54+
55+
model_config = ConfigDict(str_strip_whitespace=True, str_max_length=200)
56+
57+
@model_validator(mode="before")
58+
@classmethod
59+
def _preprocess_aliases_and_extras(cls, values):
60+
# multiple aliases for "institution"
61+
alias_by_priority = ("companyName", "company", "university", "universityName")
62+
if "institution" not in values:
63+
64+
for alias in alias_by_priority:
65+
if alias in values:
66+
values["institution"] = values.pop(alias)
67+
68+
# collect extras
69+
extra_fields = {}
70+
field_names_and_aliases = (
71+
set(cls.model_fields.keys())
72+
| {f.alias for f in cls.model_fields.values() if f.alias}
73+
| set(alias_by_priority)
74+
)
75+
for key, value in values.items():
76+
if key not in field_names_and_aliases:
77+
extra_fields[key] = value
78+
if sys.getsizeof(extra_fields) > MAX_BYTES_SIZE_EXTRAS:
79+
extra_fields.pop(key)
80+
break
81+
82+
for key in extra_fields:
83+
values.pop(key)
84+
85+
values.setdefault("extras", {})
86+
values["extras"].update(extra_fields)
87+
88+
return values
89+
90+
@field_validator("first_name", "last_name", "institution", mode="before")
91+
@classmethod
92+
def _pre_normalize_given_names(cls, v):
93+
if v:
94+
with suppress(Exception): # skip if funny characters
95+
name = re.sub(r"\s+", " ", v)
96+
return re.sub(r"\b\w+\b", lambda m: m.group(0).capitalize(), name)
97+
return v
98+
99+
@field_validator("country", mode="before")
100+
@classmethod
101+
def _pre_check_and_normalize_country(cls, v):
102+
if v:
103+
try:
104+
return pycountry.countries.lookup(v).name
105+
except LookupError as err:
106+
raise ValueError(v) from err
107+
return v
108+
109+
110+
# asserts field names are in sync
111+
assert set(PreRegisteredUserGet.model_fields).issubset(UserGet.model_fields) # nosec

services/web/server/src/simcore_service_webserver/users/_users_rest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from models_library.api_schemas_webserver.users import (
66
MyProfileGet,
77
MyProfilePatch,
8-
PreRegisteredUserGet,
98
SearchQueryParams,
109
)
1110
from servicelib.aiohttp import status
@@ -23,7 +22,7 @@
2322
from ..utils_aiohttp import envelope_json_response
2423
from . import _users_service, api
2524
from ._constants import FMSG_MISSING_CONFIG_WITH_OEC
26-
from ._schemas import UsersRequestContext
25+
from ._schemas import PreRegisteredUserGet, UsersRequestContext
2726
from .exceptions import (
2827
AlreadyPreRegisteredError,
2928
MissingGroupExtraPropertiesForProductError,

services/web/server/tests/unit/with_dbs/03/test_users.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@
1818
from aiopg.sa.connection import SAConnection
1919
from faker import Faker
2020
from models_library.api_schemas_webserver.auth import AccountRequestInfo
21-
from models_library.api_schemas_webserver.users import (
22-
MAX_BYTES_SIZE_EXTRAS,
23-
MyProfileGet,
24-
PreRegisteredUserGet,
25-
UserGet,
26-
)
21+
from models_library.api_schemas_webserver.users import MyProfileGet, UserGet
2722
from models_library.generics import Envelope
2823
from psycopg2 import OperationalError
2924
from pytest_simcore.helpers.assert_checks import assert_status
@@ -39,6 +34,10 @@
3934
from simcore_service_webserver.users._preferences_api import (
4035
get_frontend_user_preferences_aggregation,
4136
)
37+
from simcore_service_webserver.users._schemas import (
38+
MAX_BYTES_SIZE_EXTRAS,
39+
PreRegisteredUserGet,
40+
)
4241

4342

4443
@pytest.fixture

0 commit comments

Comments
 (0)