fractal-server 2.16.5__py3-none-any.whl → 2.17.0a0__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 (113) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +129 -22
  3. fractal_server/app/db/__init__.py +9 -11
  4. fractal_server/app/models/security.py +7 -3
  5. fractal_server/app/models/user_settings.py +0 -4
  6. fractal_server/app/models/v2/__init__.py +4 -0
  7. fractal_server/app/models/v2/job.py +3 -4
  8. fractal_server/app/models/v2/profile.py +16 -0
  9. fractal_server/app/models/v2/project.py +3 -0
  10. fractal_server/app/models/v2/resource.py +130 -0
  11. fractal_server/app/models/v2/task_group.py +3 -0
  12. fractal_server/app/routes/admin/v2/__init__.py +4 -0
  13. fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
  14. fractal_server/app/routes/admin/v2/profile.py +86 -0
  15. fractal_server/app/routes/admin/v2/resource.py +229 -0
  16. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +48 -82
  17. fractal_server/app/routes/api/__init__.py +26 -7
  18. fractal_server/app/routes/api/v2/_aux_functions.py +27 -1
  19. fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
  20. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  21. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -7
  22. fractal_server/app/routes/api/v2/project.py +5 -1
  23. fractal_server/app/routes/api/v2/submit.py +32 -24
  24. fractal_server/app/routes/api/v2/task.py +5 -0
  25. fractal_server/app/routes/api/v2/task_collection.py +36 -47
  26. fractal_server/app/routes/api/v2/task_collection_custom.py +11 -5
  27. fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -40
  28. fractal_server/app/routes/api/v2/task_group_lifecycle.py +39 -82
  29. fractal_server/app/routes/api/v2/workflow_import.py +4 -3
  30. fractal_server/app/routes/auth/_aux_auth.py +3 -3
  31. fractal_server/app/routes/auth/current_user.py +45 -7
  32. fractal_server/app/routes/auth/oauth.py +1 -1
  33. fractal_server/app/routes/auth/users.py +9 -0
  34. fractal_server/app/routes/aux/_runner.py +2 -1
  35. fractal_server/app/routes/aux/validate_user_profile.py +62 -0
  36. fractal_server/app/routes/aux/validate_user_settings.py +12 -9
  37. fractal_server/app/schemas/user.py +20 -13
  38. fractal_server/app/schemas/user_settings.py +0 -4
  39. fractal_server/app/schemas/v2/__init__.py +11 -0
  40. fractal_server/app/schemas/v2/profile.py +72 -0
  41. fractal_server/app/schemas/v2/resource.py +117 -0
  42. fractal_server/app/security/__init__.py +6 -13
  43. fractal_server/app/security/signup_email.py +2 -2
  44. fractal_server/app/user_settings.py +2 -12
  45. fractal_server/config/__init__.py +23 -0
  46. fractal_server/config/_database.py +58 -0
  47. fractal_server/config/_email.py +170 -0
  48. fractal_server/config/_init_data.py +27 -0
  49. fractal_server/config/_main.py +216 -0
  50. fractal_server/config/_settings_config.py +7 -0
  51. fractal_server/images/tools.py +3 -3
  52. fractal_server/logger.py +3 -3
  53. fractal_server/main.py +14 -21
  54. fractal_server/migrations/versions/90f6508c6379_drop_useroauth_username.py +36 -0
  55. fractal_server/migrations/versions/a80ac5a352bf_resource_profile.py +195 -0
  56. fractal_server/runner/config/__init__.py +2 -0
  57. fractal_server/runner/config/_local.py +21 -0
  58. fractal_server/runner/config/_slurm.py +128 -0
  59. fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
  60. fractal_server/runner/exceptions.py +4 -0
  61. fractal_server/runner/executors/base_runner.py +17 -7
  62. fractal_server/runner/executors/local/get_local_config.py +21 -86
  63. fractal_server/runner/executors/local/runner.py +48 -5
  64. fractal_server/runner/executors/slurm_common/_batching.py +2 -2
  65. fractal_server/runner/executors/slurm_common/base_slurm_runner.py +59 -25
  66. fractal_server/runner/executors/slurm_common/get_slurm_config.py +38 -54
  67. fractal_server/runner/executors/slurm_common/remote.py +1 -1
  68. fractal_server/runner/executors/slurm_common/{_slurm_config.py → slurm_config.py} +3 -254
  69. fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
  70. fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
  71. fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
  72. fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
  73. fractal_server/runner/v2/_local.py +36 -21
  74. fractal_server/runner/v2/_slurm_ssh.py +40 -4
  75. fractal_server/runner/v2/_slurm_sudo.py +41 -11
  76. fractal_server/runner/v2/db_tools.py +1 -1
  77. fractal_server/runner/v2/runner.py +3 -11
  78. fractal_server/runner/v2/runner_functions.py +42 -28
  79. fractal_server/runner/v2/submit_workflow.py +87 -108
  80. fractal_server/runner/versions.py +8 -3
  81. fractal_server/ssh/_fabric.py +6 -6
  82. fractal_server/tasks/config/__init__.py +3 -0
  83. fractal_server/tasks/config/_pixi.py +127 -0
  84. fractal_server/tasks/config/_python.py +51 -0
  85. fractal_server/tasks/v2/local/_utils.py +7 -7
  86. fractal_server/tasks/v2/local/collect.py +13 -5
  87. fractal_server/tasks/v2/local/collect_pixi.py +26 -10
  88. fractal_server/tasks/v2/local/deactivate.py +7 -1
  89. fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
  90. fractal_server/tasks/v2/local/delete.py +4 -0
  91. fractal_server/tasks/v2/local/reactivate.py +13 -5
  92. fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
  93. fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
  94. fractal_server/tasks/v2/ssh/_utils.py +6 -7
  95. fractal_server/tasks/v2/ssh/collect.py +19 -12
  96. fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
  97. fractal_server/tasks/v2/ssh/deactivate.py +12 -8
  98. fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
  99. fractal_server/tasks/v2/ssh/delete.py +12 -9
  100. fractal_server/tasks/v2/ssh/reactivate.py +18 -12
  101. fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
  102. fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
  103. fractal_server/tasks/v2/utils_database.py +2 -2
  104. fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
  105. fractal_server/tasks/v2/utils_templates.py +7 -10
  106. fractal_server/utils.py +1 -1
  107. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/METADATA +5 -5
  108. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/RECORD +112 -90
  109. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/WHEEL +1 -1
  110. fractal_server/config.py +0 -906
  111. /fractal_server/{runner → app}/shutdown.py +0 -0
  112. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info}/entry_points.txt +0 -0
  113. {fractal_server-2.16.5.dist-info → fractal_server-2.17.0a0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,216 @@
1
+ import logging
2
+ from os import environ
3
+ from os import getenv
4
+ from typing import Literal
5
+ from typing import TypeVar
6
+
7
+ from pydantic import BaseModel
8
+ from pydantic import Field
9
+ from pydantic import model_validator
10
+ from pydantic import SecretStr
11
+ from pydantic_settings import BaseSettings
12
+ from pydantic_settings import SettingsConfigDict
13
+
14
+ from ._settings_config import SETTINGS_CONFIG_DICT
15
+ from fractal_server.types import AbsolutePathStr
16
+
17
+
18
+ class FractalConfigurationError(ValueError):
19
+ pass
20
+
21
+
22
+ T = TypeVar("T")
23
+
24
+
25
+ class OAuthClientConfig(BaseModel):
26
+ """
27
+ OAuth Client Config Model
28
+
29
+ This model wraps the variables that define a client against an Identity
30
+ Provider. As some providers are supported by the libraries used within the
31
+ server, some attributes are optional.
32
+
33
+ Attributes:
34
+ CLIENT_NAME:
35
+ The name of the client
36
+ CLIENT_ID:
37
+ ID of client
38
+ CLIENT_SECRET:
39
+ Secret to authorise against the identity provider
40
+ OIDC_CONFIGURATION_ENDPOINT:
41
+ OpenID configuration endpoint,
42
+ allowing to discover the required endpoints automatically
43
+ REDIRECT_URL:
44
+ String to be used as `redirect_url` argument for
45
+ `fastapi_users.get_oauth_router`, and then in
46
+ `httpx_oauth.integrations.fastapi.OAuth2AuthorizeCallback`.
47
+ """
48
+
49
+ CLIENT_NAME: str
50
+ CLIENT_ID: str
51
+ CLIENT_SECRET: SecretStr
52
+ OIDC_CONFIGURATION_ENDPOINT: str | None = None
53
+ REDIRECT_URL: str | None = None
54
+
55
+ @model_validator(mode="before")
56
+ @classmethod
57
+ def check_configuration(cls, values):
58
+ if values.get("CLIENT_NAME") not in ["GOOGLE", "GITHUB"]:
59
+ if not values.get("OIDC_CONFIGURATION_ENDPOINT"):
60
+ raise FractalConfigurationError(
61
+ f"Missing OAUTH_{values.get('CLIENT_NAME')}"
62
+ "_OIDC_CONFIGURATION_ENDPOINT"
63
+ )
64
+ return values
65
+
66
+
67
+ class Settings(BaseSettings):
68
+ """
69
+ Contains all the configuration variables for Fractal Server
70
+
71
+ The attributes of this class are set from the environment.
72
+ """
73
+
74
+ model_config = SettingsConfigDict(**SETTINGS_CONFIG_DICT)
75
+
76
+ OAUTH_CLIENTS_CONFIG: list[OAuthClientConfig] = Field(default_factory=list)
77
+
78
+ # JWT TOKEN
79
+ JWT_EXPIRE_SECONDS: int = 180
80
+ """
81
+ JWT token lifetime, in seconds.
82
+ """
83
+
84
+ JWT_SECRET_KEY: SecretStr
85
+ """
86
+ JWT secret
87
+
88
+ ⚠️ **IMPORTANT**: set this variable to a secure string, and do not disclose
89
+ it.
90
+ """
91
+
92
+ # COOKIE TOKEN
93
+ COOKIE_EXPIRE_SECONDS: int = 86400
94
+ """
95
+ Cookie token lifetime, in seconds.
96
+ """
97
+
98
+ @model_validator(mode="before")
99
+ @classmethod
100
+ def collect_oauth_clients(cls, values):
101
+ """
102
+ Automatic collection of OAuth Clients
103
+
104
+ This method collects the environment variables relative to a single
105
+ OAuth client and saves them within the `Settings` object in the form
106
+ of an `OAuthClientConfig` instance.
107
+
108
+ Fractal can support an arbitrary number of OAuth providers, which are
109
+ automatically detected by parsing the environment variable names. In
110
+ particular, to set the provider `FOO`, one must specify the variables
111
+
112
+ OAUTH_FOO_CLIENT_ID
113
+ OAUTH_FOO_CLIENT_SECRET
114
+ ...
115
+
116
+ etc (cf. OAuthClientConfig).
117
+ """
118
+ oauth_env_variable_keys = [
119
+ key for key in environ.keys() if key.startswith("OAUTH_")
120
+ ]
121
+ clients_available = {
122
+ var.split("_")[1] for var in oauth_env_variable_keys
123
+ }
124
+
125
+ values["OAUTH_CLIENTS_CONFIG"] = []
126
+ for client in clients_available:
127
+ prefix = f"OAUTH_{client}"
128
+ oauth_client_config = OAuthClientConfig(
129
+ CLIENT_NAME=client,
130
+ CLIENT_ID=getenv(f"{prefix}_CLIENT_ID", None),
131
+ CLIENT_SECRET=getenv(f"{prefix}_CLIENT_SECRET", None),
132
+ OIDC_CONFIGURATION_ENDPOINT=getenv(
133
+ f"{prefix}_OIDC_CONFIGURATION_ENDPOINT", None
134
+ ),
135
+ REDIRECT_URL=getenv(f"{prefix}_REDIRECT_URL", None),
136
+ )
137
+ values["OAUTH_CLIENTS_CONFIG"].append(oauth_client_config)
138
+ return values
139
+
140
+ ###########################################################################
141
+ # FRACTAL SPECIFIC
142
+ ###########################################################################
143
+
144
+ # Note: we do not use ResourceType here to avoid circular imports
145
+ FRACTAL_RUNNER_BACKEND: Literal[
146
+ "local", "slurm_ssh", "slurm_sudo"
147
+ ] = "local"
148
+ """
149
+ Select which runner backend to use.
150
+ """
151
+
152
+ FRACTAL_LOGGING_LEVEL: int = logging.INFO
153
+ """
154
+ Logging-level threshold for logging
155
+
156
+ Only logs of with this level (or higher) will appear in the console logs.
157
+ """
158
+
159
+ FRACTAL_API_MAX_JOB_LIST_LENGTH: int = 50
160
+ """
161
+ Number of ids that can be stored in the `jobsV2` attribute of
162
+ `app.state`.
163
+ """
164
+
165
+ FRACTAL_GRACEFUL_SHUTDOWN_TIME: int = 30
166
+ """
167
+ Waiting time for the shutdown phase of executors
168
+ """
169
+
170
+ FRACTAL_VIEWER_AUTHORIZATION_SCHEME: Literal[
171
+ "viewer-paths", "users-folders", "none"
172
+ ] = "none"
173
+ """
174
+ Defines how the list of allowed viewer paths is built.
175
+
176
+ This variable affects the `GET /auth/current-user/allowed-viewer-paths/`
177
+ response, which is then consumed by
178
+ [fractal-vizarr-viewer](https://github.com/fractal-analytics-platform/fractal-vizarr-viewer).
179
+
180
+ Options:
181
+
182
+ - "viewer-paths": The list of allowed viewer paths will include the user's
183
+ `project_dir` along with any path defined in user groups' `viewer_paths`
184
+ attributes.
185
+ - "users-folders": The list will consist of the user's `project_dir` and a
186
+ user-specific folder. The user folder is constructed by concatenating
187
+ the base folder `FRACTAL_VIEWER_BASE_FOLDER` with the user's
188
+ `slurm_user`.
189
+ - "none": An empty list will be returned, indicating no access to
190
+ viewer paths. Useful when vizarr viewer is not used.
191
+ """
192
+
193
+ FRACTAL_VIEWER_BASE_FOLDER: AbsolutePathStr | None = None
194
+ """
195
+ Base path to Zarr files that will be served by fractal-vizarr-viewer;
196
+ This variable is required and used only when
197
+ FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders".
198
+ """
199
+
200
+ def check(self):
201
+ """
202
+ Make sure that required variables are set
203
+
204
+ This method must be called before the server starts
205
+ """
206
+ # FRACTAL_VIEWER_BASE_FOLDER is required when
207
+ # FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "users-folders"
208
+ # and it must be an absolute path
209
+ if self.FRACTAL_VIEWER_AUTHORIZATION_SCHEME == "users-folders":
210
+ viewer_base_folder = self.FRACTAL_VIEWER_BASE_FOLDER
211
+ if viewer_base_folder is None:
212
+ raise FractalConfigurationError(
213
+ "FRACTAL_VIEWER_BASE_FOLDER is required when "
214
+ "FRACTAL_VIEWER_AUTHORIZATION_SCHEME is set to "
215
+ "users-folders"
216
+ )
@@ -0,0 +1,7 @@
1
+ from pathlib import Path
2
+
3
+ SETTINGS_CONFIG_DICT = dict(
4
+ case_sensitive=True,
5
+ env_file=Path(".fractal_server.env"),
6
+ extra="ignore",
7
+ )
@@ -16,7 +16,7 @@ def find_image_by_zarr_url(
16
16
  """
17
17
  Return a copy of the image with a given zarr_url, and its positional index.
18
18
 
19
- Arguments:
19
+ Args:
20
20
  images: List of images.
21
21
  zarr_url: Path that the returned image must have.
22
22
 
@@ -40,7 +40,7 @@ def match_filter(
40
40
  """
41
41
  Find whether an image matches a filter set.
42
42
 
43
- Arguments:
43
+ Args:
44
44
  image: A single image.
45
45
  type_filters:
46
46
  attribute_filters:
@@ -70,7 +70,7 @@ def filter_image_list(
70
70
  """
71
71
  Compute a sublist with images that match a filter set.
72
72
 
73
- Arguments:
73
+ Args:
74
74
  images: A list of images.
75
75
  type_filters:
76
76
  attribute_filters:
fractal_server/logger.py CHANGED
@@ -44,7 +44,7 @@ def get_logger(logger_name: str | None = None) -> logging.Logger:
44
44
  close_logger(logger)
45
45
  ```
46
46
 
47
- Arguments:
47
+ Args:
48
48
  logger_name: Name of logger
49
49
  Returns:
50
50
  Logger with name `logger_name`
@@ -124,7 +124,7 @@ def close_logger(logger: logging.Logger) -> None:
124
124
  """
125
125
  Close all handlers associated to a `logging.Logger` object
126
126
 
127
- Arguments:
127
+ Args:
128
128
  logger: The actual logger
129
129
  """
130
130
  for handle in logger.handlers:
@@ -135,7 +135,7 @@ def reset_logger_handlers(logger: logging.Logger) -> None:
135
135
  """
136
136
  Close and remove all handlers associated to a `logging.Logger` object
137
137
 
138
- Arguments:
138
+ Args:
139
139
  logger: The actual logger
140
140
  """
141
141
  close_logger(logger)
fractal_server/main.py CHANGED
@@ -1,34 +1,21 @@
1
- # Copyright 2022 (C) Friedrich Miescher Institute for Biomedical Research and
2
- # University of Zurich
3
- #
4
- # Original authors:
5
- # Jacopo Nespolo <jacopo.nespolo@exact-lab.it>
6
- # Marco Franzon <marco.franzon@exact-lab.it>
7
- # Tommaso Comaprin <tommaso.comparin@exact-lab.it>
8
- #
9
- # This file is part of Fractal and was originally developed by eXact lab S.r.l.
10
- # <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
11
- # Institute for Biomedical Research and Pelkmans Lab from the University of
12
- # Zurich.
13
- """
14
- # Application factory
15
-
16
- This module sets up the FastAPI application that serves the Fractal Server.
17
- """
18
1
  import os
19
2
  from contextlib import asynccontextmanager
3
+ from itertools import chain
20
4
 
21
5
  from fastapi import FastAPI
22
6
 
23
7
  from .app.routes.aux._runner import _backend_supports_shutdown
8
+ from .app.shutdown import cleanup_after_shutdown
9
+ from .config import get_db_settings
10
+ from .config import get_email_settings
24
11
  from .config import get_settings
25
12
  from .logger import config_uvicorn_loggers
26
13
  from .logger import get_logger
27
14
  from .logger import reset_logger_handlers
28
15
  from .logger import set_logger
29
- from .runner.shutdown import cleanup_after_shutdown
30
16
  from .syringe import Inject
31
17
  from fractal_server import __VERSION__
18
+ from fractal_server.app.schemas.v2 import ResourceType
32
19
 
33
20
 
34
21
  def collect_routers(app: FastAPI) -> None:
@@ -64,10 +51,16 @@ def check_settings() -> None:
64
51
  """
65
52
  settings = Inject(get_settings)
66
53
  settings.check()
54
+ db_settings = Inject(get_db_settings)
55
+ email_settings = Inject(get_email_settings)
67
56
 
68
57
  logger = set_logger("fractal_server_settings")
69
58
  logger.debug("Fractal Settings:")
70
- for key, value in settings.model_dump().items():
59
+ for key, value in chain(
60
+ db_settings.model_dump().items(),
61
+ settings.model_dump().items(),
62
+ email_settings.model_dump().items(),
63
+ ):
71
64
  if any(s in key.upper() for s in ["PASSWORD", "SECRET", "KEY"]):
72
65
  value = "*****"
73
66
  logger.debug(f" {key}: {value}")
@@ -82,7 +75,7 @@ async def lifespan(app: FastAPI):
82
75
  check_settings()
83
76
  settings = Inject(get_settings)
84
77
 
85
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
78
+ if settings.FRACTAL_RUNNER_BACKEND == ResourceType.SLURM_SSH:
86
79
  from fractal_server.ssh._fabric import FractalSSHList
87
80
 
88
81
  app.state.fractal_ssh_list = FractalSSHList()
@@ -103,7 +96,7 @@ async def lifespan(app: FastAPI):
103
96
  logger = get_logger("fractal_server.lifespan")
104
97
  logger.info("[teardown] START")
105
98
 
106
- if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
99
+ if settings.FRACTAL_RUNNER_BACKEND == ResourceType.SLURM_SSH:
107
100
  logger.info(
108
101
  "[teardown] Close FractalSSH connections "
109
102
  f"(current size: {app.state.fractal_ssh_list.size})."
@@ -0,0 +1,36 @@
1
+ """drop useroauth.username
2
+
3
+ Revision ID: 90f6508c6379
4
+ Revises: a80ac5a352bf
5
+ Create Date: 2025-10-15 16:09:40.922407
6
+
7
+ """
8
+ import sqlalchemy as sa
9
+ from alembic import op
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "90f6508c6379"
14
+ down_revision = "a80ac5a352bf"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table("user_oauth", schema=None) as batch_op:
22
+ batch_op.drop_column("username")
23
+
24
+ # ### end Alembic commands ###
25
+
26
+
27
+ def downgrade() -> None:
28
+ # ### commands auto generated by Alembic - please adjust! ###
29
+ with op.batch_alter_table("user_oauth", schema=None) as batch_op:
30
+ batch_op.add_column(
31
+ sa.Column(
32
+ "username", sa.VARCHAR(), autoincrement=False, nullable=True
33
+ )
34
+ )
35
+
36
+ # ### end Alembic commands ###
@@ -0,0 +1,195 @@
1
+ """resource-profile
2
+
3
+ Revision ID: a80ac5a352bf
4
+ Revises: 981d588fe248
5
+ Create Date: 2025-10-15 15:53:34.823398
6
+
7
+ """
8
+ import sqlalchemy as sa
9
+ import sqlmodel
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "a80ac5a352bf"
15
+ down_revision = "981d588fe248"
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
+ "resource",
24
+ sa.Column("id", sa.Integer(), nullable=False),
25
+ sa.Column("type", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
26
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
27
+ sa.Column(
28
+ "timestamp_created", sa.DateTime(timezone=True), nullable=False
29
+ ),
30
+ sa.Column("host", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
31
+ sa.Column(
32
+ "jobs_local_dir",
33
+ sqlmodel.sql.sqltypes.AutoString(),
34
+ nullable=False,
35
+ ),
36
+ sa.Column(
37
+ "jobs_runner_config",
38
+ postgresql.JSONB(astext_type=sa.Text()),
39
+ server_default="{}",
40
+ nullable=False,
41
+ ),
42
+ sa.Column(
43
+ "jobs_slurm_python_worker",
44
+ sqlmodel.sql.sqltypes.AutoString(),
45
+ nullable=True,
46
+ ),
47
+ sa.Column("jobs_poll_interval", sa.Integer(), nullable=False),
48
+ sa.Column(
49
+ "tasks_local_dir",
50
+ sqlmodel.sql.sqltypes.AutoString(),
51
+ nullable=False,
52
+ ),
53
+ sa.Column(
54
+ "tasks_python_config",
55
+ postgresql.JSONB(astext_type=sa.Text()),
56
+ server_default="{}",
57
+ nullable=False,
58
+ ),
59
+ sa.Column(
60
+ "tasks_pixi_config",
61
+ postgresql.JSONB(astext_type=sa.Text()),
62
+ server_default="{}",
63
+ nullable=False,
64
+ ),
65
+ sa.CheckConstraint(
66
+ "(type = 'local') OR (jobs_slurm_python_worker IS NOT NULL)",
67
+ name=op.f(
68
+ "ck_resource_`ck_resource_jobs_slurm_python_worker_set`"
69
+ ),
70
+ ),
71
+ sa.CheckConstraint(
72
+ "type IN ('local', 'slurm_sudo', 'slurm_ssh')",
73
+ name=op.f("ck_resource_`ck_resource_correct_type`"),
74
+ ),
75
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_resource")),
76
+ sa.UniqueConstraint("name", name=op.f("uq_resource_name")),
77
+ )
78
+ op.create_table(
79
+ "profile",
80
+ sa.Column("id", sa.Integer(), nullable=False),
81
+ sa.Column("resource_id", sa.Integer(), nullable=False),
82
+ sa.Column(
83
+ "resource_type", sqlmodel.sql.sqltypes.AutoString(), nullable=False
84
+ ),
85
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
86
+ sa.Column(
87
+ "username", sqlmodel.sql.sqltypes.AutoString(), nullable=True
88
+ ),
89
+ sa.Column(
90
+ "ssh_key_path", sqlmodel.sql.sqltypes.AutoString(), nullable=True
91
+ ),
92
+ sa.Column(
93
+ "jobs_remote_dir",
94
+ sqlmodel.sql.sqltypes.AutoString(),
95
+ nullable=True,
96
+ ),
97
+ sa.Column(
98
+ "tasks_remote_dir",
99
+ sqlmodel.sql.sqltypes.AutoString(),
100
+ nullable=True,
101
+ ),
102
+ sa.ForeignKeyConstraint(
103
+ ["resource_id"],
104
+ ["resource.id"],
105
+ name=op.f("fk_profile_resource_id_resource"),
106
+ ondelete="CASCADE",
107
+ ),
108
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_profile")),
109
+ sa.UniqueConstraint("name", name=op.f("uq_profile_name")),
110
+ )
111
+ with op.batch_alter_table("projectv2", schema=None) as batch_op:
112
+ batch_op.add_column(
113
+ sa.Column("resource_id", sa.Integer(), nullable=True)
114
+ )
115
+ batch_op.create_foreign_key(
116
+ batch_op.f("fk_projectv2_resource_id_resource"),
117
+ "resource",
118
+ ["resource_id"],
119
+ ["id"],
120
+ ondelete="SET NULL",
121
+ )
122
+
123
+ with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
124
+ batch_op.add_column(
125
+ sa.Column("resource_id", sa.Integer(), nullable=True)
126
+ )
127
+ batch_op.create_foreign_key(
128
+ batch_op.f("fk_taskgroupv2_resource_id_resource"),
129
+ "resource",
130
+ ["resource_id"],
131
+ ["id"],
132
+ ondelete="SET NULL",
133
+ )
134
+
135
+ with op.batch_alter_table("user_oauth", schema=None) as batch_op:
136
+ batch_op.add_column(
137
+ sa.Column("profile_id", sa.Integer(), nullable=True)
138
+ )
139
+ batch_op.create_foreign_key(
140
+ batch_op.f("fk_user_oauth_profile_id_profile"),
141
+ "profile",
142
+ ["profile_id"],
143
+ ["id"],
144
+ ondelete="SET NULL",
145
+ )
146
+
147
+ with op.batch_alter_table("user_settings", schema=None) as batch_op:
148
+ batch_op.drop_column("ssh_jobs_dir")
149
+ batch_op.drop_column("ssh_tasks_dir")
150
+
151
+ # ### end Alembic commands ###
152
+
153
+
154
+ def downgrade() -> None:
155
+ # ### commands auto generated by Alembic - please adjust! ###
156
+ with op.batch_alter_table("user_settings", schema=None) as batch_op:
157
+ batch_op.add_column(
158
+ sa.Column(
159
+ "ssh_tasks_dir",
160
+ sa.VARCHAR(),
161
+ autoincrement=False,
162
+ nullable=True,
163
+ )
164
+ )
165
+ batch_op.add_column(
166
+ sa.Column(
167
+ "ssh_jobs_dir",
168
+ sa.VARCHAR(),
169
+ autoincrement=False,
170
+ nullable=True,
171
+ )
172
+ )
173
+
174
+ with op.batch_alter_table("user_oauth", schema=None) as batch_op:
175
+ batch_op.drop_constraint(
176
+ batch_op.f("fk_user_oauth_profile_id_profile"), type_="foreignkey"
177
+ )
178
+ batch_op.drop_column("profile_id")
179
+
180
+ with op.batch_alter_table("taskgroupv2", schema=None) as batch_op:
181
+ batch_op.drop_constraint(
182
+ batch_op.f("fk_taskgroupv2_resource_id_resource"),
183
+ type_="foreignkey",
184
+ )
185
+ batch_op.drop_column("resource_id")
186
+
187
+ with op.batch_alter_table("projectv2", schema=None) as batch_op:
188
+ batch_op.drop_constraint(
189
+ batch_op.f("fk_projectv2_resource_id_resource"), type_="foreignkey"
190
+ )
191
+ batch_op.drop_column("resource_id")
192
+
193
+ op.drop_table("profile")
194
+ op.drop_table("resource")
195
+ # ### end Alembic commands ###
@@ -0,0 +1,2 @@
1
+ from ._local import JobRunnerConfigLocal # noqa F401
2
+ from ._slurm import JobRunnerConfigSLURM # noqa F401
@@ -0,0 +1,21 @@
1
+ from pydantic import BaseModel
2
+ from pydantic import ConfigDict
3
+
4
+
5
+ class JobRunnerConfigLocal(BaseModel):
6
+ """
7
+ Specifications of the local-backend configuration
8
+
9
+ Attributes:
10
+ parallel_tasks_per_job:
11
+ Maximum number of tasks to be run in parallel as part of a call to
12
+ `FractalThreadPoolExecutor.map`; if `None`, then all tasks will
13
+ start at the same time.
14
+ """
15
+
16
+ model_config = ConfigDict(extra="forbid")
17
+ parallel_tasks_per_job: int | None = None
18
+
19
+ @property
20
+ def batch_size(self) -> int:
21
+ return self.parallel_tasks_per_job or 1