Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
181454d
more group examples
pcrespov Sep 3, 2025
87170f2
adds support to groups
pcrespov Sep 3, 2025
37a40a1
updates OAS
pcrespov Sep 3, 2025
0dda812
services/webserver api version: 0.75.0 → 0.76.0
pcrespov Sep 3, 2025
b77b23b
splits tests
pcrespov Sep 3, 2025
4eb1475
splits tests further
pcrespov Sep 3, 2025
5dabf47
refactors profile groups
pcrespov Sep 3, 2025
adbdb5f
fixes statics
pcrespov Sep 3, 2025
5bb292d
only group
pcrespov Sep 3, 2025
4ceaa42
gets product support group
pcrespov Sep 3, 2025
b4af788
cleanup
pcrespov Sep 3, 2025
1e1c244
tests
pcrespov Sep 3, 2025
68fc0fc
separate access rights in repo
pcrespov Sep 3, 2025
3255e69
fixing test
pcrespov Sep 3, 2025
9a34351
fixing tests
pcrespov Sep 3, 2025
12dff6c
fix tests
pcrespov Sep 3, 2025
7e8aa54
fixes pylint
pcrespov Sep 3, 2025
eb57743
updates oas
pcrespov Sep 3, 2025
db375c7
more group examples
pcrespov Sep 3, 2025
0b12263
adds support to groups
pcrespov Sep 3, 2025
91874b0
updates OAS
pcrespov Sep 3, 2025
6443f69
services/webserver api version: 0.75.0 → 0.76.0
pcrespov Sep 3, 2025
f8fa043
splits tests
pcrespov Sep 3, 2025
a2c7071
splits tests further
pcrespov Sep 3, 2025
a803a21
refactors profile groups
pcrespov Sep 3, 2025
9f6e211
fixes statics
pcrespov Sep 3, 2025
b9fbb32
only group
pcrespov Sep 3, 2025
d472d28
gets product support group
pcrespov Sep 3, 2025
ecac948
cleanup
pcrespov Sep 3, 2025
62a2014
tests
pcrespov Sep 3, 2025
c7cda4d
separate access rights in repo
pcrespov Sep 3, 2025
4ac3276
fixing test
pcrespov Sep 3, 2025
eb49427
fixing tests
pcrespov Sep 3, 2025
6f76011
fix tests
pcrespov Sep 3, 2025
bd9a264
fixes pylint
pcrespov Sep 3, 2025
852c7c2
updates oas
pcrespov Sep 3, 2025
e759c8a
fixes test
pcrespov Sep 4, 2025
cec3cff
Update packages/models-library/src/models_library/api_schemas_webserv…
pcrespov Sep 4, 2025
a8f12f1
@sanderegg review: doc
pcrespov Sep 4, 2025
3da3fdb
cleanup
pcrespov Sep 4, 2025
cfece68
@bisgaard-itis review: rename
pcrespov Sep 4, 2025
c7e33d2
merges
pcrespov Sep 4, 2025
21537c3
Merge branch 'master' into is8291/me-profile-info
pcrespov Sep 4, 2025
08aa00c
@sanderegg review: rename
pcrespov Sep 4, 2025
b241651
updates OAS
pcrespov Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,48 @@ class GroupAccessRights(BaseModel):
)


class GroupGet(OutputSchema):
gid: GroupID = Field(..., description="the group ID")
label: str = Field(..., description="the group name")
description: str = Field(..., description="the group description")
thumbnail: AnyUrl | None = Field(
default=None, description="url to the group thumbnail"
)
access_rights: GroupAccessRights = Field(..., alias="accessRights")
class BasicGroupGet(OutputSchema):
gid: Annotated[GroupID, Field(description="the group's unique ID")]
label: Annotated[str, Field(description="the group's display name")]
description: str
thumbnail: Annotated[
AnyUrl | None, Field(description="a link to the group's thumbnail")
] = None

@field_validator("thumbnail", mode="before")
@classmethod
def _sanitize_legacy_data(cls, v):
if v:
# Enforces null if thumbnail is not valid URL or empty
with suppress(ValidationError):
return TypeAdapter(AnyHttpUrl).validate_python(v)
return None

