Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

♻️ web-server: Refactor users domain for improved layer separation and upgrading to asyncpg #6937

Merged
merged 126 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
497dc84
first round
pcrespov Dec 10, 2024
c0f3318
fixes engines
pcrespov Dec 10, 2024
529c28f
handlers and mvs models
pcrespov Dec 10, 2024
3120ff7
cleanup
pcrespov Dec 10, 2024
42030b4
cleanup
pcrespov Dec 10, 2024
410a651
rename
pcrespov Dec 10, 2024
7c252d2
minor
pcrespov Dec 10, 2024
76a4a5f
updating db in api module
pcrespov Dec 10, 2024
d37149b
mypy
pcrespov Dec 10, 2024
a2859ef
rename
pcrespov Dec 11, 2024
b4f6ce1
moves schemas to models_library
pcrespov Dec 11, 2024
ed59622
moves users
pcrespov Dec 11, 2024
396eb7a
moves users
pcrespov Dec 11, 2024
0b73497
common
pcrespov Dec 11, 2024
aad32e7
moves to models-library
pcrespov Dec 11, 2024
e3ffe7d
annotated
pcrespov Dec 11, 2024
9e3c9fa
some rename
pcrespov Dec 11, 2024
44261bf
rest shcmeas
pcrespov Dec 11, 2024
a008fd6
tokens update
pcrespov Dec 11, 2024
57fb63c
tokens
pcrespov Dec 11, 2024
e4a0b5e
tokens tests
pcrespov Dec 11, 2024
43c4250
fixes
pcrespov Dec 11, 2024
832db9e
utils mixin
pcrespov Dec 11, 2024
1bf60ee
asyncpg version of aggregation
pcrespov Dec 11, 2024
18a68ab
cleanup
pcrespov Dec 11, 2024
3562640
cleanup
pcrespov Dec 11, 2024
c02392a
rm async
pcrespov Dec 12, 2024
401282b
adds tests
pcrespov Dec 12, 2024
256214e
mypy
pcrespov Dec 12, 2024
0fb7368
_common rename
pcrespov Dec 12, 2024
a0c6c05
isolated tests
pcrespov Dec 12, 2024
3e37a1b
bad merge
pcrespov Dec 12, 2024
4a694da
mv api -> service
pcrespov Dec 12, 2024
835b8d8
cleanup
pcrespov Dec 12, 2024
f570190
bad import
pcrespov Dec 12, 2024
c8a9e7e
drop unnecesary dependencies
pcrespov Dec 12, 2024
7a30cc6
cleanup
pcrespov Dec 12, 2024
12fc813
renames
pcrespov Dec 12, 2024
f2d28d3
error handling
pcrespov Dec 12, 2024
2b7c40e
doc
pcrespov Dec 12, 2024
5725250
update OAS
pcrespov Dec 12, 2024
9541669
first round
pcrespov Dec 10, 2024
37b580e
fixes engines
pcrespov Dec 10, 2024
d99ed9f
handlers and mvs models
pcrespov Dec 10, 2024
4862222
cleanup
pcrespov Dec 10, 2024
48bf359
cleanup
pcrespov Dec 10, 2024
de6c58d
rename
pcrespov Dec 10, 2024
b4f1c04
minor
pcrespov Dec 10, 2024
ada1d77
updating db in api module
pcrespov Dec 10, 2024
bcca9de
mypy
pcrespov Dec 10, 2024
4eb69ce
rename
pcrespov Dec 11, 2024
8b2fa18
moves schemas to models_library
pcrespov Dec 11, 2024
71380f2
moves users
pcrespov Dec 11, 2024
10d6c20
moves users
pcrespov Dec 11, 2024
fa1299b
common
pcrespov Dec 11, 2024
8265042
moves to models-library
pcrespov Dec 11, 2024
f0edcc0
annotated
pcrespov Dec 11, 2024
c8f89f7
some rename
pcrespov Dec 11, 2024
e4cc55e
rest shcmeas
pcrespov Dec 11, 2024
669318f
tokens update
pcrespov Dec 11, 2024
0bfa09c
tokens
pcrespov Dec 11, 2024
f2fa971
tokens tests
pcrespov Dec 11, 2024
8a2be67
fixes
pcrespov Dec 11, 2024
9036079
utils mixin
pcrespov Dec 11, 2024
5a23555
asyncpg version of aggregation
pcrespov Dec 11, 2024
ce52ed5
cleanup
pcrespov Dec 11, 2024
b5a8a40
cleanup
pcrespov Dec 11, 2024
bd4458c
rm async
pcrespov Dec 12, 2024
228c9f3
adds tests
pcrespov Dec 12, 2024
142950e
mypy
pcrespov Dec 12, 2024
2c81839
_common rename
pcrespov Dec 12, 2024
2da7901
isolated tests
pcrespov Dec 12, 2024
9a4ed7c
bad merge
pcrespov Dec 12, 2024
f5fe90d
mv api -> service
pcrespov Dec 12, 2024
38bbedc
cleanup
pcrespov Dec 12, 2024
e64bdfc
bad import
pcrespov Dec 12, 2024
efa59d0
drop unnecesary dependencies
pcrespov Dec 12, 2024
079b2de
cleanup
pcrespov Dec 12, 2024
21ac861
renames
pcrespov Dec 12, 2024
7326f20
error handling
pcrespov Dec 12, 2024
80a2426
Merge branch 'is1779/refactor-users' of github.com:pcrespov/osparc-si…
pcrespov Dec 12, 2024
e8d4782
cleanup
pcrespov Dec 12, 2024
ef7e409
tests users pass
pcrespov Dec 12, 2024
24ffb2a
fixes
pcrespov Dec 12, 2024
c857488
tools
pcrespov Dec 12, 2024
d198d56
models
pcrespov Dec 12, 2024
ed38767
model adapters
pcrespov Dec 12, 2024
dd0dcc5
utils testing
pcrespov Dec 12, 2024
24d3711
rename
pcrespov Dec 12, 2024
dd95af0
fixes
pcrespov Dec 12, 2024
7dcf156
updates OAS
pcrespov Dec 12, 2024
93846e7
doc
pcrespov Dec 12, 2024
78cf56e
udo Docker
pcrespov Dec 12, 2024
b7b1dd1
doc
pcrespov Dec 12, 2024
da56d35
@GitHK review: rename and doc
pcrespov Dec 13, 2024
38d29f2
@sanderegg review: mv to dict_tools
pcrespov Dec 13, 2024
58428b1
@sanderegg review: mv to dict_tools
pcrespov Dec 13, 2024
1f4153a
@GitHK review: sentinel
pcrespov Dec 13, 2024
e62814b
mypy
pcrespov Dec 13, 2024
afecd8d
tests
pcrespov Dec 13, 2024
a0b47ec
Merge branch 'master' into is1779/refactor-users
pcrespov Dec 13, 2024
1503da4
fixing test
pcrespov Dec 13, 2024
5e993f7
fixing test
pcrespov Dec 13, 2024
6f077d3
@sanderegg review: rename
pcrespov Dec 13, 2024
a9b48dc
@sanderegg review: await
pcrespov Dec 13, 2024
50cd075
pylint
pcrespov Dec 13, 2024
dec6270
Merge branch 'master' into is1779/refactor-users
pcrespov Dec 16, 2024
ba14597
cleanup
pcrespov Dec 16, 2024
aee07f9
cleanup
pcrespov Dec 16, 2024
f00c127
renameing groups
pcrespov Dec 16, 2024
057a032
undo function
pcrespov Dec 16, 2024
5f093bb
annotated test
pcrespov Dec 16, 2024
6c4f709
fixes
pcrespov Dec 16, 2024
9e0a825
fixes front-end
pcrespov Dec 16, 2024
2906201
minor
pcrespov Dec 16, 2024
2371638
minor
pcrespov Dec 16, 2024
2937250
Merge branch 'master' into is1779/refactor-users
pcrespov Dec 16, 2024
c745d26
minor
pcrespov Dec 16, 2024
2151a15
update
pcrespov Dec 16, 2024
e853393
Merge branch 'master' into is1779/refactor-users
pcrespov Dec 16, 2024
c530f93
drop uuids from tokens
pcrespov Dec 16, 2024
1319a38
OM front-end
pcrespov Dec 16, 2024
53cc350
Merge branch 'master' into is1779/refactor-users
pcrespov Dec 16, 2024
76cbf58
Merge branch 'is1779/refactor-users' of github.com:pcrespov/osparc-si…
pcrespov Dec 16, 2024
4d4351f
mypy
pcrespov Dec 16, 2024
56ea4f7
fixes tokens
pcrespov Dec 16, 2024
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
46 changes: 23 additions & 23 deletions api/specs/web-server/_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@
from typing import Annotated

