fractal-server 2.3.10__py3-none-any.whl → 2.4.0__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.
Files changed (55) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +25 -2
  3. fractal_server/app/models/__init__.py +11 -5
  4. fractal_server/app/models/linkusergroup.py +11 -0
  5. fractal_server/app/models/security.py +24 -3
  6. fractal_server/app/models/v1/project.py +1 -1
  7. fractal_server/app/models/v2/project.py +3 -3
  8. fractal_server/app/routes/admin/v1.py +14 -14
  9. fractal_server/app/routes/admin/v2.py +12 -12
  10. fractal_server/app/routes/api/__init__.py +2 -2
  11. fractal_server/app/routes/api/v1/_aux_functions.py +2 -2
  12. fractal_server/app/routes/api/v1/dataset.py +17 -15
  13. fractal_server/app/routes/api/v1/job.py +11 -9
  14. fractal_server/app/routes/api/v1/project.py +9 -9
  15. fractal_server/app/routes/api/v1/task.py +8 -8
  16. fractal_server/app/routes/api/v1/task_collection.py +5 -5
  17. fractal_server/app/routes/api/v1/workflow.py +13 -11
  18. fractal_server/app/routes/api/v1/workflowtask.py +6 -6
  19. fractal_server/app/routes/api/v2/_aux_functions.py +2 -2
  20. fractal_server/app/routes/api/v2/dataset.py +11 -11
  21. fractal_server/app/routes/api/v2/images.py +6 -6
  22. fractal_server/app/routes/api/v2/job.py +9 -9
  23. fractal_server/app/routes/api/v2/project.py +7 -7
  24. fractal_server/app/routes/api/v2/status.py +3 -3
  25. fractal_server/app/routes/api/v2/submit.py +3 -3
  26. fractal_server/app/routes/api/v2/task.py +8 -8
  27. fractal_server/app/routes/api/v2/task_collection.py +5 -5
  28. fractal_server/app/routes/api/v2/task_collection_custom.py +3 -3
  29. fractal_server/app/routes/api/v2/task_legacy.py +9 -9
  30. fractal_server/app/routes/api/v2/workflow.py +11 -11
  31. fractal_server/app/routes/api/v2/workflowtask.py +6 -6
  32. fractal_server/app/routes/auth/__init__.py +55 -0
  33. fractal_server/app/routes/auth/_aux_auth.py +107 -0
  34. fractal_server/app/routes/auth/current_user.py +60 -0
  35. fractal_server/app/routes/auth/group.py +176 -0
  36. fractal_server/app/routes/auth/group_names.py +34 -0
  37. fractal_server/app/routes/auth/login.py +25 -0
  38. fractal_server/app/routes/auth/oauth.py +63 -0
  39. fractal_server/app/routes/auth/register.py +23 -0
  40. fractal_server/app/routes/auth/router.py +19 -0
  41. fractal_server/app/routes/auth/users.py +192 -0
  42. fractal_server/app/runner/v2/__init__.py +1 -5
  43. fractal_server/app/runner/v2/_slurm_ssh/__init__.py +17 -0
  44. fractal_server/app/schemas/user.py +11 -0
  45. fractal_server/app/schemas/user_group.py +65 -0
  46. fractal_server/app/security/__init__.py +72 -75
  47. fractal_server/data_migrations/2_4_0.py +61 -0
  48. fractal_server/main.py +1 -9
  49. fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +53 -0
  50. {fractal_server-2.3.10.dist-info → fractal_server-2.4.0.dist-info}/METADATA +1 -1
  51. {fractal_server-2.3.10.dist-info → fractal_server-2.4.0.dist-info}/RECORD +54 -41
  52. fractal_server/app/routes/auth.py +0 -165
  53. {fractal_server-2.3.10.dist-info → fractal_server-2.4.0.dist-info}/LICENSE +0 -0
  54. {fractal_server-2.3.10.dist-info → fractal_server-2.4.0.dist-info}/WHEEL +0 -0
  55. {fractal_server-2.3.10.dist-info → fractal_server-2.4.0.dist-info}/entry_points.txt +0 -0
@@ -29,41 +29,38 @@ All routes are registerd under the `auth/` prefix.
29
29
  import contextlib
30
30
  from typing import Any
31
31
  from typing import AsyncGenerator
32
- from typing import Dict
33
32
  from typing import Generic
34
33
  from typing import Optional
35
34
  from typing import Type
36
35
 
37
36
  from fastapi import Depends
37
+ from fastapi import Request
38
38
  from fastapi_users import BaseUserManager
39
- from fastapi_users import FastAPIUsers
40
39
  from fastapi_users import IntegerIDMixin
41
- from fastapi_users.authentication import AuthenticationBackend
42
- from fastapi_users.authentication import BearerTransport
43
- from fastapi_users.authentication import CookieTransport
44
- from fastapi_users.authentication import JWTStrategy
45
40
  from fastapi_users.db.base import BaseUserDatabase
46
41
  from fastapi_users.exceptions import InvalidPasswordException
47
42
  from fastapi_users.exceptions import UserAlreadyExists
48
43
  from fastapi_users.models import ID
49
44
  from fastapi_users.models import OAP
50
45
  from fastapi_users.models import UP
51
- from sqlalchemy.exc import IntegrityError
52
46
  from sqlalchemy.ext.asyncio import AsyncSession
53
47
  from sqlalchemy.orm import selectinload
54
48
  from sqlmodel import func
55
49
  from sqlmodel import select
56
50
 
57
- from ...config import get_settings
58
- from ...syringe import Inject
59
51
  from ..db import get_async_db
60
- from fractal_server.app.models.security import OAuthAccount
61
- from fractal_server.app.models.security import UserOAuth as User
52
+ from fractal_server.app.db import get_sync_db
53
+ from fractal_server.app.models import LinkUserGroup
54
+ from fractal_server.app.models import OAuthAccount
55
+ from fractal_server.app.models import UserGroup
56
+ from fractal_server.app.models import UserOAuth
62
57
  from fractal_server.app.schemas.user import UserCreate