@classmethod
def dump_basic_group_data(cls, group: Group) -> dict:
"""Helper function to extract common group data for schema conversion"""
return remap_keys(
group.model_dump(
include={
"gid",
"name",
"description",
"thumbnail",
},
exclude={
"inclusion_rules", # deprecated
},
exclude_unset=True,
by_alias=False,
),
rename={
"name": "label",
},
)


class GroupGet(BasicGroupGet):
access_rights: Annotated[GroupAccessRights, Field(alias="accessRights")]

inclusion_rules: Annotated[
dict[str, str],
Expand All @@ -77,24 +111,7 @@ def from_domain_model(cls, group: Group, access_rights: AccessRightsDict) -> Sel
# Adapts these domain models into this schema
return cls.model_validate(
{
**remap_keys(
group.model_dump(
include={
"gid",
"name",
"description",
"thumbnail",
},
exclude={
"inclusion_rules", # deprecated
},
exclude_unset=True,
by_alias=False,
),
rename={
"name": "label",
},
),
**cls.dump_basic_group_data(group),
"access_rights": access_rights,
}
)
Expand Down Expand Up @@ -136,15 +153,6 @@ def _update_json_schema_extra(schema: JsonDict) -> None:

model_config = ConfigDict(json_schema_extra=_update_json_schema_extra)

@field_validator("thumbnail", mode="before")
@classmethod
def _sanitize_legacy_data(cls, v):
if v:
# Enforces null if thumbnail is not valid URL or empty
with suppress(ValidationError):
return TypeAdapter(AnyHttpUrl).validate_python(v)
return None


class GroupCreate(InputSchema):
label: str
Expand Down Expand Up @@ -187,6 +195,12 @@ class MyGroupsGet(OutputSchema):
organizations: list[GroupGet] | None = None
all: GroupGet
product: GroupGet | None = None
support: Annotated[
BasicGroupGet | None,
Field(
description="Group ID of the app support team or None if no support is defined for this product"
),
] = None

model_config = ConfigDict(
json_schema_extra={
Expand Down Expand Up @@ -225,6 +239,12 @@ class MyGroupsGet(OutputSchema):
"description": "Open to all users",
"accessRights": {"read": True, "write": False, "delete": False},
},
"support": {
"gid": "2",
"label": "Support Team",
"description": "The support team of the application",
"thumbnail": "https://placekitten.com/15/15",
},
}
}
)
Expand All @@ -234,6 +254,7 @@ def from_domain_model(
cls,
groups_by_type: GroupsByTypeTuple,
my_product_group: tuple[Group, AccessRightsDict] | None,
product_support_group: Group | None,
) -> Self:
assert groups_by_type.primary # nosec
assert groups_by_type.everyone # nosec
Expand All @@ -249,6 +270,13 @@ def from_domain_model(
if my_product_group
else None
),
support=(
BasicGroupGet.model_validate(
BasicGroupGet.dump_basic_group_data(product_support_group)
)
if product_support_group
else None
),
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def from_domain_model(
my_groups_by_type: GroupsByTypeTuple,
my_product_group: tuple[Group, AccessRightsDict] | None,
my_preferences: AggregatedPreferences,
my_support_group: Group | None,
) -> Self:
data = remap_keys(
my_profile.model_dump(
Expand All @@ -152,7 +153,9 @@ def from_domain_model(
)
return cls(
**data,
groups=MyGroupsGet.from_domain_model(my_groups_by_type, my_product_group),
groups=MyGroupsGet.from_domain_model(
my_groups_by_type, my_product_group, my_support_group
),
preferences=my_preferences,
)

Expand Down
71 changes: 42 additions & 29 deletions packages/models-library/src/models_library/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,38 +39,51 @@ class Group(BaseModel):

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
everyone: JsonDict = {
"gid": 1,
"name": "Everyone",
"type": "everyone",
"description": "all users",
"thumbnail": None,
}
user: JsonDict = {
"gid": 2,
"name": "User",
"description": "primary group",
"type": "primary",
"thumbnail": None,
}
organization: JsonDict = {
"gid": 3,
"name": "Organization",
"description": "standard group",
"type": "standard",
"thumbnail": None,
"inclusionRules": {},
}
product: JsonDict = {
"gid": 4,
"name": "Product",
"description": "standard group for products",
"type": "standard",
"thumbnail": None,
}
support: JsonDict = {
"gid": 5,
"name": "Support",
"description": "support group",
"type": "standard",
"thumbnail": None,
}

schema.update(
{
"examples": [
{
"gid": 1,
"name": "Everyone",
"type": "everyone",
"description": "all users",
"thumbnail": None,
},
{
"gid": 2,
"name": "User",
"description": "primary group",
"type": "primary",
"thumbnail": None,
},
{
"gid": 3,
"name": "Organization",
"description": "standard group",
"type": "standard",
"thumbnail": None,
"inclusionRules": {},
},
{
"gid": 4,
"name": "Product",
"description": "standard group for products",
"type": "standard",
"thumbnail": None,
},
everyone,
user,
organization,
product,
support,
]
}
)
Expand Down
22 changes: 18 additions & 4 deletions packages/models-library/tests/test_users.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from models_library.api_schemas_webserver.users import (
MyProfileRestGet,
)
Expand All @@ -7,7 +8,11 @@
from pydantic import TypeAdapter


def test_adapter_from_model_to_schema():
@pytest.mark.parametrize("with_support_group", [True, False])
@pytest.mark.parametrize("with_standard_groups", [True, False])
def test_adapter_from_model_to_schema(
with_support_group: bool, with_standard_groups: bool
):
my_profile = MyProfile.model_validate(MyProfile.model_json_schema()["example"])

groups = TypeAdapter(list[Group]).validate_python(
Expand All @@ -17,13 +22,22 @@ def test_adapter_from_model_to_schema():
ar = AccessRightsDict(read=False, write=False, delete=False)

my_groups_by_type = GroupsByTypeTuple(
primary=(groups[1], ar), standard=[(groups[2], ar)], everyone=(groups[0], ar)
primary=(groups[1], ar),
standard=[(groups[2], ar)] if with_standard_groups else [],
everyone=(groups[0], ar),
)
my_product_group = groups[-1], AccessRightsDict(
my_product_group = groups[3], AccessRightsDict(
read=False, write=False, delete=False
)

my_support_group = groups[4]

my_preferences = {"foo": Preference(default_value=3, value=1)}

MyProfileRestGet.from_domain_model(
my_profile, my_groups_by_type, my_product_group, my_preferences
my_profile,
my_groups_by_type,
my_product_group,
my_preferences,
my_support_group if with_support_group else None,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
# pylint: disable=unused-argument
# pylint: disable=unused-variable
"""
Fixtures to produce fake data for a product:
- it is self-consistent
- granular customization by overriding fixtures
Fixtures to produce fake data for a product:
- it is self-consistent
- granular customization by overriding fixtures
"""

from typing import Any
Expand Down Expand Up @@ -65,11 +65,25 @@ def bcc_email(request: pytest.FixtureRequest, product_name: ProductName) -> Emai
)


@pytest.fixture
def support_standard_group_id(faker: Faker) -> int | None:
# NOTE: override to change
return None


@pytest.fixture
def product(
faker: Faker, product_name: ProductName, support_email: EmailStr
faker: Faker,
product_name: ProductName,
support_email: EmailStr,
support_standard_group_id: int | None,
) -> dict[str, Any]:
return random_product(name=product_name, support_email=support_email, fake=faker)
return random_product(
name=product_name,
support_email=support_email,
support_standard_group_id=support_standard_group_id,
fake=faker,
)


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def fake_task(**overrides) -> dict[str, Any]:
def random_product(
*,
group_id: int | None = None,
support_standard_group_id: int | None = None,
registration_email_template: str | None = None,
fake: Faker = DEFAULT_FAKER,
**overrides,
Expand Down Expand Up @@ -302,6 +303,7 @@ def random_product(
"priority": fake.pyint(0, 10),
"max_open_studies_per_user": fake.pyint(1, 10),
"group_id": group_id,
"support_standard_group_id": support_standard_group_id,
}

if ui := fake.random_element(
Expand Down
2 changes: 1 addition & 1 deletion services/web/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.75.0
0.76.0
2 changes: 1 addition & 1 deletion services/web/server/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.75.0
current_version = 0.76.0
commit = True
message = services/webserver api version: {current_version} → {new_version}
tag = False
Expand Down
Loading
Loading