from fastapi import APIRouter, Depends, status
from models_library.api_schemas_webserver.users import MyProfileGet, MyProfilePatch
from models_library.api_schemas_webserver.users import (
MyPermissionGet,
MyProfileGet,
MyProfilePatch,
MyTokenCreate,
MyTokenGet,
UserGet,
UsersSearchQueryParams,
)
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
from models_library.generics import Envelope
from models_library.user_preferences import PreferenceIdentifier
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.users._handlers import PreUserProfile, _SearchQueryParams
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet
from simcore_service_webserver.users._notifications import (
UserNotification,
UserNotificationCreate,
UserNotificationPatch,
)
from simcore_service_webserver.users._notifications_handlers import (
_NotificationPathParams,
)
from simcore_service_webserver.users._schemas import UserProfile
from simcore_service_webserver.users._tokens_handlers import _TokenPathParams
from simcore_service_webserver.users.schemas import (
PermissionGet,
ThirdPartyToken,
TokenCreate,
)
from simcore_service_webserver.users._notifications_rest import _NotificationPathParams
from simcore_service_webserver.users._tokens_rest import _TokenPathParams

router = APIRouter(prefix=f"/{API_VTAG}", tags=["user"])

Expand Down Expand Up @@ -63,32 +63,32 @@ async def replace_my_profile(_profile: MyProfilePatch):
status_code=status.HTTP_204_NO_CONTENT,
)
async def set_frontend_preference(
preference_id: PreferenceIdentifier, # noqa: ARG001
body_item: PatchRequestBody, # noqa: ARG001
preference_id: PreferenceIdentifier,
body_item: PatchRequestBody,
):
...


