fractal-server 2.14.12__py3-none-any.whl → 2.14.13__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/api/v2/_aux_task_group_disambiguation.py +163 -0
- fractal_server/app/routes/api/v2/task_group.py +52 -4
- fractal_server/app/routes/api/v2/workflow_import.py +3 -70
- fractal_server/exceptions.py +2 -0
- {fractal_server-2.14.12.dist-info → fractal_server-2.14.13.dist-info}/METADATA +1 -1
- {fractal_server-2.14.12.dist-info → fractal_server-2.14.13.dist-info}/RECORD +10 -8
- {fractal_server-2.14.12.dist-info → fractal_server-2.14.13.dist-info}/LICENSE +0 -0
- {fractal_server-2.14.12.dist-info → fractal_server-2.14.13.dist-info}/WHEEL +0 -0
- {fractal_server-2.14.12.dist-info → fractal_server-2.14.13.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.14.
|
1
|
+
__VERSION__ = "2.14.13"
|
@@ -0,0 +1,163 @@
|
|
1
|
+
import itertools
|
2
|
+
|
3
|
+
from sqlmodel import select
|
4
|
+
|
5
|
+
from fractal_server.app.db import AsyncSession
|
6
|
+
from fractal_server.app.models import LinkUserGroup
|
7
|
+
from fractal_server.app.models.v2 import TaskGroupV2
|
8
|
+
from fractal_server.exceptions import UnreachableBranchError
|
9
|
+
from fractal_server.logger import set_logger
|
10
|
+
|
11
|
+
|
12
|
+
logger = set_logger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
async def _disambiguate_task_groups(
|
16
|
+
*,
|
17
|
+
matching_task_groups: list[TaskGroupV2],
|
18
|
+
user_id: int,
|
19
|
+
default_group_id: int,
|
20
|
+
db: AsyncSession,
|
21
|
+
) -> TaskGroupV2 | None:
|
22
|
+
"""
|
23
|
+
Find ownership-based top-priority task group, if any.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
matching_task_groups:
|
27
|
+
user_id:
|
28
|
+
default_group_id:
|
29
|
+
db:
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
The task group or `None`.
|
33
|
+
"""
|
34
|
+
|
35
|
+
# Highest priority: task groups created by user
|
36
|
+
list_user_ids = [tg.user_id for tg in matching_task_groups]
|
37
|
+
try:
|
38
|
+
ind_user_id = list_user_ids.index(user_id)
|
39
|
+
task_group = matching_task_groups[ind_user_id]
|
40
|
+
logger.debug(
|
41
|
+
"[_disambiguate_task_groups] "
|
42
|
+
f"Found task group {task_group.id} with {user_id=}, return."
|
43
|
+
)
|
44
|
+
return task_group
|
45
|
+
except ValueError:
|
46
|
+
logger.debug(
|
47
|
+
"[_disambiguate_task_groups] "
|
48
|
+
f"No task group with {user_id=}, continue."
|
49
|
+
)
|
50
|
+
|
51
|
+
# Medium priority: task groups owned by default user group
|
52
|
+
list_user_group_ids = [tg.user_group_id for tg in matching_task_groups]
|
53
|
+
try:
|
54
|
+
ind_user_group_id = list_user_group_ids.index(default_group_id)
|
55
|
+
task_group = matching_task_groups[ind_user_group_id]
|
56
|
+
logger.debug(
|
57
|
+
"[_disambiguate_task_groups] "
|
58
|
+
f"Found task group {task_group.id} with {user_id=}, return."
|
59
|
+
)
|
60
|
+
return task_group
|
61
|
+
except ValueError:
|
62
|
+
logger.debug(
|
63
|
+
"[_disambiguate_task_groups] "
|
64
|
+
"No task group with user_group_id="
|
65
|
+
f"{default_group_id}, continue."
|
66
|
+
)
|
67
|
+
|
68
|
+
# Lowest priority: task groups owned by other groups, sorted
|
69
|
+
# according to age of the user/usergroup link
|
70
|
+
logger.debug(
|
71
|
+
"[_disambiguate_task_groups] "
|
72
|
+
"Sort remaining task groups by oldest-user-link."
|
73
|
+
)
|
74
|
+
stm = (
|
75
|
+
select(LinkUserGroup.group_id)
|
76
|
+
.where(LinkUserGroup.user_id == user_id)
|
77
|
+
.where(LinkUserGroup.group_id.in_(list_user_group_ids))
|
78
|
+
.order_by(LinkUserGroup.timestamp_created.asc())
|
79
|
+
)
|
80
|
+
res = await db.execute(stm)
|
81
|
+
oldest_user_group_id = res.scalars().first()
|
82
|
+
logger.debug(
|
83
|
+
"[_disambiguate_task_groups] " f"Result: {oldest_user_group_id=}."
|
84
|
+
)
|
85
|
+
task_group = next(
|
86
|
+
iter(
|
87
|
+
task_group
|
88
|
+
for task_group in matching_task_groups
|
89
|
+
if task_group.user_group_id == oldest_user_group_id
|
90
|
+
),
|
91
|
+
None,
|
92
|
+
)
|
93
|
+
return task_group
|
94
|
+
|
95
|
+
|
96
|
+
async def _disambiguate_task_groups_not_none(
|
97
|
+
*,
|
98
|
+
matching_task_groups: list[TaskGroupV2],
|
99
|
+
user_id: int,
|
100
|
+
default_group_id: int,
|
101
|
+
db: AsyncSession,
|
102
|
+
) -> TaskGroupV2:
|
103
|
+
"""
|
104
|
+
Find ownership-based top-priority task group, and fail otherwise.
|
105
|
+
|
106
|
+
Args:
|
107
|
+
matching_task_groups:
|
108
|
+
user_id:
|
109
|
+
default_group_id:
|
110
|
+
db:
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
The top-priority task group.
|
114
|
+
"""
|
115
|
+
task_group = await _disambiguate_task_groups(
|
116
|
+
matching_task_groups=matching_task_groups,
|
117
|
+
user_id=user_id,
|
118
|
+
default_group_id=default_group_id,
|
119
|
+
db=db,
|
120
|
+
)
|
121
|
+
if task_group is None:
|
122
|
+
error_msg = (
|
123
|
+
"[_disambiguate_task_groups_not_none] Could not find a task "
|
124
|
+
f"group ({user_id=}, {default_group_id=})."
|
125
|
+
)
|
126
|
+
logger.error(f"UnreachableBranchError {error_msg}")
|
127
|
+
raise UnreachableBranchError(error_msg)
|
128
|
+
else:
|
129
|
+
return task_group
|
130
|
+
|
131
|
+
|
132
|
+
async def remove_duplicate_task_groups(
|
133
|
+
*,
|
134
|
+
task_groups: list[TaskGroupV2],
|
135
|
+
user_id: int,
|
136
|
+
default_group_id: int,
|
137
|
+
db: AsyncSession,
|
138
|
+
) -> list[TaskGroupV2]:
|
139
|
+
"""
|
140
|
+
Extract a single task group for each `version`.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
task_groups: A list of task groups with identical `pkg_name`
|
144
|
+
user_id: User ID
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
New list of task groups with no duplicate `(pkg_name,version)` entries
|
148
|
+
"""
|
149
|
+
|
150
|
+
new_task_groups = [
|
151
|
+
(
|
152
|
+
await _disambiguate_task_groups_not_none(
|
153
|
+
matching_task_groups=list(groups),
|
154
|
+
user_id=user_id,
|
155
|
+
default_group_id=default_group_id,
|
156
|
+
db=db,
|
157
|
+
)
|
158
|
+
)
|
159
|
+
for version, groups in itertools.groupby(
|
160
|
+
task_groups, key=lambda tg: tg.version
|
161
|
+
)
|
162
|
+
]
|
163
|
+
return new_task_groups
|
@@ -1,8 +1,13 @@
|
|
1
|
+
import itertools
|
2
|
+
|
1
3
|
from fastapi import APIRouter
|
2
4
|
from fastapi import Depends
|
3
5
|
from fastapi import HTTPException
|
4
6
|
from fastapi import Response
|
5
7
|
from fastapi import status
|
8
|
+
from packaging.version import InvalidVersion
|
9
|
+
from packaging.version import parse
|
10
|
+
from packaging.version import Version
|
6
11
|
from pydantic.types import AwareDatetime
|
7
12
|
from sqlmodel import or_
|
8
13
|
from sqlmodel import select
|
@@ -10,6 +15,7 @@ from sqlmodel import select
|
|
10
15
|
from ._aux_functions_tasks import _get_task_group_full_access
|
11
16
|
from ._aux_functions_tasks import _get_task_group_read_access
|
12
17
|
from ._aux_functions_tasks import _verify_non_duplication_group_constraint
|
18
|
+
from ._aux_task_group_disambiguation import remove_duplicate_task_groups
|
13
19
|
from fractal_server.app.db import AsyncSession
|
14
20
|
from fractal_server.app.db import get_async_db
|
15
21
|
from fractal_server.app.models import LinkUserGroup
|
@@ -18,6 +24,7 @@ from fractal_server.app.models.v2 import TaskGroupActivityV2
|
|
18
24
|
from fractal_server.app.models.v2 import TaskGroupV2
|
19
25
|
from fractal_server.app.models.v2 import WorkflowTaskV2
|
20
26
|
from fractal_server.app.routes.auth import current_active_user
|
27
|
+
from fractal_server.app.routes.auth._aux_auth import _get_default_usergroup_id
|
21
28
|
from fractal_server.app.routes.auth._aux_auth import (
|
22
29
|
_verify_user_belongs_to_group,
|
23
30
|
)
|
@@ -33,6 +40,26 @@ router = APIRouter()
|
|
33
40
|
logger = set_logger(__name__)
|
34
41
|
|
35
42
|
|
43
|
+
def _version_sort_key(
|
44
|
+
task_group: TaskGroupV2,
|
45
|
+
) -> tuple[int, Version | str | None]:
|
46
|
+
"""
|
47
|
+
Returns a tuple used as (reverse) ordering key for TaskGroups in
|
48
|
+
`get_task_group_list`.
|
49
|
+
The TaskGroups with a parsable versions are the first in order,
|
50
|
+
sorted according to the sorting rules of packaging.version.Version.
|
51
|
+
Next in order we have the TaskGroups with non-null non-parsable versions,
|
52
|
+
sorted alphabetically.
|
53
|
+
Last we have the TaskGroups with null version.
|
54
|
+
"""
|
55
|
+
if task_group.version is None:
|
56
|
+
return (0, task_group.version)
|
57
|
+
try:
|
58
|
+
return (2, parse(task_group.version))
|
59
|
+
except InvalidVersion:
|
60
|
+
return (1, task_group.version)
|
61
|
+
|
62
|
+
|
36
63
|
@router.get("/activity/", response_model=list[TaskGroupActivityV2Read])
|
37
64
|
async def get_task_group_activity_list(
|
38
65
|
task_group_activity_id: int | None = None,
|
@@ -97,14 +124,14 @@ async def get_task_group_activity(
|
|
97
124
|
return activity
|
98
125
|
|
99
126
|
|
100
|
-
@router.get("/", response_model=list[TaskGroupReadV2])
|
127
|
+
@router.get("/", response_model=list[tuple[str, list[TaskGroupReadV2]]])
|
101
128
|
async def get_task_group_list(
|
102
129
|
user: UserOAuth = Depends(current_active_user),
|
103
130
|
db: AsyncSession = Depends(get_async_db),
|
104
131
|
only_active: bool = False,
|
105
132
|
only_owner: bool = False,
|
106
133
|
args_schema: bool = True,
|
107
|
-
) -> list[TaskGroupReadV2]:
|
134
|
+
) -> list[tuple[str, list[TaskGroupReadV2]]]:
|
108
135
|
"""
|
109
136
|
Get all accessible TaskGroups
|
110
137
|
"""
|
@@ -119,7 +146,7 @@ async def get_task_group_list(
|
|
119
146
|
)
|
120
147
|
),
|
121
148
|
)
|
122
|
-
stm = select(TaskGroupV2).where(condition)
|
149
|
+
stm = select(TaskGroupV2).where(condition).order_by(TaskGroupV2.pkg_name)
|
123
150
|
if only_active:
|
124
151
|
stm = stm.where(TaskGroupV2.active)
|
125
152
|
|
@@ -132,7 +159,28 @@ async def get_task_group_list(
|
|
132
159
|
setattr(task, "args_schema_non_parallel", None)
|
133
160
|
setattr(task, "args_schema_parallel", None)
|
134
161
|
|
135
|
-
|
162
|
+
default_group_id = await _get_default_usergroup_id(db)
|
163
|
+
grouped_result = [
|
164
|
+
(
|
165
|
+
pkg_name,
|
166
|
+
sorted(
|
167
|
+
(
|
168
|
+
await remove_duplicate_task_groups(
|
169
|
+
task_groups=list(groups),
|
170
|
+
user_id=user.id,
|
171
|
+
default_group_id=default_group_id,
|
172
|
+
db=db,
|
173
|
+
)
|
174
|
+
),
|
175
|
+
key=_version_sort_key,
|
176
|
+
reverse=True,
|
177
|
+
),
|
178
|
+
)
|
179
|
+
for pkg_name, groups in itertools.groupby(
|
180
|
+
task_groups, key=lambda tg: tg.pkg_name
|
181
|
+
)
|
182
|
+
]
|
183
|
+
return grouped_result
|
136
184
|
|
137
185
|
|
138
186
|
@router.get("/{task_group_id}/", response_model=TaskGroupReadV2)
|
@@ -21,6 +21,9 @@ from ._aux_functions_tasks import _check_type_filters_compatibility
|
|
21
21
|
from fractal_server.app.models import LinkUserGroup
|
22
22
|
from fractal_server.app.models import UserOAuth
|
23
23
|
from fractal_server.app.models.v2 import TaskGroupV2
|
24
|
+
from fractal_server.app.routes.api.v2._aux_task_group_disambiguation import (
|
25
|
+
_disambiguate_task_groups,
|
26
|
+
)
|
24
27
|
from fractal_server.app.routes.auth import current_active_user
|
25
28
|
from fractal_server.app.routes.auth._aux_auth import _get_default_usergroup_id
|
26
29
|
from fractal_server.app.schemas.v2 import TaskImportV2
|
@@ -85,76 +88,6 @@ async def _get_task_by_source(
|
|
85
88
|
return task_id
|
86
89
|
|
87
90
|
|
88
|
-
async def _disambiguate_task_groups(
|
89
|
-
*,
|
90
|
-
matching_task_groups: list[TaskGroupV2],
|
91
|
-
user_id: int,
|
92
|
-
db: AsyncSession,
|
93
|
-
default_group_id: int,
|
94
|
-
) -> TaskV2 | None:
|
95
|
-
"""
|
96
|
-
Disambiguate task groups based on ownership information.
|
97
|
-
"""
|
98
|
-
# Highest priority: task groups created by user
|
99
|
-
for task_group in matching_task_groups:
|
100
|
-
if task_group.user_id == user_id:
|
101
|
-
logger.info(
|
102
|
-
"[_disambiguate_task_groups] "
|
103
|
-
f"Found task group {task_group.id} with {user_id=}, return."
|
104
|
-
)
|
105
|
-
return task_group
|
106
|
-
logger.info(
|
107
|
-
"[_disambiguate_task_groups] "
|
108
|
-
f"No task group found with {user_id=}, continue."
|
109
|
-
)
|
110
|
-
|
111
|
-
# Medium priority: task groups owned by default user group
|
112
|
-
for task_group in matching_task_groups:
|
113
|
-
if task_group.user_group_id == default_group_id:
|
114
|
-
logger.info(
|
115
|
-
"[_disambiguate_task_groups] "
|
116
|
-
f"Found task group {task_group.id} with user_group_id="
|
117
|
-
f"{default_group_id}, return."
|
118
|
-
)
|
119
|
-
return task_group
|
120
|
-
logger.info(
|
121
|
-
"[_disambiguate_task_groups] "
|
122
|
-
"No task group found with user_group_id="
|
123
|
-
f"{default_group_id}, continue."
|
124
|
-
)
|
125
|
-
|
126
|
-
# Lowest priority: task groups owned by other groups, sorted
|
127
|
-
# according to age of the user/usergroup link
|
128
|
-
logger.info(
|
129
|
-
"[_disambiguate_task_groups] "
|
130
|
-
"Now sorting remaining task groups by oldest-user-link."
|
131
|
-
)
|
132
|
-
user_group_ids = [
|
133
|
-
task_group.user_group_id for task_group in matching_task_groups
|
134
|
-
]
|
135
|
-
stm = (
|
136
|
-
select(LinkUserGroup.group_id)
|
137
|
-
.where(LinkUserGroup.user_id == user_id)
|
138
|
-
.where(LinkUserGroup.group_id.in_(user_group_ids))
|
139
|
-
.order_by(LinkUserGroup.timestamp_created.asc())
|
140
|
-
)
|
141
|
-
res = await db.execute(stm)
|
142
|
-
oldest_user_group_id = res.scalars().first()
|
143
|
-
logger.info(
|
144
|
-
"[_disambiguate_task_groups] "
|
145
|
-
f"Result of sorting: {oldest_user_group_id=}."
|
146
|
-
)
|
147
|
-
task_group = next(
|
148
|
-
iter(
|
149
|
-
task_group
|
150
|
-
for task_group in matching_task_groups
|
151
|
-
if task_group.user_group_id == oldest_user_group_id
|
152
|
-
),
|
153
|
-
None,
|
154
|
-
)
|
155
|
-
return task_group
|
156
|
-
|
157
|
-
|
158
91
|
async def _get_task_by_taskimport(
|
159
92
|
*,
|
160
93
|
task_import: TaskImportV2,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
fractal_server/__init__.py,sha256=
|
1
|
+
fractal_server/__init__.py,sha256=6bdBE5bJoA01yAEh1heVK-Ta-FFnpeeh531ZgEO8YXY,24
|
2
2
|
fractal_server/__main__.py,sha256=rkM8xjY1KeS3l63irB8yCrlVobR-73uDapC4wvrIlxI,6957
|
3
3
|
fractal_server/alembic.ini,sha256=MWwi7GzjzawI9cCAK1LW7NxIBQDUqD12-ptJoq5JpP0,3153
|
4
4
|
fractal_server/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -35,6 +35,7 @@ fractal_server/app/routes/api/v2/_aux_functions_history.py,sha256=Z23xwvBaVEEQ5B
|
|
35
35
|
fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py,sha256=GpKfw9yj01LmOAuNMTOreU1PFkCKpjK5oCt7_wp35-A,6741
|
36
36
|
fractal_server/app/routes/api/v2/_aux_functions_task_version_update.py,sha256=WLDOYCnb6fnS5avKflyx6yN24Vo1n5kJk5ZyiKbzb8Y,1175
|
37
37
|
fractal_server/app/routes/api/v2/_aux_functions_tasks.py,sha256=MFYnyNPBACSHXTDLXe6cSennnpmlpajN84iivOOMW7Y,11599
|
38
|
+
fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py,sha256=2sK7-bZzcl3-2mkx62tw0MPxeUYVDch30DSWgdhouHI,4615
|
38
39
|
fractal_server/app/routes/api/v2/dataset.py,sha256=6u4MFqJ3YZ0Zq6Xx8CRMrTPKW55ZaR63Uno21DqFr4Q,8889
|
39
40
|
fractal_server/app/routes/api/v2/history.py,sha256=BEmf_ENF5HNMy8yXrxRdo4280rWuRUa1Jw4u8R9-LQQ,15477
|
40
41
|
fractal_server/app/routes/api/v2/images.py,sha256=TS1ltUhP0_SaViupdHrSh3MLDi5OVk-lOhE1VCVyZj0,7869
|
@@ -46,11 +47,11 @@ fractal_server/app/routes/api/v2/submit.py,sha256=_BDkWtFdo8-p7kZ0Oxaidei04MfuBe
|
|
46
47
|
fractal_server/app/routes/api/v2/task.py,sha256=cUFrCxFOLGlRV7UCbUMHs4Xy4tIc3pqwG8gEqVP5GcU,6939
|
47
48
|
fractal_server/app/routes/api/v2/task_collection.py,sha256=FGMhTnU88Umd8nMdriUYPtpTtAHcRBRrZIYyOesFhrU,12577
|
48
49
|
fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=EfGpv6W7xDyuYYp6E7XAcXLJiLNAImUHFqMDLgfh-4s,6730
|
49
|
-
fractal_server/app/routes/api/v2/task_group.py,sha256=
|
50
|
+
fractal_server/app/routes/api/v2/task_group.py,sha256=Xfsj5Wy0NOIkbeYsdqyFke4mkaeq0riJeTrGCHbt-eM,9059
|
50
51
|
fractal_server/app/routes/api/v2/task_group_lifecycle.py,sha256=C2U2V76YbbqDWmErJ98MH9C2C26Lve2p_35FZ1dNmXg,9095
|
51
52
|
fractal_server/app/routes/api/v2/task_version_update.py,sha256=h2c6aTLXj0_ZyBuHVsD5-ZTNMGEUpS96qZ4Ot1jlb74,7974
|
52
53
|
fractal_server/app/routes/api/v2/workflow.py,sha256=gwMtpfUY_JiTv5_R_q1I9WNkp6nTqEVtYx8jWNJRxcU,10227
|
53
|
-
fractal_server/app/routes/api/v2/workflow_import.py,sha256=
|
54
|
+
fractal_server/app/routes/api/v2/workflow_import.py,sha256=kOGDaCj0jCGK1WSYGbnUjtUg2U1YxUY9UMH-2ilqJg4,9027
|
54
55
|
fractal_server/app/routes/api/v2/workflowtask.py,sha256=vVqEoJa3lrMl2CU94WoxFaqO3U0QImPgvrkkUNdqDOU,7462
|
55
56
|
fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
|
56
57
|
fractal_server/app/routes/auth/_aux_auth.py,sha256=UZgauY0V6mSqjte_sYI1cBl2h8bcbLaeWzgpl1jdJlk,4883
|
@@ -131,6 +132,7 @@ fractal_server/config.py,sha256=ldI9VzEWmwU75Z7zVku6I-rXGKS3bJDdCifZnwad9-4,2592
|
|
131
132
|
fractal_server/data_migrations/2_14_10.py,sha256=gMRR5QB0SDv0ToEiXVLg1VrHprM_Ii-9O1Kg-ZF-YhY,1599
|
132
133
|
fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
|
133
134
|
fractal_server/data_migrations/tools.py,sha256=LeMeASwYGtEqd-3wOLle6WARdTGAimoyMmRbbJl-hAM,572
|
135
|
+
fractal_server/exceptions.py,sha256=7ftpWwNsTQmNonWCynhH5ErUh1haPPhIaVPrNHla7-o,53
|
134
136
|
fractal_server/gunicorn_fractal.py,sha256=u6U01TLGlXgq1v8QmEpLih3QnsInZD7CqphgJ_GrGzc,1230
|
135
137
|
fractal_server/images/__init__.py,sha256=-_wjoKtSX02P1KjDxDP_EXKvmbONTRmbf7iGVTsyBpM,154
|
136
138
|
fractal_server/images/models.py,sha256=6WchcIzLLLwdkLNRfg71Dl4Y-9UFLPyrrzh1lWgjuP0,1245
|
@@ -214,8 +216,8 @@ fractal_server/types/validators/_workflow_task_arguments_validators.py,sha256=HL
|
|
214
216
|
fractal_server/urls.py,sha256=QjIKAC1a46bCdiPMu3AlpgFbcv6a4l3ABcd5xz190Og,471
|
215
217
|
fractal_server/utils.py,sha256=FCY6HUsRnnbsWkT2kwQ2izijiHuCrCD3Kh50G0QudxE,3531
|
216
218
|
fractal_server/zip_tools.py,sha256=tqz_8f-vQ9OBRW-4OQfO6xxY-YInHTyHmZxU7U4PqZo,4885
|
217
|
-
fractal_server-2.14.
|
218
|
-
fractal_server-2.14.
|
219
|
-
fractal_server-2.14.
|
220
|
-
fractal_server-2.14.
|
221
|
-
fractal_server-2.14.
|
219
|
+
fractal_server-2.14.13.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
|
220
|
+
fractal_server-2.14.13.dist-info/METADATA,sha256=-h8oCvhvIAs4Sp0G5zxavNQkpS6NkBsM8XHHmCT47EE,4244
|
221
|
+
fractal_server-2.14.13.dist-info/WHEEL,sha256=7dDg4QLnNKTvwIDR9Ac8jJaAmBC_owJrckbC0jjThyA,88
|
222
|
+
fractal_server-2.14.13.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
|
223
|
+
fractal_server-2.14.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|