63
58
  from fractal_server.logger import get_logger
64
59
 
65
60
  logger = get_logger(__name__)
66
61
 
62
+ FRACTAL_DEFAULT_GROUP_NAME = "All"
63
+
67
64
 
68
65
  class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
69
66
  """
@@ -125,7 +122,7 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
125
122
  return user
126
123
  return None
127
124
 
128
- async def create(self, create_dict: Dict[str, Any]) -> UP:
125
+ async def create(self, create_dict: dict[str, Any]) -> UP:
129
126
  """Create a user."""
130
127
  user = self.user_model(**create_dict)
131
128
  self.session.add(user)
@@ -133,7 +130,7 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
133
130
  await self.session.refresh(user)
134
131
  return user
135
132
 
136
- async def update(self, user: UP, update_dict: Dict[str, Any]) -> UP:
133
+ async def update(self, user: UP, update_dict: dict[str, Any]) -> UP:
137
134
  for key, value in update_dict.items():
138
135
  setattr(user, key, value)
139
136
  self.session.add(user)
@@ -146,7 +143,7 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
146
143
  await self.session.commit()
147
144
 
148
145
  async def add_oauth_account(
149
- self, user: UP, create_dict: Dict[str, Any]
146
+ self, user: UP, create_dict: dict[str, Any]
150
147
  ) -> UP: # noqa
151
148
  if self.oauth_account_model is None:
152
149
  raise NotImplementedError()
@@ -160,7 +157,7 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
160
157
  return user
161
158
 
162
159
  async def update_oauth_account(
163
- self, user: UP, oauth_account: OAP, update_dict: Dict[str, Any]
160
+ self, user: UP, oauth_account: OAP, update_dict: dict[str, Any]
164
161
  ) -> UP:
165
162
  if self.oauth_account_model is None:
166
163
  raise NotImplementedError()
@@ -176,13 +173,14 @@ class SQLModelUserDatabaseAsync(Generic[UP, ID], BaseUserDatabase[UP, ID]):
176
173
  async def get_user_db(
177
174
  session: AsyncSession = Depends(get_async_db),
178
175
  ) -> AsyncGenerator[SQLModelUserDatabaseAsync, None]:
179
- yield SQLModelUserDatabaseAsync(session, User, OAuthAccount)
176
+ yield SQLModelUserDatabaseAsync(session, UserOAuth, OAuthAccount)
180
177
 
181
178
 
182
- class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
183
- async def validate_password(self, password: str, user: User) -> None:
179
+ class UserManager(IntegerIDMixin, BaseUserManager[UserOAuth, int]):
180
+ async def validate_password(self, password: str, user: UserOAuth) -> None:
184
181
  # check password length
185
- min_length, max_length = 4, 100
182
+ min_length = 4
183
+ max_length = 100
186
184
  if len(password) < min_length:
187
185
  raise InvalidPasswordException(
188
186
  f"The password is too short (minimum length: {min_length})."
@@ -192,6 +190,38 @@ class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
192
190
  f"The password is too long (maximum length: {min_length})."
193
191
  )
194
192
 
193
+ async def on_after_register(
194
+ self, user: UserOAuth, request: Optional[Request] = None
195
+ ):
196
+ logger.info(
197
+ f"New-user registration completed ({user.id=}, {user.email=})."
198
+ )
199
+ async for db in get_async_db():
200
+ # Find default group
201
+ stm = select(UserGroup).where(
202
+ UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
203
+ )
204
+ res = await db.execute(stm)
205
+ default_group = res.scalar_one_or_none()
206
+ if default_group is None:
207
+ logger.error(
208
+ f"No group found with name {FRACTAL_DEFAULT_GROUP_NAME}"
209
+ )
210
+ else:
211
+ logger.warning(
212
+ f"START adding {user.email} user to group "
213
+ f"{default_group.id=}."
214
+ )
215
+ link = LinkUserGroup(
216
+ user_id=user.id, group_id=default_group.id
217
+ )
218
+ db.add(link)
219
+ await db.commit()
220
+ logger.warning(
221
+ f"END adding {user.email} user to group "
222
+ f"{default_group.id=}."
223
+ )
224
+
195
225
 
196
226
  async def get_user_manager(
197
227
  user_db: SQLModelUserDatabaseAsync = Depends(get_user_db),
@@ -199,53 +229,6 @@ async def get_user_manager(
199
229
  yield UserManager(user_db)
200
230
 
201
231
 
202
- bearer_transport = BearerTransport(tokenUrl="/auth/token/login")
203
- cookie_transport = CookieTransport(cookie_samesite="none")
204
-
205
-
206
- def get_jwt_strategy() -> JWTStrategy:
207
- settings = Inject(get_settings)
208
- return JWTStrategy(
209
- secret=settings.JWT_SECRET_KEY, # type: ignore
210
- lifetime_seconds=settings.JWT_EXPIRE_SECONDS,
211
- )
212
-
213
-
214
- def get_jwt_cookie_strategy() -> JWTStrategy:
215
- settings = Inject(get_settings)
216
- return JWTStrategy(
217
- secret=settings.JWT_SECRET_KEY, # type: ignore
218
- lifetime_seconds=settings.COOKIE_EXPIRE_SECONDS,
219
- )
220
-
221
-
222
- token_backend = AuthenticationBackend(
223
- name="bearer-jwt",
224
- transport=bearer_transport,
225
- get_strategy=get_jwt_strategy,
226
- )
227
- cookie_backend = AuthenticationBackend(
228
- name="cookie-jwt",
229
- transport=cookie_transport,
230
- get_strategy=get_jwt_cookie_strategy,
231
- )
232
-
233
-
234
- fastapi_users = FastAPIUsers[User, int](
235
- get_user_manager,
236
- [token_backend, cookie_backend],
237
- )
238
-
239
-
240
- # Create dependencies for users
241
- current_active_user = fastapi_users.current_user(active=True)
242
- current_active_verified_user = fastapi_users.current_user(
243
- active=True, verified=True
244
- )
245
- current_active_superuser = fastapi_users.current_user(
246
- active=True, superuser=True
247
- )
248
-
249
232
  get_async_session_context = contextlib.asynccontextmanager(get_async_db)
250
233
  get_user_db_context = contextlib.asynccontextmanager(get_user_db)
251
234
  get_user_manager_context = contextlib.asynccontextmanager(get_user_manager)
@@ -286,9 +269,9 @@ async def _create_first_user(
286
269
 
287
270
  if is_superuser is True:
288
271
  # If a superuser already exists, exit
289
- stm = select(User).where(
290
- User.is_superuser == True # noqa E712
291
- )
272
+ stm = select(UserOAuth).where( # noqa
273
+ UserOAuth.is_superuser == True # noqa
274
+ ) # noqa
292
275
  res = await session.execute(stm)
293
276
  existing_superuser = res.scalars().first()
294
277
  if existing_superuser is not None:
@@ -311,11 +294,25 @@ async def _create_first_user(
311
294
  user = await user_manager.create(UserCreate(**kwargs))
312
295
  logger.info(f"User {user.email} created")
313
296
 
314
- except IntegrityError:
315
- logger.warning(
316
- f"Creation of user {email} failed with IntegrityError "
317
- "(likely due to concurrent attempts from different workers)."
318
- )
319
-
320
297
  except UserAlreadyExists:
321
298
  logger.warning(f"User {email} already exists")
299
+
300
+
301
+ def _create_first_group():
302
+ logger.info(
303
+ f"START _create_first_group, with name {FRACTAL_DEFAULT_GROUP_NAME}"
304
+ )
305
+ with next(get_sync_db()) as db:
306
+ group_all = db.execute(select(UserGroup))
307
+ if group_all.scalars().one_or_none() is None:
308
+ first_group = UserGroup(name=FRACTAL_DEFAULT_GROUP_NAME)
309
+ db.add(first_group)
310
+ db.commit()
311
+ logger.info(f"Created group {FRACTAL_DEFAULT_GROUP_NAME}")
312
+ else:
313
+ logger.info(
314
+ f"Group {FRACTAL_DEFAULT_GROUP_NAME} already exists, skip."
315
+ )
316
+ logger.info(
317
+ f"END _create_first_group, with name {FRACTAL_DEFAULT_GROUP_NAME}"
318
+ )
@@ -0,0 +1,61 @@
1
+ import logging
2
+
3
+ from packaging.version import parse
4
+ from sqlalchemy import select
5
+
6
+ import fractal_server
7
+ from fractal_server.app.db import get_sync_db
8
+ from fractal_server.app.models import LinkUserGroup
9
+ from fractal_server.app.models import UserGroup
10
+ from fractal_server.app.models import UserOAuth
11
+ from fractal_server.app.security import FRACTAL_DEFAULT_GROUP_NAME
12
+
13
+
14
+ def _check_current_version(*, expected_version: str):
15
+ # Check that this module matches with the current version
16
+ module_version = parse(expected_version)
17
+ current_version = parse(fractal_server.__VERSION__)
18
+ if (
19
+ current_version.major != module_version.major
20
+ or current_version.minor != module_version.minor
21
+ or current_version.micro != module_version.micro
22
+ ):
23
+ raise RuntimeError(
24
+ f"{fractal_server.__VERSION__=} not matching with {__file__=}"
25
+ )
26
+
27
+
28
+ def fix_db():
29
+ logger = logging.getLogger("fix_db")
30
+ logger.warning("START execution of fix_db function")
31
+ _check_current_version(expected_version="2.4.0")
32
+
33
+ with next(get_sync_db()) as db:
34
+ # Find default group
35
+ stm = select(UserGroup).where(
36
+ UserGroup.name == FRACTAL_DEFAULT_GROUP_NAME
37
+ )
38
+ res = db.execute(stm)
39
+ default_group = res.scalar_one_or_none()
40
+ if default_group is None:
41
+ raise RuntimeError("Default group not found, exit.")
42
+ logger.warning(
43
+ "Default user group exists: "
44
+ f"{default_group.id=}, {default_group.name=}."
45
+ )
46
+
47
+ # Find
48
+ stm = select(UserOAuth)
49
+ users = db.execute(stm).scalars().unique().all()
50
+ for user in sorted(users, key=lambda x: x.id):
51
+ logger.warning(
52
+ f"START adding {user.id=} ({user.email=}) to default group."
53
+ )
54
+ link = LinkUserGroup(user_id=user.id, group_id=default_group.id)
55
+ db.add(link)
56
+ db.commit()
57
+ logger.warning(
58
+ f"END adding {user.id=} ({user.email=}) to default group."
59
+ )
60
+
61
+ logger.warning("END of execution of fix_db function")
fractal_server/main.py CHANGED
@@ -22,7 +22,6 @@ from fastapi import FastAPI
22
22
 
23
23
  from .app.routes.aux._runner import _backend_supports_shutdown # FIXME: change
24
24
  from .app.runner.shutdown import cleanup_after_shutdown
25
- from .app.security import _create_first_user
26
25
  from .config import get_settings
27
26
  from .logger import config_uvicorn_loggers
28
27
  from .logger import get_logger
@@ -44,7 +43,7 @@ def collect_routers(app: FastAPI) -> None:
44
43
  from .app.routes.api.v2 import router_api_v2
45
44
  from .app.routes.admin.v1 import router_admin_v1
46
45
  from .app.routes.admin.v2 import router_admin_v2
47
- from .app.routes.auth import router_auth
46
+ from .app.routes.auth.router import router_auth
48
47
 
49
48
  settings = Inject(get_settings)
50
49
 
@@ -91,13 +90,6 @@ async def lifespan(app: FastAPI):
91
90
  logger.info("Start application startup")
92
91
  check_settings()
93
92
  settings = Inject(get_settings)
94
- await _create_first_user(
95
- email=settings.FRACTAL_DEFAULT_ADMIN_EMAIL,
96
- password=settings.FRACTAL_DEFAULT_ADMIN_PASSWORD,
97
- username=settings.FRACTAL_DEFAULT_ADMIN_USERNAME,
98
- is_superuser=True,
99
- is_verified=True,
100
- )
101
93
 
102
94
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
103
95
  from fractal_server.ssh._fabric import get_ssh_connection
@@ -0,0 +1,53 @@
1
+ """“Add_usergroup_and_linkusergroup_table”
2
+
3
+ Revision ID: 091b01f51f88
4
+ Revises: 5bf02391cfef
5
+ Create Date: 2024-09-09 13:17:51.008231
6
+
7
+ """
8
+ import sqlalchemy as sa
9
+ import sqlmodel
10
+ from alembic import op
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "091b01f51f88"
15
+ down_revision = "5bf02391cfef"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table(
23
+ "usergroup",
24
+ sa.Column("id", sa.Integer(), nullable=False),
25
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
26
+ sa.Column(
27
+ "timestamp_created", sa.DateTime(timezone=True), nullable=False
28
+ ),
29
+ sa.PrimaryKeyConstraint("id"),
30
+ sa.UniqueConstraint("name"),
31
+ )
32
+ op.create_table(
33
+ "linkusergroup",
34
+ sa.Column("group_id", sa.Integer(), nullable=False),
35
+ sa.Column("user_id", sa.Integer(), nullable=False),
36
+ sa.ForeignKeyConstraint(
37
+ ["group_id"],
38
+ ["usergroup.id"],
39
+ ),
40
+ sa.ForeignKeyConstraint(
41
+ ["user_id"],
42
+ ["user_oauth.id"],
43
+ ),
44
+ sa.PrimaryKeyConstraint("group_id", "user_id"),
45
+ )
46
+ # ### end Alembic commands ###
47
+
48
+
49
+ def downgrade() -> None:
50
+ # ### commands auto generated by Alembic - please adjust! ###
51
+ op.drop_table("linkusergroup")
52
+ op.drop_table("usergroup")
53
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.3.10
3
+ Version: 2.4.0
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,15 +1,16 @@
1
- fractal_server/__init__.py,sha256=eQ4t9Ak1YcHc5P0da0IEITN9Og3jo9sAKVRGq7f9qp0,23
2
- fractal_server/__main__.py,sha256=CocbzZooX1UtGqPi55GcHGNxnrJXFg5tUU5b3wyFCyo,4958
1
+ fractal_server/__init__.py,sha256=pxca-5QdURrVFaaROnGdT3tkxUBWpx7_zq2uX-gI-b0,22
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
5
5
  fractal_server/app/db/__init__.py,sha256=KpS_mfmRfew0kieFDlwkSrDFvpzxftcvPALBBZ0LxsY,4048
6
- fractal_server/app/models/__init__.py,sha256=QGRjxBgk6GzHyyXh_7RuHvpLoe5PTl1g5KLkGqhFYMQ,199
6
+ fractal_server/app/models/__init__.py,sha256=zt0Tiv91DWLg0daT8bkENz-LusihOJJX1h09UfHlAns,452
7
+ fractal_server/app/models/linkusergroup.py,sha256=ufthlbLFAWMU_dJmsVZzVlQa_D9C9SmgydxypQ2Xq1U,309
7
8
  fractal_server/app/models/linkuserproject.py,sha256=eQaourbGRshvlMVlKzLYJKHEjfsW1CbWws9yW4eHXhA,567
8
- fractal_server/app/models/security.py,sha256=0oYj_cqPcQFsPFDyN4OTsqbXsLlXRcweawjP_iSiRI0,2900
9
+ fractal_server/app/models/security.py,sha256=JGC3NNakIbJWhwksSIgJq0Jawo7m_fhsWX9L-uKpWJc,3551
9
10
  fractal_server/app/models/v1/__init__.py,sha256=hUI7dEbPaiZGN0IbHW4RSmSicyvtn_xeuevoX7zvUwI,466
10
11
  fractal_server/app/models/v1/dataset.py,sha256=99GDgt7njx8yYQApkImqp_7bHA5HH3ElvbR6Oyj9kVI,2017
11
12
  fractal_server/app/models/v1/job.py,sha256=QLGXcWdVRHaUHQNDapYYlLpEfw4K7QyD8TmcwhrWw2o,3304
12
- fractal_server/app/models/v1/project.py,sha256=tf6fniyBH-sb6rBvGiqNl2wgN9ipR4hDEE3OKvxKaoo,804
13
+ fractal_server/app/models/v1/project.py,sha256=JG7b5J9CzVNxua4MaMYpfB57xt2qjbXr5SnR7_oKQ70,819
13
14
  fractal_server/app/models/v1/state.py,sha256=m9gMZqqnm3oDpJNJp-Lht4kM7oO7pcEI7sL1g7LFvWU,1043
14
15
  fractal_server/app/models/v1/task.py,sha256=3xZqNeFYUqslh8ddMSXF2nO4nIiOD8T5Ij37wY20kss,2782
15
16
  fractal_server/app/models/v1/workflow.py,sha256=dnY5eMaOe3oZv8arn00RNX9qVkBtTLG-vYdWXcQuyo4,3950
@@ -17,39 +18,48 @@ fractal_server/app/models/v2/__init__.py,sha256=uLzdInqATSwi0bS_V4vKB-TqFrOFaXux
17
18
  fractal_server/app/models/v2/collection_state.py,sha256=nxb042i8tt8rCpmgbFJoBCYWU-34m0HdUfO9YurTp8k,588
18
19
  fractal_server/app/models/v2/dataset.py,sha256=-7sxHEw4IIAvF_uSan7tA3o8hvoakBkQ0SRvqS2iOQU,1455
19
20
  fractal_server/app/models/v2/job.py,sha256=ypJmN-qspkKBGhBG7Mt-HypSQqcQ2EmB4Bzzb2-y550,1535
20
- fractal_server/app/models/v2/project.py,sha256=CRBnZ8QITNp6u1f5bMxvi1_mcvEfXpWyitsWB5f7gn8,759
21
+ fractal_server/app/models/v2/project.py,sha256=rAHoh5KfYwIaW7rTX0_O0jvWmxEvfo1BafvmcXuSSRk,786
21
22
  fractal_server/app/models/v2/task.py,sha256=Esf2j9c-0pGYjdbb__Ptpdx7NCAKVxqbQMoza524miU,1286
22
23
  fractal_server/app/models/v2/workflow.py,sha256=YBgFGCziUgU0aJ5EM3Svu9W2c46AewZO9VBlFCHiSps,1069
23
24
  fractal_server/app/models/v2/workflowtask.py,sha256=3jEkObsSnlI05Pur_dSsXYdJxRqPL60Z7tK5-EJLOks,1532
24
25
  fractal_server/app/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
26
  fractal_server/app/routes/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- fractal_server/app/routes/admin/v1.py,sha256=jOBESHtU-RmkxqK3yxsYnea0ecJaeoYEIBPUTU72cMk,13898
27
- fractal_server/app/routes/admin/v2.py,sha256=A8TwuxlhhA1dJZqd5f3lkIYijXK_yKArbR4a5WM2IqM,13659
28
- fractal_server/app/routes/api/__init__.py,sha256=XlJUFd-0FossfyKyJti4dmwY6SMysQn1yiisMrNzgBE,615
27
+ fractal_server/app/routes/admin/v1.py,sha256=GIpZlwAwwwLGDWkBqywhtmp9TGsKLhGmZAdj1TDKJvE,13976
28
+ fractal_server/app/routes/admin/v2.py,sha256=P6ndmwNYfXDW5Xh971hMcmlVyVrGF8ubYS074r4bzok,13727
29
+ fractal_server/app/routes/api/__init__.py,sha256=2IDheFi0OFdsUg7nbUiyahqybvpgXqeHUXIL2QtWrQQ,641
29
30
  fractal_server/app/routes/api/v1/__init__.py,sha256=Y2HQdG197J0a7DyQEE2jn53IfxD0EHGhzK1I2JZuEck,958
30
- fractal_server/app/routes/api/v1/_aux_functions.py,sha256=hDBo2nBW04k3NKYCvrwO0T1FwzJQuMi2QfLNDDEek90,13000
31
- fractal_server/app/routes/api/v1/dataset.py,sha256=Yz3La_Vl6lUPRtAEMX730zNXal1D_8n1l0jQf8lWQSI,17167
32
- fractal_server/app/routes/api/v1/job.py,sha256=Ulec7Ik_wJkkapcLnMB1jlDf1Mt3r5BMu9cKKaKs9ZI,5285
33
- fractal_server/app/routes/api/v1/project.py,sha256=_zuaT2w6yTGP6YKjpY7UIlz7RxydJEaQO5PIaISUEfk,16289
34
- fractal_server/app/routes/api/v1/task.py,sha256=-dWV-xrLm25dQa8DXncFK4v3jBb00Yi0s6MoH1EyzTQ,6277
35
- fractal_server/app/routes/api/v1/task_collection.py,sha256=qWYaHmAg35coHBS5GOieMPWQfyjG_IrM5qA_wL4lv5k,8996
36
- fractal_server/app/routes/api/v1/workflow.py,sha256=0mvooYkLipblhDEzuaYtABstidLDSms30GENtsc2LlY,11122
37
- fractal_server/app/routes/api/v1/workflowtask.py,sha256=SogMLhpqUtW6QO008mOAIncfu9xP-7R0wC1GnQsY_4s,5729
31
+ fractal_server/app/routes/api/v1/_aux_functions.py,sha256=1YZdLch-Q1c44hQ_1ZEiijgWhLW6H3QEL5y5SHw5Ijk,13023
32
+ fractal_server/app/routes/api/v1/dataset.py,sha256=KVfKdp-bT8eB14kCjTSmpji4a2IPIHxGID8L10h3Wac,17282
33
+ fractal_server/app/routes/api/v1/job.py,sha256=0jGxvu0xNQnWuov2qnoo9yE7Oat37XbcVn4Ute-UsiE,5370
34
+ fractal_server/app/routes/api/v1/project.py,sha256=mFDqhkd_zR3wqxW5zadCCrxIX-jgL3lYF3CtfbV4pPs,16373
35
+ fractal_server/app/routes/api/v1/task.py,sha256=OLASM6M4yfHvQgHQOuR5810jR2s0_W1HQAmhGQkg0so,6356
36
+ fractal_server/app/routes/api/v1/task_collection.py,sha256=VYxhtd_idBppgJM7-FCHikI2OKMAIz05fhV_TsJpWI8,9060
37
+ fractal_server/app/routes/api/v1/workflow.py,sha256=2T93DuEnSshaDCue-JPmjuvGCtbk6lt9pFMuPt783t8,11217
38
+ fractal_server/app/routes/api/v1/workflowtask.py,sha256=OYYConwJbmNULDw5I3T-UbSJKrbbBiAHbbBeVcpoFKQ,5785
38
39
  fractal_server/app/routes/api/v2/__init__.py,sha256=JrPWfKIJy9btRCP-zw2nZwLpSdBxEKY5emuCuJbqG0s,1813
39
- fractal_server/app/routes/api/v2/_aux_functions.py,sha256=yeA0650pBk43M5ZQGpVQ17nH5D97NIGY-3tNNLQIW1M,14901
40
- fractal_server/app/routes/api/v2/dataset.py,sha256=_HjKNP9XsMGoqyubGdF2ZyeW7vXC3VdK_0_TaUxgIF0,8248
41
- fractal_server/app/routes/api/v2/images.py,sha256=4r_HblPWyuKSZSJZfn8mbDaLv1ncwZU0gWdKneZcNG4,7894
42
- fractal_server/app/routes/api/v2/job.py,sha256=EUoM-cg9761uY0W1B8B0ZhQYb_g6JwS8GdNepRub40k,5086
43
- fractal_server/app/routes/api/v2/project.py,sha256=U4TxD-J4TtQuu1D4BOhL1kTse5fCiNc3BwGH7bnlo38,6592
44
- fractal_server/app/routes/api/v2/status.py,sha256=osLexiMOSqmYcEV-41tlrwt9ofyFbtRm5HmPS5BU0t4,6394
45
- fractal_server/app/routes/api/v2/submit.py,sha256=Oqggq3GeBrUsE535tmw-JsRZEWa7jziU34fKdlj4QUE,8734
46
- fractal_server/app/routes/api/v2/task.py,sha256=bRTtGgL8BBGbT7csVeRB-a54clgU2xHydi5XpcByDxg,8297
47
- fractal_server/app/routes/api/v2/task_collection.py,sha256=BiZ5s6DwdQbM79s_dPivgoxvNJC3Dzfa4iR3N6A7xfE,12273
48
- fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=cvZorN_Xt57Rj-2JATRrdtSw6I_5befB0ua3FWh6hW4,5988
49
- fractal_server/app/routes/api/v2/task_legacy.py,sha256=P_VJv9v0yzFUBuS-DQHhMVSOe20ecGJJcFBqiiFciOM,1628
50
- fractal_server/app/routes/api/v2/workflow.py,sha256=2GlcYNjpvCdjwC_Kn7y0UP16B3pOLSNXBvIVsVDtDKM,11863
51
- fractal_server/app/routes/api/v2/workflowtask.py,sha256=l_eQPniK1zR0u249bJj4c2hFlyDwsSJgsFR6hxJaOjs,8007
52
- fractal_server/app/routes/auth.py,sha256=Xv80iqdyfY3lyicYs2Y8B6zEDEnyUu_H6_6psYtv3R4,4885
40
+ fractal_server/app/routes/api/v2/_aux_functions.py,sha256=sm__AJvEQfRYlh47uYMJ7PjSrmwz5LjjZMTcJdvhuVE,14924
41
+ fractal_server/app/routes/api/v2/dataset.py,sha256=Eilf_BAGjicIhqUiVwI86jlW45ineA5sVzxXW4b2GoQ,8329
42
+ fractal_server/app/routes/api/v2/images.py,sha256=JR1rR6qEs81nacjriOXAOBQjAbCXF4Ew7M7mkWdxBU0,7920
43
+ fractal_server/app/routes/api/v2/job.py,sha256=Bga2Kz1OjvDIdxZObWaaXVhNIhC_5JKhKRjEH2_ayEE,5157
44
+ fractal_server/app/routes/api/v2/project.py,sha256=eWYFJ7F2ZYQcpi-_n-rhPF-Q4gJhzYBsVGYFhHZZXAE,6653
45
+ fractal_server/app/routes/api/v2/status.py,sha256=6N9DSZ4iFqbZImorWfEAPoyoFUgEruo4Hweqo0x0xXU,6435
46
+ fractal_server/app/routes/api/v2/submit.py,sha256=iTGCYbxiZNszHQa8r3gmAR4QcF6QhVrb8ktzste2Ovc,8775
47
+ fractal_server/app/routes/api/v2/task.py,sha256=XgRnGBvSoI9VNJHtWZQ2Ide99f6elo7a2FN3GQkf0dU,8376
48
+ fractal_server/app/routes/api/v2/task_collection.py,sha256=wEwP8VfsxhKPZ6K3v1Bnput_Zw0Cjhlaal0-e50feIQ,12337
49
+ fractal_server/app/routes/api/v2/task_collection_custom.py,sha256=6MW-l7xTCTbWKDSYDw8e_hnm5jFCJpgFL3UdvrHAaBk,6029
50
+ fractal_server/app/routes/api/v2/task_legacy.py,sha256=GjW0lpX51b64-LCg2EtCO3Ik6Yx4eTqgbtspXh-K_4k,1744
51
+ fractal_server/app/routes/api/v2/workflow.py,sha256=8rir-alW5KjS6_HIkYFPkJcm3BouAFsKboKqGu8HSqo,11944
52
+ fractal_server/app/routes/api/v2/workflowtask.py,sha256=HoHFnVRDa0Cw1oqTea1Of6A5ZhjGJeSTDTdI-SKP7Co,8063
53
+ fractal_server/app/routes/auth/__init__.py,sha256=fao6CS0WiAjHDTvBzgBVV_bSXFpEAeDBF6Z6q7rRkPc,1658
54
+ fractal_server/app/routes/auth/_aux_auth.py,sha256=Kpgiw5q1eiCYLFkfhTT7XJGBu1d08YM71CEHhNtfJ5g,3126
55
+ fractal_server/app/routes/auth/current_user.py,sha256=LK0Z13NgaXYQ3FaQ3MNec0p2RRiKxKN31XIt2g9mcGk,2003
56
+ fractal_server/app/routes/auth/group.py,sha256=az8kRJJU5rA0L8GCX5kvRNuQhrsoGWPuvQ26z7iFQ3E,5388
57
+ fractal_server/app/routes/auth/group_names.py,sha256=zvYDfhxKlDmbSr-oLXYy6WUVkPPTvzH6ZJtuoNdGZbE,960
58
+ fractal_server/app/routes/auth/login.py,sha256=tSu6OBLOieoBtMZB4JkBAdEgH2Y8KqPGSbwy7NIypIo,566
59
+ fractal_server/app/routes/auth/oauth.py,sha256=AnFHbjqL2AgBX3eksI931xD6RTtmbciHBEuGf9YJLjU,1895
60
+ fractal_server/app/routes/auth/register.py,sha256=DlHq79iOvGd_gt2v9uwtsqIKeO6i_GKaW59VIkllPqY,587
61
+ fractal_server/app/routes/auth/router.py,sha256=zWoZWiO69U48QFQf5tLRYQDWu8PUCj7GacnaFeW1n_I,618
62
+ fractal_server/app/routes/auth/users.py,sha256=hNTIR0e3QFbUUDvkzKNXJt4uKwONlI36i6UjjMPCgAA,6672
53
63
  fractal_server/app/routes/aux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
64
  fractal_server/app/routes/aux/_job.py,sha256=q-RCiW17yXnZKAC_0La52RLvhqhxuvbgQJ2MlGXOj8A,702
55
65
  fractal_server/app/routes/aux/_runner.py,sha256=FdCVla5DxGAZ__aB7Z8dEJzD_RIeh5tftjrPyqkr8N8,895
@@ -90,7 +100,7 @@ fractal_server/app/runner/v1/_slurm/_submit_setup.py,sha256=KO9c694d318adoPQh9UG
90
100
  fractal_server/app/runner/v1/_slurm/get_slurm_config.py,sha256=6pQNNx997bLIfLp0guF09t_O0ZYRXnbEGLktSAcKnic,5999
91
101
  fractal_server/app/runner/v1/common.py,sha256=_L-vjLnWato80VdlB_BFN4G8P4jSM07u-5cnl1T3S34,3294
92
102
  fractal_server/app/runner/v1/handle_failed_job.py,sha256=bHzScC_aIlU3q-bQxGW6rfWV4xbZ2tho_sktjsAs1no,4684
93
- fractal_server/app/runner/v2/__init__.py,sha256=OYmJRpYTGUCwnbLeIDTms7erZxOQMV8vq5tdJmL0pNo,17368
103
+ fractal_server/app/runner/v2/__init__.py,sha256=gEof2P2PApNM4Ubc8JQg6NEBA8ej55Ar15YMWEdXdgA,17176
94
104
  fractal_server/app/runner/v2/_local/__init__.py,sha256=KTj14K6jH8fXGUi5P7u5_RqEE1zF4aXtgPxCKzw46iw,5971
95
105
  fractal_server/app/runner/v2/_local/_local_config.py,sha256=9oi209Dlp35ANfxb_DISqmMKKc6DPaMsmYVWbZLseME,3630
96
106
  fractal_server/app/runner/v2/_local/_submit_setup.py,sha256=MucNOo8Er0F5ZIwH7CnTeXgnFMc6d3pKPkv563QNVi0,1630
@@ -101,7 +111,7 @@ fractal_server/app/runner/v2/_local_experimental/_submit_setup.py,sha256=we7r-sQ
101
111
  fractal_server/app/runner/v2/_local_experimental/executor.py,sha256=vcBKjireIIyF5WgIQLatD6ojlWEydbTwyIG0bcpIjys,5438
102
112
  fractal_server/app/runner/v2/_slurm_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
113
  fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py,sha256=V47uckqA4Vp-7m5esDnTitekc-yabLhaZSlPj4jN_D8,6307
104
- fractal_server/app/runner/v2/_slurm_ssh/__init__.py,sha256=1p6d_ppXBqRNPXPGxM8cmIOffEsfkEPEfvDeT-_90dE,3990
114
+ fractal_server/app/runner/v2/_slurm_ssh/__init__.py,sha256=D0Dnbhnzw0BXwQmjqLmxqpE9oreAtasA-9aOzxC4l_I,4530
105
115
  fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py,sha256=a5_FDPH_yxYmrjAjMRLgh_Y4DSG3mRslCLQodGM3-t4,2838
106
116
  fractal_server/app/runner/v2/_slurm_sudo/__init__.py,sha256=q2fwiKqtNpXtfs5wUFQjwJxdYqKPPTbCy1ieBhhi-Bw,4316
107
117
  fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py,sha256=a5_FDPH_yxYmrjAjMRLgh_Y4DSG3mRslCLQodGM3-t4,2838
@@ -116,7 +126,8 @@ fractal_server/app/runner/v2/v1_compat.py,sha256=t0ficzAHUFaaeI56nqTb4YEKxfARF7L
116
126
  fractal_server/app/runner/versions.py,sha256=dSaPRWqmFPHjg20kTCHmi_dmGNcCETflDtDLronNanU,852
117
127
  fractal_server/app/schemas/__init__.py,sha256=jiIf54owztXupv3PO6Ilh0qcrkh2RUzKq4bcEFqEfc4,40
118
128
  fractal_server/app/schemas/_validators.py,sha256=1dTOYr1IZykrxuQSV2-zuEMZbKe_nGwrfS7iUrsh-sE,3461
119
- fractal_server/app/schemas/user.py,sha256=rE8WgBz-ceVUs0Sz2ZwcjUrSTZTnS0ys5SBtD2XD9r8,3113
129
+ fractal_server/app/schemas/user.py,sha256=jRMUd3v7kmnOdHhGSoljKbCk6xyaJuGHRXkaxAxI8RA,3437
130
+ fractal_server/app/schemas/user_group.py,sha256=2f9XQ6kIar6NMY4UCN0yOnve6ZDHUVZaHv1dna1Vfjg,1446
120
131
  fractal_server/app/schemas/v1/__init__.py,sha256=CrBGgBhoemCvmZ70ZUchM-jfVAICnoa7AjZBAtL2UB0,1852
121
132
  fractal_server/app/schemas/v1/applyworkflow.py,sha256=uuIh7fHlHEL4yLqL-dePI6-nfCsqgBYATmht7w_KITw,4302
122
133
  fractal_server/app/schemas/v1/dataset.py,sha256=n71lNUO3JLy2K3IM9BZM2Fk1EnKQOTU7pm2s2rJ1FGY,3444
@@ -138,18 +149,20 @@ fractal_server/app/schemas/v2/task.py,sha256=7IfxiZkaVqlARy7WYE_H8m7j_IEcuQaZORU
138
149
  fractal_server/app/schemas/v2/task_collection.py,sha256=8PG1bOqkfQqORMN0brWf6mHDmijt0bBW-mZsF7cSxUs,6129
139
150
  fractal_server/app/schemas/v2/workflow.py,sha256=Zzx3e-qgkH8le0FUmAx9UrV5PWd7bj14PPXUh_zgZXM,1827
140
151
  fractal_server/app/schemas/v2/workflowtask.py,sha256=atVuVN4aXsVEOmSd-vyg-8_8OnPmqx-gT75rXcn_AlQ,6552
141
- fractal_server/app/security/__init__.py,sha256=2-QbwuR-nsuHM_uwKS_WzYvkhnuhO5jUv8UVROetyVk,11169
152
+ fractal_server/app/security/__init__.py,sha256=FBxdrMvn2s3Gdmp1orqOpYji87JojLBzr9TMfblj1SI,11441
142
153
  fractal_server/config.py,sha256=R0VezSe2PEDjQjHEX2V29A1jMdoomdyECBjWNY15v_0,25049
154
+ fractal_server/data_migrations/2_4_0.py,sha256=T1HRRWp9ZuXeVfBY6NRGxQ8aNIHVSftOMnB-CMrfvi8,2117
143
155
  fractal_server/data_migrations/README.md,sha256=_3AEFvDg9YkybDqCLlFPdDmGJvr6Tw7HRI14aZ3LOIw,398
144
156
  fractal_server/gunicorn_fractal.py,sha256=u6U01TLGlXgq1v8QmEpLih3QnsInZD7CqphgJ_GrGzc,1230
145
157
  fractal_server/images/__init__.py,sha256=xO6jTLE4EZKO6cTDdJsBmK9cdeh9hFTaSbSuWgQg7y4,196
146
158
  fractal_server/images/models.py,sha256=9ipU5h4N6ogBChoB-2vHoqtL0TXOHCv6kRR-fER3mkM,4167
147
159
  fractal_server/images/tools.py,sha256=gxeniYy4Z-cp_ToK2LHPJUTVVUUrdpogYdcBUvBuLiY,2209
148
160
  fractal_server/logger.py,sha256=56wfka6fHaa3Rx5qO009nEs_y8gx5wZ2NUNZZ1I-uvc,5130
149
- fractal_server/main.py,sha256=Kmty1C9jPfH101nP3b82u9H9QvT-5Z-8Dd60wf9S5h0,5298
161
+ fractal_server/main.py,sha256=NUvMd8C8kosulAcQ8pCFLnOGdLw7j-6RzcHxoNvSB7k,5003
150
162
  fractal_server/migrations/README,sha256=4rQvyDfqodGhpJw74VYijRmgFP49ji5chyEemWGHsuw,59
151
163
  fractal_server/migrations/env.py,sha256=Bvg-FJzRJZIH_wqS_ZyZNXANIaathjo22_IY7c3fCjo,2636
152
164
  fractal_server/migrations/script.py.mako,sha256=oMXw9LC3zRbinWWPPDgeZ4z9FJrV2zhRWiYdS5YgNbI,526
165
+ fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py,sha256=BtcSkXsY7ZHNmwV93bSTiDw-wuxpfL7xpbZ5zH-nLnA,1456
153
166
  fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py,sha256=-wHe-fOffmYeAm0JXVl_lxZ7hhDkaEVqxgxpHkb_uL8,954
154
167
  fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py,sha256=Mob8McGYAcmgvrseyyYOa54E6Gsgr-4SiGdC-r9O4_A,1157
155
168
  fractal_server/migrations/versions/50a13d6138fd_initial_schema.py,sha256=zwXegXs9J40eyCWi3w0c_iIBVJjXNn4VdVnQaT3KxDg,8770
@@ -194,8 +207,8 @@ fractal_server/tasks/v2/utils.py,sha256=JOyCacb6MNvrwfLNTyLwcz8y79J29YuJeJ2MK5kq
194
207
  fractal_server/urls.py,sha256=5o_qq7PzKKbwq12NHSQZDmDitn5RAOeQ4xufu-2v9Zk,448
195
208
  fractal_server/utils.py,sha256=b7WwFdcFZ8unyT65mloFToYuEDXpQoHRcmRNqrhd_dQ,2115
196
209
  fractal_server/zip_tools.py,sha256=xYpzBshysD2nmxkD5WLYqMzPYUcCRM3kYy-7n9bJL-U,4426
197
- fractal_server-2.3.10.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
198
- fractal_server-2.3.10.dist-info/METADATA,sha256=nM7HELGaWnZRvIUFecLPswW58LsENUTxpt_p-Pt3PvM,4629
199
- fractal_server-2.3.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
200
- fractal_server-2.3.10.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
201
- fractal_server-2.3.10.dist-info/RECORD,,
210
+ fractal_server-2.4.0.dist-info/LICENSE,sha256=QKAharUuhxL58kSoLizKJeZE3mTCBnX6ucmz8W0lxlk,1576
211
+ fractal_server-2.4.0.dist-info/METADATA,sha256=iBNotVuDgKZmNmaiJb2sEKTYEUe0T4vLuE2iQZXHcYY,4628
212
+ fractal_server-2.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
213
+ fractal_server-2.4.0.dist-info/entry_points.txt,sha256=8tV2kynvFkjnhbtDnxAqImL6HMVKsopgGfew0DOp5UY,58
214
+ fractal_server-2.4.0.dist-info/RECORD,,