@router.get(
"/me/tokens",
response_model=Envelope[list[ThirdPartyToken]],
response_model=Envelope[list[MyTokenGet]],
)
async def list_tokens():
...


@router.post(
"/me/tokens",
response_model=Envelope[ThirdPartyToken],
response_model=Envelope[MyTokenGet],
status_code=status.HTTP_201_CREATED,
)
async def create_token(_token: TokenCreate):
async def create_token(_token: MyTokenCreate):
...


@router.get(
"/me/tokens/{service}",
response_model=Envelope[ThirdPartyToken],
response_model=Envelope[MyTokenGet],
)
async def get_token(_params: Annotated[_TokenPathParams, Depends()]):
...
Expand Down Expand Up @@ -131,30 +131,30 @@ async def mark_notification_as_read(

@router.get(
"/me/permissions",
response_model=Envelope[list[PermissionGet]],
response_model=Envelope[list[MyPermissionGet]],
)
async def list_user_permissions():
...


@router.get(
"/users:search",
response_model=Envelope[list[UserProfile]],
response_model=Envelope[list[UserGet]],
tags=[
"po",
],
)
async def search_users(_params: Annotated[_SearchQueryParams, Depends()]):
async def search_users(_params: Annotated[UsersSearchQueryParams, Depends()]):
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
...


@router.post(
"/users:pre-register",
response_model=Envelope[UserProfile],
response_model=Envelope[UserGet],
tags=[
"po",
],
)
async def pre_register_user(_body: PreUserProfile):
async def pre_register_user(_body: PreRegisteredUserGet):
...
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
""" Utils to operate with dicts """
""" A collection of free functions to manipulate dicts
"""

from copy import deepcopy
from typing import Any, Mapping
from collections.abc import Mapping
from copy import copy, deepcopy
from typing import Any

ConfigDict = dict[str, Any]

def remap_keys(data: dict, rename: dict[str, str]) -> dict[str, Any]:
"""A new dict that renames the keys of a dict while keeping the values unchanged

NOTE: Does not support renaming of nested keys
"""
return {rename.get(k, k): v for k, v in data.items()}


def get_from_dict(obj: Mapping[str, Any], dotted_key: str, default=None) -> Any:
Expand All @@ -28,10 +36,10 @@ def copy_from_dict(
#

if include is None:
return deepcopy(data) if deep else data.copy()
return deepcopy(data) if deep else copy(data)

if include == ...:
return deepcopy(data) if deep else data.copy()
return deepcopy(data) if deep else copy(data)

if isinstance(include, set):
return {key: data[key] for key in include}
Expand All @@ -46,7 +54,7 @@ def copy_from_dict(

def update_dict(obj: dict, **updates):
for key, update_value in updates.items():
if callable(update_value):
update_value = update_value(obj[key])
obj.update({key: update_value})
obj.update(
{key: update_value(obj[key]) if callable(update_value) else update_value}
)
return obj
59 changes: 59 additions & 0 deletions packages/common-library/src/common_library/users_enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from enum import Enum
from functools import total_ordering

_USER_ROLE_TO_LEVEL = {
"ANONYMOUS": 0,
"GUEST": 10,
"USER": 20,
"TESTER": 30,
"PRODUCT_OWNER": 40,
"ADMIN": 100,
}


@total_ordering
class UserRole(Enum):
"""SORTED enumeration of user roles

A role defines a set of privileges the user can perform
Roles are sorted from lower to highest privileges
USER is the role assigned by default A user with a higher/lower role is denoted super/infra user

ANONYMOUS : The user is not logged in
GUEST : Temporary user with very limited access. Main used for demos and for a limited amount of time
USER : Registered user. Basic permissions to use the platform [default]
TESTER : Upgraded user. First level of super-user with privileges to test the framework.
Can use everything but does not have an effect in other users or actual data
ADMIN : Framework admin.

See security_access.py
"""

ANONYMOUS = "ANONYMOUS"
GUEST = "GUEST"
USER = "USER"
TESTER = "TESTER"
PRODUCT_OWNER = "PRODUCT_OWNER"
ADMIN = "ADMIN"

@property
def privilege_level(self) -> int:
return _USER_ROLE_TO_LEVEL[self.name]

def __lt__(self, other: "UserRole") -> bool:
if self.__class__ is other.__class__:
return self.privilege_level < other.privilege_level
return NotImplemented


class UserStatus(str, Enum):
# This is a transition state. The user is registered but not confirmed. NOTE that state is optional depending on LOGIN_REGISTRATION_CONFIRMATION_REQUIRED
CONFIRMATION_PENDING = "CONFIRMATION_PENDING"
# This user can now operate the platform
ACTIVE = "ACTIVE"
# This user is inactive because it expired after a trial period
EXPIRED = "EXPIRED"
# This user is inactive because he has been a bad boy
BANNED = "BANNED"
# This user is inactive because it was marked for deletion
DELETED = "DELETED"
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
# pylint: disable=unused-variable


import json
import sys
from typing import Any

import pytest
from pytest_simcore.helpers.dict_tools import copy_from_dict, get_from_dict
from pytest_simcore.helpers.typing_docker import TaskDict
from common_library.dict_tools import (
copy_from_dict,
get_from_dict,
remap_keys,
update_dict,
)


@pytest.fixture
def data():
def data() -> dict[str, Any]:
return {
"ID": "3ifd79yhz2vpgu1iz43mf9m2d",
"Version": {"Index": 176},
Expand Down Expand Up @@ -113,7 +116,20 @@ def data():
}


def test_get_from_dict(data: TaskDict):
def test_remap_keys():
assert remap_keys({"a": 1, "b": 2}, rename={"a": "A"}) == {"A": 1, "b": 2}


def test_update_dict():
def _increment(x):
return x + 1

data = {"a": 1, "b": 2, "c": 3}

assert update_dict(data, a=_increment, b=42) == {"a": 2, "b": 42, "c": 3}


def test_get_from_dict(data: dict[str, Any]):

assert get_from_dict(data, "Spec.ContainerSpec.Labels") == {
"com.docker.stack.namespace": "master-simcore"
Expand All @@ -122,7 +138,7 @@ def test_get_from_dict(data: TaskDict):
assert get_from_dict(data, "Invalid.Invalid.Invalid", default=42) == 42


def test_copy_from_dict(data: TaskDict):
def test_copy_from_dict(data: dict[str, Any]):

selected_data = copy_from_dict(
data,
Expand All @@ -136,20 +152,11 @@ def test_copy_from_dict(data: TaskDict):
},
)

print(json.dumps(selected_data, indent=2))

assert selected_data["ID"] == data["ID"]
assert (
selected_data["Spec"]["ContainerSpec"]["Image"]
== data["Spec"]["ContainerSpec"]["Image"]
)
assert selected_data["Status"]["State"] == data["Status"]["State"]
assert "Message" not in selected_data["Status"]["State"]
assert "Message" in data["Status"]["State"]


if __name__ == "__main__":
# NOTE: use in vscode "Run and Debug" -> select 'Python: Current File'
sys.exit(
pytest.main(["-vv", "-s", "--pdb", "--log-cli-level=WARNING", sys.argv[0]])
)
assert "running" in data["Status"]["State"]
79 changes: 79 additions & 0 deletions packages/common-library/tests/test_users_enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# pylint: disable=no-value-for-parameter
# pylint: disable=redefined-outer-name
# pylint: disable=unused-argument
# pylint: disable=unused-variable


from common_library.users_enums import _USER_ROLE_TO_LEVEL, UserRole


def test_user_role_to_level_map_in_sync():
# If fails, then update _USER_ROLE_TO_LEVEL map
assert set(_USER_ROLE_TO_LEVEL.keys()) == set(UserRole.__members__.keys())


def test_user_roles_compares_to_admin():
assert UserRole.ANONYMOUS < UserRole.ADMIN
assert UserRole.GUEST < UserRole.ADMIN
assert UserRole.USER < UserRole.ADMIN
assert UserRole.TESTER < UserRole.ADMIN
assert UserRole.PRODUCT_OWNER < UserRole.ADMIN
assert UserRole.ADMIN == UserRole.ADMIN


def test_user_roles_compares_to_product_owner():
assert UserRole.ANONYMOUS < UserRole.PRODUCT_OWNER
assert UserRole.GUEST < UserRole.PRODUCT_OWNER
assert UserRole.USER < UserRole.PRODUCT_OWNER
assert UserRole.TESTER < UserRole.PRODUCT_OWNER
assert UserRole.PRODUCT_OWNER == UserRole.PRODUCT_OWNER
assert UserRole.ADMIN > UserRole.PRODUCT_OWNER


def test_user_roles_compares_to_tester():
assert UserRole.ANONYMOUS < UserRole.TESTER
assert UserRole.GUEST < UserRole.TESTER
assert UserRole.USER < UserRole.TESTER
assert UserRole.TESTER == UserRole.TESTER
assert UserRole.PRODUCT_OWNER > UserRole.TESTER
assert UserRole.ADMIN > UserRole.TESTER


def test_user_roles_compares_to_user():
assert UserRole.ANONYMOUS < UserRole.USER
assert UserRole.GUEST < UserRole.USER
assert UserRole.USER == UserRole.USER
assert UserRole.TESTER > UserRole.USER
assert UserRole.PRODUCT_OWNER > UserRole.USER
assert UserRole.ADMIN > UserRole.USER


def test_user_roles_compares_to_guest():
assert UserRole.ANONYMOUS < UserRole.GUEST
assert UserRole.GUEST == UserRole.GUEST
assert UserRole.USER > UserRole.GUEST
assert UserRole.TESTER > UserRole.GUEST
assert UserRole.PRODUCT_OWNER > UserRole.GUEST
assert UserRole.ADMIN > UserRole.GUEST


def test_user_roles_compares_to_anonymous():
assert UserRole.ANONYMOUS == UserRole.ANONYMOUS
assert UserRole.GUEST > UserRole.ANONYMOUS
assert UserRole.USER > UserRole.ANONYMOUS
assert UserRole.TESTER > UserRole.ANONYMOUS
assert UserRole.PRODUCT_OWNER > UserRole.ANONYMOUS
assert UserRole.ADMIN > UserRole.ANONYMOUS


def test_user_roles_compares():
# < and >
assert UserRole.TESTER < UserRole.ADMIN
assert UserRole.ADMIN > UserRole.TESTER

# >=, == and <=
assert UserRole.TESTER <= UserRole.ADMIN
assert UserRole.ADMIN >= UserRole.TESTER

assert UserRole.ADMIN <= UserRole.ADMIN
assert UserRole.ADMIN == UserRole.ADMIN
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ class InputSchema(BaseModel):
)


class OutputSchemaWithoutCamelCase(BaseModel):
model_config = ConfigDict(
populate_by_name=True,
extra="ignore",
frozen=True,
)


class OutputSchema(BaseModel):
model_config = ConfigDict(
alias_generator=snake_to_camel,
Expand Down
Loading
Loading