fractal-server 2.9.0a9__py3-none-any.whl → 2.9.0a11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- __VERSION__ = "2.9.0a9"
1
+ __VERSION__ = "2.9.0a11"
@@ -7,6 +7,7 @@ from fastapi import status
7
7
  from pydantic import BaseModel
8
8
  from pydantic import EmailStr
9
9
  from pydantic import Field
10
+ from sqlmodel import func
10
11
  from sqlmodel import select
11
12
 
12
13
  from fractal_server.app.db import AsyncSession
@@ -60,6 +61,9 @@ async def query_tasks(
60
61
  version: Optional[str] = None,
61
62
  name: Optional[str] = None,
62
63
  max_number_of_results: int = 25,
64
+ category: Optional[str] = None,
65
+ modality: Optional[str] = None,
66
+ author: Optional[str] = None,
63
67
  user: UserOAuth = Depends(current_active_superuser),
64
68
  db: AsyncSession = Depends(get_async_db),
65
69
  ) -> list[TaskV2Info]:
@@ -74,6 +78,9 @@ async def query_tasks(
74
78
  version: If not `None`, query for matching `task.version`.
75
79
  name: If not `None`, query for contained case insensitive `task.name`.
76
80
  max_number_of_results: The maximum length of the response.
81
+ category:
82
+ modality:
83
+ author:
77
84
  """
78
85
 
79
86
  stm = select(TaskV2)
@@ -86,6 +93,12 @@ async def query_tasks(
86
93
  stm = stm.where(TaskV2.version == version)
87
94
  if name is not None:
88
95
  stm = stm.where(TaskV2.name.icontains(name))
96
+ if category is not None:
97
+ stm = stm.where(func.lower(TaskV2.category) == category.lower())
98
+ if modality is not None:
99
+ stm = stm.where(func.lower(TaskV2.modality) == modality.lower())
100
+ if author is not None:
101
+ stm = stm.where(TaskV2.authors.icontains(author))
89
102
 
90
103
  res = await db.execute(stm)
91
104
  task_list = res.scalars().all()
@@ -6,14 +6,12 @@ from fastapi import Depends
6
6
  from fastapi import HTTPException
7
7
  from fastapi import Response
8
8
  from fastapi import status
9
- from sqlalchemy.exc import IntegrityError
10
9
  from sqlalchemy.ext.asyncio import AsyncSession
11
- from sqlmodel import col
12
- from sqlmodel import func
13
10
  from sqlmodel import select
14
11
 
15
12
  from . import current_active_superuser
16
13
  from ._aux_auth import _get_single_usergroup_with_user_ids
14
+ from ._aux_auth import _user_or_404
17
15
  from ._aux_auth import _usergroup_or_404
18
16
  from fractal_server.app.db import get_async_db
19
17
  from fractal_server.app.models import LinkUserGroup
@@ -126,42 +124,6 @@ async def update_single_group(
126
124
 
127
125
  group = await _usergroup_or_404(group_id, db)
128
126
 
129
- # Check that all required users exist
130
- # Note: The reason for introducing `col` is as in
131
- # https://sqlmodel.tiangolo.com/tutorial/where/#type-annotations-and-errors,
132
- stm = select(func.count()).where(
133
- col(UserOAuth.id).in_(group_update.new_user_ids)
134
- )
135
- res = await db.execute(stm)
136
- number_matching_users = res.scalar()
137
- if number_matching_users != len(group_update.new_user_ids):
138
- raise HTTPException(
139
- status_code=status.HTTP_404_NOT_FOUND,
140
- detail=(
141
- f"Not all requested users (IDs {group_update.new_user_ids}) "
142
- "exist."
143
- ),
144
- )
145
-
146
- # Add new users to existing group
147
- for user_id in group_update.new_user_ids:
148
- link = LinkUserGroup(user_id=user_id, group_id=group_id)
149
- db.add(link)
150
- try:
151
- await db.commit()
152
- except IntegrityError as e:
153
- error_msg = (
154
- f"Cannot link users with IDs {group_update.new_user_ids} "
155
- f"to group {group_id}. "
156
- "Likely reason: one of these links already exists.\n"
157
- f"Original error: {str(e)}"
158
- )
159
- logger.info(error_msg)
160
- raise HTTPException(
161
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
162
- detail=error_msg,
163
- )
164
-
165
127
  # Patch `viewer_paths`
166
128
  if group_update.viewer_paths is not None:
167
129
  group.viewer_paths = group_update.viewer_paths
@@ -239,3 +201,49 @@ async def patch_user_settings_bulk(
239
201
  await db.commit()
240
202
 
241
203
  return Response(status_code=status.HTTP_200_OK)
204
+
205
+
206
+ @router_group.post("/group/{group_id}/add-user/{user_id}/", status_code=200)
207
+ async def add_user_to_group(
208
+ group_id: int,
209
+ user_id: int,
210
+ superuser: UserOAuth = Depends(current_active_superuser),
211
+ db: AsyncSession = Depends(get_async_db),
212
+ ) -> UserGroupRead:
213
+ await _usergroup_or_404(group_id, db)
214
+ user = await _user_or_404(user_id, db)
215
+ link = await db.get(LinkUserGroup, (group_id, user_id))
216
+ if link is None:
217
+ db.add(LinkUserGroup(group_id=group_id, user_id=user_id))
218
+ await db.commit()
219
+ else:
220
+ raise HTTPException(
221
+ status_code=422,
222
+ detail=(
223
+ f"User '{user.email}' is already a member of group {group_id}."
224
+ ),
225
+ )
226
+ group = await _get_single_usergroup_with_user_ids(group_id=group_id, db=db)
227
+ return group
228
+
229
+
230
+ @router_group.post("/group/{group_id}/remove-user/{user_id}/", status_code=200)
231
+ async def remove_user_from_group(
232
+ group_id: int,
233
+ user_id: int,
234
+ superuser: UserOAuth = Depends(current_active_superuser),
235
+ db: AsyncSession = Depends(get_async_db),
236
+ ) -> UserGroupRead:
237
+ await _usergroup_or_404(group_id, db)
238
+ user = await _user_or_404(user_id, db)
239
+ link = await db.get(LinkUserGroup, (group_id, user_id))
240
+ if link is None:
241
+ raise HTTPException(
242
+ status_code=422,
243
+ detail=f"User '{user.email}' is not a member of group {group_id}.",
244
+ )
245
+ else:
246
+ await db.delete(link)
247
+ await db.commit()
248
+ group = await _get_single_usergroup_with_user_ids(group_id=group_id, db=db)
249
+ return group
@@ -8,9 +8,7 @@ from fastapi import status
8
8
  from fastapi_users import exceptions
9
9
  from fastapi_users import schemas
10
10
  from fastapi_users.router.common import ErrorCode
11
- from sqlalchemy.exc import IntegrityError
12
11
  from sqlalchemy.ext.asyncio import AsyncSession
13
- from sqlmodel import col
14
12
  from sqlmodel import func
15
13
  from sqlmodel import select
16
14
 
@@ -18,9 +16,10 @@ from . import current_active_superuser
18
16
  from ...db import get_async_db
19
17
  from ...schemas.user import UserRead
20
18
  from ...schemas.user import UserUpdate
21
- from ...schemas.user import UserUpdateWithNewGroupIds
22
19
  from ..aux.validate_user_settings import verify_user_has_settings
20
+ from ._aux_auth import _get_default_usergroup_id
23
21
  from ._aux_auth import _get_single_user_with_groups
22
+ from ._aux_auth import FRACTAL_DEFAULT_GROUP_NAME
24
23
  from fractal_server.app.models import LinkUserGroup
25
24
  from fractal_server.app.models import UserGroup
26
25
  from fractal_server.app.models import UserOAuth
@@ -28,6 +27,7 @@ from fractal_server.app.models import UserSettings
28
27
  from fractal_server.app.routes.auth._aux_auth import _user_or_404
29
28
  from fractal_server.app.schemas import UserSettingsRead
30
29
  from fractal_server.app.schemas import UserSettingsUpdate
30
+ from fractal_server.app.schemas.user import UserUpdateGroups
31
31
  from fractal_server.app.security import get_user_manager
32
32
  from fractal_server.app.security import UserManager
33
33
  from fractal_server.logger import set_logger
@@ -55,114 +55,43 @@ async def get_user(
55
55
  @router_users.patch("/users/{user_id}/", response_model=UserRead)
56
56
  async def patch_user(
57
57
  user_id: int,
58
- user_update: UserUpdateWithNewGroupIds,
58
+ user_update: UserUpdate,
59
59
  current_superuser: UserOAuth = Depends(current_active_superuser),
60
60
  user_manager: UserManager = Depends(get_user_manager),
61
61
  db: AsyncSession = Depends(get_async_db),
62
62
  ):
63
63
  """
64
64
  Custom version of the PATCH-user route from `fastapi-users`.
65
-
66
- In order to keep the fastapi-users logic in place (which is convenient to
67
- update user attributes), we split the endpoint into two branches. We either
68
- go through the fastapi-users-based attribute-update branch, or through the
69
- branch where we establish new user/group relationships.
70
-
71
- Note that we prevent making both changes at the same time, since it would
72
- be more complex to guarantee that endpoint error would leave the database
73
- in the same state as before the API call.
74
65
  """
75
66
 
76
- # We prevent simultaneous editing of both user attributes and user/group
77
- # associations
78
- user_update_dict_without_groups = user_update.dict(
79
- exclude_unset=True, exclude={"new_group_ids"}
80
- )
81
- edit_attributes = user_update_dict_without_groups != {}
82
- edit_groups = user_update.new_group_ids is not None
83
- if edit_attributes and edit_groups:
84
- raise HTTPException(
85
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
86
- detail=(
87
- "Cannot modify both user attributes and group membership. "
88
- "Please make two independent PATCH calls"
89
- ),
90
- )
91
-
92
67
  # Check that user exists
93
68
  user_to_patch = await _user_or_404(user_id, db)
94
69
 
95
- if edit_groups:
96
- # Establish new user/group relationships
97
-
98
- # Check that all required groups exist
99
- # Note: The reason for introducing `col` is as in
100
- # https://sqlmodel.tiangolo.com/tutorial/where/#type-annotations-and-errors,
101
- stm = select(func.count()).where(
102
- col(UserGroup.id).in_(user_update.new_group_ids)
70
+ # Modify user attributes
71
+ try:
72
+ user = await user_manager.update(
73
+ user_update,
74
+ user_to_patch,
75
+ safe=False,
76
+ request=None,
77
+ )
78
+ validated_user = schemas.model_validate(UserOAuth, user)
79
+ patched_user = await db.get(
80
+ UserOAuth, validated_user.id, populate_existing=True
81
+ )
82
+ except exceptions.InvalidPasswordException as e:
83
+ raise HTTPException(
84
+ status_code=status.HTTP_400_BAD_REQUEST,
85
+ detail={
86
+ "code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
87
+ "reason": e.reason,
88
+ },
89
+ )
90
+ except exceptions.UserAlreadyExists:
91
+ raise HTTPException(
92
+ status.HTTP_400_BAD_REQUEST,
93
+ detail=ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS,
103
94
  )
104
- res = await db.execute(stm)
105
- number_matching_groups = res.scalar()
106
- if number_matching_groups != len(user_update.new_group_ids):
107
- raise HTTPException(
108
- status_code=status.HTTP_404_NOT_FOUND,
109
- detail=(
110
- "Not all requested groups (IDs: "
111
- f"{user_update.new_group_ids}) exist."
112
- ),
113
- )
114
-
115
- for new_group_id in user_update.new_group_ids:
116
- link = LinkUserGroup(user_id=user_id, group_id=new_group_id)
117
- db.add(link)
118
-
119
- try:
120
- await db.commit()
121
- except IntegrityError as e:
122
- error_msg = (
123
- f"Cannot link groups with IDs {user_update.new_group_ids} "
124
- f"to user {user_id}. "
125
- "Likely reason: one of these links already exists.\n"
126
- f"Original error: {str(e)}"
127
- )
128
- logger.info(error_msg)
129
- raise HTTPException(
130
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
131
- detail=error_msg,
132
- )
133
- patched_user = user_to_patch
134
- elif edit_attributes:
135
- # Modify user attributes
136
- try:
137
- user_update_without_groups = UserUpdate(
138
- **user_update_dict_without_groups
139
- )
140
- user = await user_manager.update(
141
- user_update_without_groups,
142
- user_to_patch,
143
- safe=False,
144
- request=None,
145
- )
146
- validated_user = schemas.model_validate(UserOAuth, user)
147
- patched_user = await db.get(
148
- UserOAuth, validated_user.id, populate_existing=True
149
- )
150
- except exceptions.InvalidPasswordException as e:
151
- raise HTTPException(
152
- status_code=status.HTTP_400_BAD_REQUEST,
153
- detail={
154
- "code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
155
- "reason": e.reason,
156
- },
157
- )
158
- except exceptions.UserAlreadyExists:
159
- raise HTTPException(
160
- status.HTTP_400_BAD_REQUEST,
161
- detail=ErrorCode.UPDATE_USER_EMAIL_ALREADY_EXISTS,
162
- )
163
- else:
164
- # Nothing to do, just continue
165
- patched_user = user_to_patch
166
95
 
167
96
  # Enrich user object with `group_ids_names` attribute
168
97
  patched_user_with_groups = await _get_single_user_with_groups(
@@ -203,6 +132,75 @@ async def list_users(
203
132
  return user_list
204
133
 
205
134
 
135
+ @router_users.post("/users/{user_id}/set-groups/", response_model=UserRead)
136
+ async def set_user_groups(
137
+ user_id: int,
138
+ user_update: UserUpdateGroups,
139
+ superuser: UserOAuth = Depends(current_active_superuser),
140
+ db: AsyncSession = Depends(get_async_db),
141
+ ) -> UserRead:
142
+
143
+ # Preliminary check that all objects exist in the db
144
+ user = await _user_or_404(user_id=user_id, db=db)
145
+ target_group_ids = user_update.group_ids
146
+ stm = select(func.count(UserGroup.id)).where(
147
+ UserGroup.id.in_(target_group_ids)
148
+ )
149
+ res = await db.execute(stm)
150
+ count = res.scalar()
151
+ if count != len(target_group_ids):
152
+ raise HTTPException(
153
+ status_code=status.HTTP_404_NOT_FOUND,
154
+ detail=f"Some UserGroups in {target_group_ids} do not exist.",
155
+ )
156
+
157
+ # Check that default group is not being removed
158
+ default_group_id = await _get_default_usergroup_id(db=db)
159
+ if default_group_id not in target_group_ids:
160
+ raise HTTPException(
161
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
162
+ detail=(
163
+ f"Cannot remove user from "
164
+ f"'{FRACTAL_DEFAULT_GROUP_NAME}' group.",
165
+ ),
166
+ )
167
+
168
+ # Prepare lists of links to be removed
169
+ res = await db.execute(
170
+ select(LinkUserGroup)
171
+ .where(LinkUserGroup.user_id == user_id)
172
+ .where(LinkUserGroup.group_id.not_in(target_group_ids))
173
+ )
174
+ links_to_remove = res.scalars().all()
175
+
176
+ # Prepare lists of links to be added
177
+ res = await db.execute(
178
+ select(LinkUserGroup.group_id)
179
+ .where(LinkUserGroup.user_id == user_id)
180
+ .where(LinkUserGroup.group_id.in_(target_group_ids))
181
+ )
182
+ ids_links_already_in = res.scalars().all()
183
+ ids_links_to_add = set(target_group_ids) - set(ids_links_already_in)
184
+
185
+ # Remove/create links as needed
186
+ for link in links_to_remove:
187
+ logger.info(
188
+ f"Removing LinkUserGroup with {link.user_id=} "
189
+ f"and {link.group_id=}."
190
+ )
191
+ await db.delete(link)
192
+ for group_id in ids_links_to_add:
193
+ logger.info(
194
+ f"Creating new LinkUserGroup with {user_id=} " f"and {group_id=}."
195
+ )
196
+ db.add(LinkUserGroup(user_id=user_id, group_id=group_id))
197
+ await db.commit()
198
+
199
+ user_with_groups = await _get_single_user_with_groups(user, db)
200
+
201
+ return user_with_groups
202
+
203
+
206
204
  @router_users.get(
207
205
  "/users/{user_id}/settings/", response_model=UserSettingsRead
208
206
  )
@@ -3,6 +3,7 @@ from typing import Optional
3
3
  from fastapi_users import schemas
4
4
  from pydantic import BaseModel
5
5
  from pydantic import Extra
6
+ from pydantic import Field
6
7
  from pydantic import validator
7
8
 
8
9
  from ._validators import val_unique_list
@@ -11,8 +12,8 @@ from ._validators import valstr
11
12
  __all__ = (
12
13
  "UserRead",
13
14
  "UserUpdate",
15
+ "UserUpdateGroups",
14
16
  "UserCreate",
15
- "UserUpdateWithNewGroupIds",
16
17
  )
17
18
 
18
19
 
@@ -45,7 +46,7 @@ class UserRead(schemas.BaseUser[int]):
45
46
  oauth_accounts: list[OAuthAccountRead]
46
47
 
47
48
 
48
- class UserUpdate(schemas.BaseUserUpdate):
49
+ class UserUpdate(schemas.BaseUserUpdate, extra=Extra.forbid):
49
50
  """
50
51
  Schema for `User` update.
51
52
 
@@ -82,14 +83,6 @@ class UserUpdateStrict(BaseModel, extra=Extra.forbid):
82
83
  pass
83
84
 
84
85
 
85
- class UserUpdateWithNewGroupIds(UserUpdate):
86
- new_group_ids: Optional[list[int]] = None
87
-
88
- _val_unique = validator("new_group_ids", allow_reuse=True)(
89
- val_unique_list("new_group_ids")
90
- )
91
-
92
-
93
86
  class UserCreate(schemas.BaseUserCreate):
94
87
  """
95
88
  Schema for `User` creation.
@@ -103,3 +96,16 @@ class UserCreate(schemas.BaseUserCreate):
103
96
  # Validators
104
97
 
105
98
  _username = validator("username", allow_reuse=True)(valstr("username"))
99
+
100
+
101
+ class UserUpdateGroups(BaseModel, extra=Extra.forbid):
102
+ """
103
+ Schema for `POST /auth/users/{user_id}/set-groups/`
104
+
105
+ """
106
+
107
+ group_ids: list[int] = Field(min_items=1)
108
+
109
+ _group_ids = validator("group_ids", allow_reuse=True)(
110
+ val_unique_list("group_ids")
111
+ )
@@ -59,21 +59,10 @@ class UserGroupCreate(BaseModel, extra=Extra.forbid):
59
59
  class UserGroupUpdate(BaseModel, extra=Extra.forbid):
60
60
  """
61
61
  Schema for `UserGroup` update
62
-
63
- NOTE: `new_user_ids` does not correspond to a column of the `UserGroup`
64
- table, but it is rather used to create new `LinkUserGroup` rows.
65
-
66
- Attributes:
67
- new_user_ids: IDs of groups to be associated to user.
68
62
  """
69
63
 
70
- new_user_ids: list[int] = Field(default_factory=list)
71
64
  viewer_paths: Optional[list[str]] = None
72
65
 
73
- _val_unique = validator("new_user_ids", allow_reuse=True)(
74
- val_unique_list("new_user_ids")
75
- )
76
-
77
66
  @validator("viewer_paths")
78
67
  def viewer_paths_validator(cls, value):
79
68
  for i, path in enumerate(value):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.9.0a9
3
+ Version: 2.9.0a11
4
4
  Summary: Server component of the Fractal analytics platform
5
5
  Home-page: https://github.com/fractal-analytics-platform/fractal-server
6
6
  License: BSD-3-Clause
@@ -1,4 +1,4 @@
1
- fractal_server/__init__.py,sha256=AgkJcJmAplHlQOvkvgp6iM_-dHeN1ajiKqWDu3YtZwc,24
1
+ fractal_server/__init__.py,sha256=awxUTr6llNkjH1-5c-_yED0mpNs4APblXMX5R7fA0Qs,25
2
2
  fractal_server/__main__.py,sha256=dEkCfzLLQrIlxsGC-HBfoR-RBMWnJDgNrxYTyzmE9c0,6146
3
3
  fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
4
4
  fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -29,7 +29,7 @@ fractal_server/app/routes/admin/v1.py,sha256=ggJZMeKhRijfVe2h2VzfIcpR15FqkKImANh
29
29
  fractal_server/app/routes/admin/v2/__init__.py,sha256=KYrw0COmmMuIMp7c6YcYRXah4tEYplCWeROnPK1VTeg,681
30
30
  fractal_server/app/routes/admin/v2/job.py,sha256=cbkFIRIIXaWmNsUFI7RAu8HpQ0mWn_bgoxtvWZxr-IA,7624
31
31
  fractal_server/app/routes/admin/v2/project.py,sha256=luy-yiGX1JYTdPm1hpIdDUUqPm8xHuipLy9k2X6zu74,1223
32
- fractal_server/app/routes/admin/v2/task.py,sha256=Y0eujBgGhVapNXfW9azDxw4EBzLmEmCdh70y1RNQcb0,3895
32
+ fractal_server/app/routes/admin/v2/task.py,sha256=gShC2EAOYa0qTB69EXTDXz5Y375QoarOLv9T9vfntAE,4368
33
33
  fractal_server/app/routes/admin/v2/task_group.py,sha256=DncrOAB4q-v3BAmxg35m4EohleriW_FLGE5gpW_Or08,8120
34
34
  fractal_server/app/routes/admin/v2/task_group_lifecycle.py,sha256=0e0ZJ_k75TVHaT2o8Xk33DPDSgh-eBhZf-y4y7t-Adg,9429
35
35
  fractal_server/app/routes/api/__init__.py,sha256=2IDheFi0OFdsUg7nbUiyahqybvpgXqeHUXIL2QtWrQQ,641
@@ -63,12 +63,12 @@ fractal_server/app/routes/api/v2/workflowtask.py,sha256=ciHTwXXFiFnMF7ZpJ3Xs0q6Y
63
63
  fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
64
64
  fractal_server/app/routes/auth/_aux_auth.py,sha256=ifkNocTYatBSMYGwiR14qohmvR9SfMldceiEj6uJBrU,4783
65
65
  fractal_server/app/routes/auth/current_user.py,sha256=I3aVY5etWAJ_SH6t65Mj5TjvB2X8sAGuu1KG7FxLyPU,5883
66
- fractal_server/app/routes/auth/group.py,sha256=dSS7r8J2cejZ6sKnOWAPSDKynxD9VyBNtqDbFpySzIU,7489
66
+ fractal_server/app/routes/auth/group.py,sha256=cS9I6pCIWGbOWc3gUBYmQq6yjFYzm6rVQDukWF_9L90,7721
67
67
  fractal_server/app/routes/auth/login.py,sha256=tSu6OBLOieoBtMZB4JkBAdEgH2Y8KqPGSbwy7NIypIo,566
68
68
  fractal_server/app/routes/auth/oauth.py,sha256=AnFHbjqL2AgBX3eksI931xD6RTtmbciHBEuGf9YJLjU,1895
69
69
  fractal_server/app/routes/auth/register.py,sha256=DlHq79iOvGd_gt2v9uwtsqIKeO6i_GKaW59VIkllPqY,587
70
70
  fractal_server/app/routes/auth/router.py,sha256=tzJrygXFZlmV_uWelVqTOJMEH-3Fr7ydwlgx1LxRjxY,527
71
- fractal_server/app/routes/auth/users.py,sha256=FzKNoB-wD32AkVOj1Vi29lGGyOl8NSMCRL9tEhxqpJk,8403
71
+ fractal_server/app/routes/auth/users.py,sha256=kZv-Ls224WBFiuvVeM584LhYq_BLz6HQ9HpWbWQxRRM,7808
72
72
  fractal_server/app/routes/aux/__init__.py,sha256=LR4bR7RunHAK6jc9IR2bReQd-BdXADdnDccXI4uGeGY,731
73
73
  fractal_server/app/routes/aux/_job.py,sha256=q-RCiW17yXnZKAC_0La52RLvhqhxuvbgQJ2MlGXOj8A,702
74
74
  fractal_server/app/routes/aux/_runner.py,sha256=FdCVla5DxGAZ__aB7Z8dEJzD_RIeh5tftjrPyqkr8N8,895
@@ -135,8 +135,8 @@ fractal_server/app/runner/v2/task_interface.py,sha256=hT3p-bRGsLNAR_dNv_PYFoqzIF
135
135
  fractal_server/app/runner/versions.py,sha256=dSaPRWqmFPHjg20kTCHmi_dmGNcCETflDtDLronNanU,852
136
136
  fractal_server/app/schemas/__init__.py,sha256=stURAU_t3AOBaH0HSUbV-GKhlPKngnnIMoqWc3orFyI,135
137
137
  fractal_server/app/schemas/_validators.py,sha256=T5EswIJAJRvawfzqWtPcN2INAfiBXyE4m0iwQm4ht-0,3149
138
- fractal_server/app/schemas/user.py,sha256=aUD8YAcfYTEO06TEUoTx4heVrXFiX7E2Mb8D2--4FsA,2130
139
- fractal_server/app/schemas/user_group.py,sha256=YwJvYgj-PI66LWy38CEd_FIZPsBV1_2N5zJPGFcFvBw,2143
138
+ fractal_server/app/schemas/user.py,sha256=icjox9gK_invW44Nh_L4CvqfRa92qghyQhmevyg09nQ,2243
139
+ fractal_server/app/schemas/user_group.py,sha256=t30Kd07PY43G_AqFDb8vjdInTeLeU9WvFZDx8fVLPSI,1750
140
140
  fractal_server/app/schemas/user_settings.py,sha256=TalISeEfCrtN8LgqbLx1Q8ZPoeiZnbksg5NYAVzkIqY,3527
141
141
  fractal_server/app/schemas/v1/__init__.py,sha256=CrBGgBhoemCvmZ70ZUchM-jfVAICnoa7AjZBAtL2UB0,1852
142
142
  fractal_server/app/schemas/v1/applyworkflow.py,sha256=dYArxQAOBdUIEXX_Ejz8b9fBhEYu1nMm6b_Z6_P6TgA,4052
@@ -238,8 +238,8 @@ fractal_server/tasks/v2/utils_templates.py,sha256=C5WLuY3uGG2s53OEL-__H35-fmSlgu
238
238
  fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
239
239
  fractal_server/utils.py,sha256=utvmBx8K9I8hRWFquxna2pBaOqe0JifDL_NVPmihEJI,3525
240
240
  fractal_server/zip_tools.py,sha256=GjDgo_sf6V_DDg6wWeBlZu5zypIxycn_l257p_YVKGc,4876
241
- fractal_server-2.9.0a9.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
242
- fractal_server-2.9.0a9.dist-info/METADATA,sha256=kVjqPsTd7RPiIlTSbj8CzDM6dZEKzvo23ofH6ylpl2E,4585
243
- fractal_server-2.9.0a9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
244
- fractal_server-2.9.0a9.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
245
- fractal_server-2.9.0a9.dist-info/RECORD,,
241
+ fractal_server-2.9.0a11.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
242
+ fractal_server-2.9.0a11.dist-info/METADATA,sha256=0SUdcGO7gPL9_VuNTLt10VitKNIgZTgQ5VL4FV2xjX8,4586
243
+ fractal_server-2.9.0a11.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
244
+ fractal_server-2.9.0a11.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
245
+ fractal_server-2.9.0a11.dist-info/RECORD,,