fractal-server 2.4.0a0__py3-none-any.whl → 2.4.0a2__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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/routes/auth/group.py +25 -22
- fractal_server/app/routes/auth/users.py +103 -14
- fractal_server/app/schemas/user.py +9 -0
- fractal_server/app/schemas/user_group.py +8 -0
- fractal_server/app/security/__init__.py +0 -7
- {fractal_server-2.4.0a0.dist-info → fractal_server-2.4.0a2.dist-info}/METADATA +1 -1
- {fractal_server-2.4.0a0.dist-info → fractal_server-2.4.0a2.dist-info}/RECORD +11 -11
- {fractal_server-2.4.0a0.dist-info → fractal_server-2.4.0a2.dist-info}/LICENSE +0 -0
- {fractal_server-2.4.0a0.dist-info → fractal_server-2.4.0a2.dist-info}/WHEEL +0 -0
- {fractal_server-2.4.0a0.dist-info → fractal_server-2.4.0a2.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.4.
|
1
|
+
__VERSION__ = "2.4.0a2"
|
@@ -5,8 +5,10 @@ from fastapi import APIRouter
|
|
5
5
|
from fastapi import Depends
|
6
6
|
from fastapi import HTTPException
|
7
7
|
from fastapi import status
|
8
|
+
from sqlalchemy.exc import IntegrityError
|
8
9
|
from sqlalchemy.ext.asyncio import AsyncSession
|
9
10
|
from sqlmodel import col
|
11
|
+
from sqlmodel import func
|
10
12
|
from sqlmodel import select
|
11
13
|
|
12
14
|
from . import current_active_superuser
|
@@ -18,7 +20,9 @@ from ._aux_auth import _get_single_group_with_user_ids
|
|
18
20
|
from fractal_server.app.models import LinkUserGroup
|
19
21
|
from fractal_server.app.models import UserGroup
|
20
22
|
from fractal_server.app.models import UserOAuth
|
23
|
+
from fractal_server.logger import set_logger
|
21
24
|
|
25
|
+
logger = set_logger(__name__)
|
22
26
|
|
23
27
|
router_group = APIRouter()
|
24
28
|
|
@@ -31,9 +35,6 @@ async def get_list_user_groups(
|
|
31
35
|
user: UserOAuth = Depends(current_active_superuser),
|
32
36
|
db: AsyncSession = Depends(get_async_db),
|
33
37
|
) -> list[UserGroupRead]:
|
34
|
-
"""
|
35
|
-
FIXME docstring
|
36
|
-
"""
|
37
38
|
|
38
39
|
# Get all groups
|
39
40
|
stm_all_groups = select(UserGroup)
|
@@ -46,7 +47,8 @@ async def get_list_user_groups(
|
|
46
47
|
res = await db.execute(stm_all_links)
|
47
48
|
links = res.scalars().all()
|
48
49
|
|
49
|
-
#
|
50
|
+
# TODO: possible optimizations for this construction are listed in
|
51
|
+
# https://github.com/fractal-analytics-platform/fractal-server/issues/1742
|
50
52
|
for ind, group in enumerate(groups):
|
51
53
|
groups[ind] = dict(
|
52
54
|
group.model_dump(),
|
@@ -68,9 +70,6 @@ async def get_single_user_group(
|
|
68
70
|
user: UserOAuth = Depends(current_active_superuser),
|
69
71
|
db: AsyncSession = Depends(get_async_db),
|
70
72
|
) -> UserGroupRead:
|
71
|
-
"""
|
72
|
-
FIXME docstring
|
73
|
-
"""
|
74
73
|
group = await _get_single_group_with_user_ids(group_id=group_id, db=db)
|
75
74
|
return group
|
76
75
|
|
@@ -85,9 +84,6 @@ async def create_single_group(
|
|
85
84
|
user: UserOAuth = Depends(current_active_superuser),
|
86
85
|
db: AsyncSession = Depends(get_async_db),
|
87
86
|
) -> UserGroupRead:
|
88
|
-
"""
|
89
|
-
FIXME docstring
|
90
|
-
"""
|
91
87
|
|
92
88
|
# Check that name is not already in use
|
93
89
|
existing_name_str = select(UserGroup).where(
|
@@ -119,24 +115,21 @@ async def update_single_group(
|
|
119
115
|
user: UserOAuth = Depends(current_active_superuser),
|
120
116
|
db: AsyncSession = Depends(get_async_db),
|
121
117
|
) -> UserGroupRead:
|
122
|
-
"""
|
123
|
-
FIXME docstring
|
124
|
-
"""
|
125
118
|
|
126
119
|
# Check that all required users exist
|
127
120
|
# Note: The reason for introducing `col` is as in
|
128
121
|
# https://sqlmodel.tiangolo.com/tutorial/where/#type-annotations-and-errors,
|
129
|
-
stm = select(
|
122
|
+
stm = select(func.count()).where(
|
130
123
|
col(UserOAuth.id).in_(group_update.new_user_ids)
|
131
124
|
)
|
132
125
|
res = await db.execute(stm)
|
133
|
-
|
134
|
-
if
|
126
|
+
number_matching_users = res.scalar()
|
127
|
+
if number_matching_users != len(group_update.new_user_ids):
|
135
128
|
raise HTTPException(
|
136
129
|
status_code=status.HTTP_404_NOT_FOUND,
|
137
130
|
detail=(
|
138
|
-
f"
|
139
|
-
"
|
131
|
+
f"Not all requested users (IDs {group_update.new_user_ids}) "
|
132
|
+
"exist."
|
140
133
|
),
|
141
134
|
)
|
142
135
|
|
@@ -144,7 +137,20 @@ async def update_single_group(
|
|
144
137
|
for user_id in group_update.new_user_ids:
|
145
138
|
link = LinkUserGroup(user_id=user_id, group_id=group_id)
|
146
139
|
db.add(link)
|
147
|
-
|
140
|
+
try:
|
141
|
+
await db.commit()
|
142
|
+
except IntegrityError as e:
|
143
|
+
error_msg = (
|
144
|
+
f"Cannot link users with IDs {group_update.new_user_ids} "
|
145
|
+
f"to group {group_id}. "
|
146
|
+
"Likely reason: one of these links already exists.\n"
|
147
|
+
f"Original error: {str(e)}"
|
148
|
+
)
|
149
|
+
logger.info(error_msg)
|
150
|
+
raise HTTPException(
|
151
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
152
|
+
detail=error_msg,
|
153
|
+
)
|
148
154
|
|
149
155
|
updated_group = await _get_single_group_with_user_ids(
|
150
156
|
group_id=group_id, db=db
|
@@ -161,9 +167,6 @@ async def delete_single_group(
|
|
161
167
|
user: UserOAuth = Depends(current_active_superuser),
|
162
168
|
db: AsyncSession = Depends(get_async_db),
|
163
169
|
) -> UserGroupRead:
|
164
|
-
"""
|
165
|
-
FIXME docstring
|
166
|
-
"""
|
167
170
|
raise HTTPException(
|
168
171
|
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
169
172
|
detail=(
|
@@ -8,23 +8,32 @@ 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
|
11
12
|
from sqlalchemy.ext.asyncio import AsyncSession
|
13
|
+
from sqlmodel import col
|
14
|
+
from sqlmodel import func
|
12
15
|
from sqlmodel import select
|
13
16
|
|
14
17
|
from . import current_active_superuser
|
15
18
|
from ...db import get_async_db
|
16
19
|
from ...schemas.user import UserRead
|
17
20
|
from ...schemas.user import UserUpdate
|
21
|
+
from ...schemas.user import UserUpdateWithNewGroupIds
|
18
22
|
from ._aux_auth import _get_single_user_with_group_ids
|
19
23
|
from fractal_server.app.models import LinkUserGroup
|
24
|
+
from fractal_server.app.models import UserGroup
|
20
25
|
from fractal_server.app.models import UserOAuth
|
21
26
|
from fractal_server.app.routes.auth._aux_auth import _user_or_404
|
22
27
|
from fractal_server.app.security import get_user_manager
|
23
28
|
from fractal_server.app.security import UserManager
|
29
|
+
from fractal_server.logger import set_logger
|
24
30
|
|
25
31
|
router_users = APIRouter()
|
26
32
|
|
27
33
|
|
34
|
+
logger = set_logger(__name__)
|
35
|
+
|
36
|
+
|
28
37
|
@router_users.get("/users/{user_id}/", response_model=UserRead)
|
29
38
|
async def get_user(
|
30
39
|
user_id: int,
|
@@ -43,31 +52,110 @@ async def get_user(
|
|
43
52
|
@router_users.patch("/users/{user_id}/", response_model=UserRead)
|
44
53
|
async def patch_user(
|
45
54
|
user_id: int,
|
46
|
-
user_update:
|
55
|
+
user_update: UserUpdateWithNewGroupIds,
|
47
56
|
current_superuser: UserOAuth = Depends(current_active_superuser),
|
48
57
|
user_manager: UserManager = Depends(get_user_manager),
|
49
58
|
db: AsyncSession = Depends(get_async_db),
|
50
59
|
):
|
51
60
|
"""
|
52
61
|
Custom version of the PATCH-user route from `fastapi-users`.
|
62
|
+
|
63
|
+
In order to keep the fastapi-users logic in place (which is convenient to
|
64
|
+
update user attributes), we split the endpoint into two branches. We either
|
65
|
+
go through the fastapi-users-based attribute-update branch, or through the
|
66
|
+
branch where we establish new user/group relationships.
|
67
|
+
|
68
|
+
Note that we prevent making both changes at the same time, since it would
|
69
|
+
be more complex to guarantee that endpoint error would leave the database
|
70
|
+
in the same state as before the API call.
|
53
71
|
"""
|
54
72
|
|
73
|
+
# We prevent simultaneous editing of both user attributes and user/group
|
74
|
+
# associations
|
75
|
+
user_update_dict_without_groups = user_update.dict(
|
76
|
+
exclude_unset=True, exclude={"new_group_ids"}
|
77
|
+
)
|
78
|
+
edit_attributes = user_update_dict_without_groups != {}
|
79
|
+
edit_groups = user_update.new_group_ids is not None
|
80
|
+
if edit_attributes and edit_groups:
|
81
|
+
raise HTTPException(
|
82
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
83
|
+
detail=(
|
84
|
+
"Cannot modify both user attributes and group membership. "
|
85
|
+
"Please make two independent PATCH calls"
|
86
|
+
),
|
87
|
+
)
|
88
|
+
|
89
|
+
# Check that user exists
|
55
90
|
user_to_patch = await _user_or_404(user_id, db)
|
56
91
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
detail={
|
66
|
-
"code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
|
67
|
-
"reason": e.reason,
|
68
|
-
},
|
92
|
+
if edit_groups:
|
93
|
+
# Establish new user/group relationships
|
94
|
+
|
95
|
+
# Check that all required groups exist
|
96
|
+
# Note: The reason for introducing `col` is as in
|
97
|
+
# https://sqlmodel.tiangolo.com/tutorial/where/#type-annotations-and-errors,
|
98
|
+
stm = select(func.count()).where(
|
99
|
+
col(UserGroup.id).in_(user_update.new_group_ids)
|
69
100
|
)
|
101
|
+
res = await db.execute(stm)
|
102
|
+
number_matching_groups = res.scalar()
|
103
|
+
if number_matching_groups != len(user_update.new_group_ids):
|
104
|
+
raise HTTPException(
|
105
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
106
|
+
detail=(
|
107
|
+
"Not all requested groups (IDs: "
|
108
|
+
f"{user_update.new_group_ids}) exist."
|
109
|
+
),
|
110
|
+
)
|
111
|
+
|
112
|
+
for new_group_id in user_update.new_group_ids:
|
113
|
+
link = LinkUserGroup(user_id=user_id, group_id=new_group_id)
|
114
|
+
db.add(link)
|
115
|
+
|
116
|
+
try:
|
117
|
+
await db.commit()
|
118
|
+
except IntegrityError as e:
|
119
|
+
error_msg = (
|
120
|
+
f"Cannot link groups with IDs {user_update.new_group_ids} "
|
121
|
+
f"to user {user_id}. "
|
122
|
+
"Likely reason: one of these links already exists.\n"
|
123
|
+
f"Original error: {str(e)}"
|
124
|
+
)
|
125
|
+
logger.info(error_msg)
|
126
|
+
raise HTTPException(
|
127
|
+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
128
|
+
detail=error_msg,
|
129
|
+
)
|
130
|
+
|
131
|
+
patched_user = user_to_patch
|
132
|
+
|
133
|
+
elif edit_attributes:
|
134
|
+
# Modify user attributes
|
135
|
+
try:
|
136
|
+
user_update_without_groups = UserUpdate(
|
137
|
+
**user_update_dict_without_groups
|
138
|
+
)
|
139
|
+
user = await user_manager.update(
|
140
|
+
user_update_without_groups,
|
141
|
+
user_to_patch,
|
142
|
+
safe=False,
|
143
|
+
request=None,
|
144
|
+
)
|
145
|
+
patched_user = schemas.model_validate(UserOAuth, user)
|
146
|
+
except exceptions.InvalidPasswordException as e:
|
147
|
+
raise HTTPException(
|
148
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
149
|
+
detail={
|
150
|
+
"code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
|
151
|
+
"reason": e.reason,
|
152
|
+
},
|
153
|
+
)
|
154
|
+
else:
|
155
|
+
# Nothing to do, just continue
|
156
|
+
patched_user = user_to_patch
|
70
157
|
|
158
|
+
# Enrich user object with `group_ids` attribute
|
71
159
|
patched_user_with_group_ids = await _get_single_user_with_group_ids(
|
72
160
|
patched_user, db
|
73
161
|
)
|
@@ -92,7 +180,8 @@ async def list_users(
|
|
92
180
|
res = await db.execute(stm_all_links)
|
93
181
|
links = res.scalars().all()
|
94
182
|
|
95
|
-
#
|
183
|
+
# TODO: possible optimizations for this construction are listed in
|
184
|
+
# https://github.com/fractal-analytics-platform/fractal-server/issues/1742
|
96
185
|
for ind, user in enumerate(user_list):
|
97
186
|
user_list[ind] = dict(
|
98
187
|
user.model_dump(),
|
@@ -16,6 +16,7 @@ __all__ = (
|
|
16
16
|
"UserRead",
|
17
17
|
"UserUpdate",
|
18
18
|
"UserCreate",
|
19
|
+
"UserUpdateWithNewGroupIds",
|
19
20
|
)
|
20
21
|
|
21
22
|
|
@@ -102,6 +103,14 @@ class UserUpdateStrict(BaseModel, extra=Extra.forbid):
|
|
102
103
|
)
|
103
104
|
|
104
105
|
|
106
|
+
class UserUpdateWithNewGroupIds(UserUpdate):
|
107
|
+
new_group_ids: Optional[list[int]] = None
|
108
|
+
|
109
|
+
_val_unique = validator("new_group_ids", allow_reuse=True)(
|
110
|
+
val_unique_list("new_group_ids")
|
111
|
+
)
|
112
|
+
|
113
|
+
|
105
114
|
class UserCreate(schemas.BaseUserCreate):
|
106
115
|
"""
|
107
116
|
Schema for `User` creation.
|
@@ -4,6 +4,10 @@ from typing import Optional
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
from pydantic import Extra
|
6
6
|
from pydantic import Field
|
7
|
+
from pydantic import validator
|
8
|
+
|
9
|
+
from ._validators import val_unique_list
|
10
|
+
|
7
11
|
|
8
12
|
__all__ = (
|
9
13
|
"UserGroupRead",
|
@@ -55,3 +59,7 @@ class UserGroupUpdate(BaseModel, extra=Extra.forbid):
|
|
55
59
|
"""
|
56
60
|
|
57
61
|
new_user_ids: list[int] = Field(default_factory=list)
|
62
|
+
|
63
|
+
_val_unique = validator("new_user_ids", allow_reuse=True)(
|
64
|
+
val_unique_list("new_user_ids")
|
65
|
+
)
|
@@ -43,7 +43,6 @@ from fastapi_users.exceptions import UserAlreadyExists
|
|
43
43
|
from fastapi_users.models import ID
|
44
44
|
from fastapi_users.models import OAP
|
45
45
|
from fastapi_users.models import UP
|
46
|
-
from sqlalchemy.exc import IntegrityError
|
47
46
|
from sqlalchemy.ext.asyncio import AsyncSession
|
48
47
|
from sqlalchemy.orm import selectinload
|
49
48
|
from sqlmodel import func
|
@@ -295,12 +294,6 @@ async def _create_first_user(
|
|
295
294
|
user = await user_manager.create(UserCreate(**kwargs))
|
296
295
|
logger.info(f"User {user.email} created")
|
297
296
|
|
298
|
-
except IntegrityError:
|
299
|
-
logger.warning(
|
300
|
-
f"Creation of user {email} failed with IntegrityError "
|
301
|
-
"(likely due to concurrent attempts from different workers)."
|
302
|
-
)
|
303
|
-
|
304
297
|
except UserAlreadyExists:
|
305
298
|
logger.warning(f"User {email} already exists")
|
306
299
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
fractal_server/__init__.py,sha256=
|
1
|
+
fractal_server/__init__.py,sha256=LqB8PB8-x2CZLC5OUrNdxwSPLBLIrriGxEQXweZeNSc,24
|
2
2
|
fractal_server/__main__.py,sha256=I9hF_SYc-GTZWDZZhihwyUBK7BMU5GAecbPLTjkpW4U,5830
|
3
3
|
fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
|
4
4
|
fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -53,13 +53,13 @@ fractal_server/app/routes/api/v2/workflowtask.py,sha256=HoHFnVRDa0Cw1oqTea1Of6A5
|
|
53
53
|
fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
|
54
54
|
fractal_server/app/routes/auth/_aux_auth.py,sha256=Kpgiw5q1eiCYLFkfhTT7XJGBu1d08YM71CEHhNtfJ5g,3126
|
55
55
|
fractal_server/app/routes/auth/current_user.py,sha256=LK0Z13NgaXYQ3FaQ3MNec0p2RRiKxKN31XIt2g9mcGk,2003
|
56
|
-
fractal_server/app/routes/auth/group.py,sha256=
|
56
|
+
fractal_server/app/routes/auth/group.py,sha256=az8kRJJU5rA0L8GCX5kvRNuQhrsoGWPuvQ26z7iFQ3E,5388
|
57
57
|
fractal_server/app/routes/auth/group_names.py,sha256=zvYDfhxKlDmbSr-oLXYy6WUVkPPTvzH6ZJtuoNdGZbE,960
|
58
58
|
fractal_server/app/routes/auth/login.py,sha256=tSu6OBLOieoBtMZB4JkBAdEgH2Y8KqPGSbwy7NIypIo,566
|
59
59
|
fractal_server/app/routes/auth/oauth.py,sha256=AnFHbjqL2AgBX3eksI931xD6RTtmbciHBEuGf9YJLjU,1895
|
60
60
|
fractal_server/app/routes/auth/register.py,sha256=DlHq79iOvGd_gt2v9uwtsqIKeO6i_GKaW59VIkllPqY,587
|
61
61
|
fractal_server/app/routes/auth/router.py,sha256=zWoZWiO69U48QFQf5tLRYQDWu8PUCj7GacnaFeW1n_I,618
|
62
|
-
fractal_server/app/routes/auth/users.py,sha256=
|
62
|
+
fractal_server/app/routes/auth/users.py,sha256=hNTIR0e3QFbUUDvkzKNXJt4uKwONlI36i6UjjMPCgAA,6672
|
63
63
|
fractal_server/app/routes/aux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
64
64
|
fractal_server/app/routes/aux/_job.py,sha256=q-RCiW17yXnZKAC_0La52RLvhqhxuvbgQJ2MlGXOj8A,702
|
65
65
|
fractal_server/app/routes/aux/_runner.py,sha256=FdCVla5DxGAZ__aB7Z8dEJzD_RIeh5tftjrPyqkr8N8,895
|
@@ -126,8 +126,8 @@ fractal_server/app/runner/v2/v1_compat.py,sha256=t0ficzAHUFaaeI56nqTb4YEKxfARF7L
|
|
126
126
|
fractal_server/app/runner/versions.py,sha256=dSaPRWqmFPHjg20kTCHmi_dmGNcCETflDtDLronNanU,852
|
127
127
|
fractal_server/app/schemas/__init__.py,sha256=jiIf54owztXupv3PO6Ilh0qcrkh2RUzKq4bcEFqEfc4,40
|
128
128
|
fractal_server/app/schemas/_validators.py,sha256=1dTOYr1IZykrxuQSV2-zuEMZbKe_nGwrfS7iUrsh-sE,3461
|
129
|
-
fractal_server/app/schemas/user.py,sha256=
|
130
|
-
fractal_server/app/schemas/user_group.py,sha256=
|
129
|
+
fractal_server/app/schemas/user.py,sha256=jRMUd3v7kmnOdHhGSoljKbCk6xyaJuGHRXkaxAxI8RA,3437
|
130
|
+
fractal_server/app/schemas/user_group.py,sha256=2f9XQ6kIar6NMY4UCN0yOnve6ZDHUVZaHv1dna1Vfjg,1446
|
131
131
|
fractal_server/app/schemas/v1/__init__.py,sha256=CrBGgBhoemCvmZ70ZUchM-jfVAICnoa7AjZBAtL2UB0,1852
|
132
132
|
fractal_server/app/schemas/v1/applyworkflow.py,sha256=uuIh7fHlHEL4yLqL-dePI6-nfCsqgBYATmht7w_KITw,4302
|
133
133
|
fractal_server/app/schemas/v1/dataset.py,sha256=n71lNUO3JLy2K3IM9BZM2Fk1EnKQOTU7pm2s2rJ1FGY,3444
|
@@ -149,7 +149,7 @@ fractal_server/app/schemas/v2/task.py,sha256=7IfxiZkaVqlARy7WYE_H8m7j_IEcuQaZORU
|
|
149
149
|
fractal_server/app/schemas/v2/task_collection.py,sha256=8PG1bOqkfQqORMN0brWf6mHDmijt0bBW-mZsF7cSxUs,6129
|
150
150
|
fractal_server/app/schemas/v2/workflow.py,sha256=Zzx3e-qgkH8le0FUmAx9UrV5PWd7bj14PPXUh_zgZXM,1827
|
151
151
|
fractal_server/app/schemas/v2/workflowtask.py,sha256=atVuVN4aXsVEOmSd-vyg-8_8OnPmqx-gT75rXcn_AlQ,6552
|
152
|
-
fractal_server/app/security/__init__.py,sha256=
|
152
|
+
fractal_server/app/security/__init__.py,sha256=FBxdrMvn2s3Gdmp1orqOpYji87JojLBzr9TMfblj1SI,11441
|
153
153
|
fractal_server/config.py,sha256=R0VezSe2PEDjQjHEX2V29A1jMdoomdyECBjWNY15v_0,25049
|
154
154
|
fractal_server/data_migrations/2_4_0.py,sha256=T1HRRWp9ZuXeVfBY6NRGxQ8aNIHVSftOMnB-CMrfvi8,2117
|
155
155
|
fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
|
@@ -207,8 +207,8 @@ fractal_server/tasks/v2/utils.py,sha256=JOyCacb6MNvrwfLNTyLwcz8y79J29YuJeJ2MK5kq
|
|
207
207
|
fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
|
208
208
|
fractal_server/utils.py,sha256=b7WwFdcFZ8unyT65mloFToYuEDXpQoHRcmRNqrhd_dQ,2115
|
209
209
|
fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
|
210
|
-
fractal_server-2.4.
|
211
|
-
fractal_server-2.4.
|
212
|
-
fractal_server-2.4.
|
213
|
-
fractal_server-2.4.
|
214
|
-
fractal_server-2.4.
|
210
|
+
fractal_server-2.4.0a2.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
|
211
|
+
fractal_server-2.4.0a2.dist-info/METADATA,sha256=ujcw0RxpRcxYPLLI1NdkZ8v0IaKQP3d3D_CXsaxua4U,4630
|
212
|
+
fractal_server-2.4.0a2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
213
|
+
fractal_server-2.4.0a2.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
|
214
|
+
fractal_server-2.4.0a2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|