fractal-server 2.5.2__tar.gz → 2.6.0a1__tar.gz

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 (222) hide show
  1. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/PKG-INFO +1 -1
  2. fractal_server-2.6.0a1/fractal_server/__init__.py +1 -0
  3. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/__main__.py +24 -9
  4. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/__init__.py +1 -0
  5. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/security.py +8 -0
  6. fractal_server-2.6.0a1/fractal_server/app/models/user_settings.py +38 -0
  7. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/_aux_functions.py +6 -1
  8. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/project.py +11 -24
  9. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/task.py +12 -9
  10. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/_aux_functions.py +6 -1
  11. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/submit.py +19 -23
  12. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/task.py +12 -9
  13. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/task_collection.py +10 -4
  14. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/task_collection_custom.py +6 -1
  15. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/_aux_auth.py +5 -5
  16. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/current_user.py +41 -0
  17. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/users.py +42 -0
  18. fractal_server-2.6.0a1/fractal_server/app/routes/aux/validate_user_settings.py +74 -0
  19. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/__init__.py +5 -7
  20. fractal_server-2.6.0a1/fractal_server/app/schemas/__init__.py +3 -0
  21. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/user.py +1 -62
  22. fractal_server-2.6.0a1/fractal_server/app/schemas/user_settings.py +94 -0
  23. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/security/__init__.py +22 -9
  24. fractal_server-2.6.0a1/fractal_server/app/user_settings.py +42 -0
  25. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/config.py +0 -16
  26. fractal_server-2.6.0a1/fractal_server/data_migrations/2_6_0.py +49 -0
  27. fractal_server-2.6.0a1/fractal_server/data_migrations/tools.py +17 -0
  28. fractal_server-2.6.0a1/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +74 -0
  29. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/background_operations_ssh.py +14 -5
  30. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/pyproject.toml +2 -2
  31. fractal_server-2.5.2/fractal_server/__init__.py +0 -1
  32. fractal_server-2.5.2/fractal_server/app/schemas/__init__.py +0 -1
  33. fractal_server-2.5.2/fractal_server/data_migrations/2_4_0.py +0 -61
  34. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/LICENSE +0 -0
  35. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/README.md +0 -0
  36. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/alembic.ini +0 -0
  37. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/__init__.py +0 -0
  38. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/db/__init__.py +0 -0
  39. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/linkusergroup.py +0 -0
  40. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/linkuserproject.py +0 -0
  41. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/__init__.py +0 -0
  42. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/dataset.py +0 -0
  43. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/job.py +0 -0
  44. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/project.py +0 -0
  45. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/state.py +0 -0
  46. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/task.py +0 -0
  47. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v1/workflow.py +0 -0
  48. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/__init__.py +0 -0
  49. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/collection_state.py +0 -0
  50. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/dataset.py +0 -0
  51. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/job.py +0 -0
  52. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/project.py +0 -0
  53. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/task.py +0 -0
  54. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/workflow.py +0 -0
  55. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/models/v2/workflowtask.py +0 -0
  56. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/__init__.py +0 -0
  57. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/admin/__init__.py +0 -0
  58. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/admin/v1.py +0 -0
  59. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/admin/v2.py +0 -0
  60. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/__init__.py +0 -0
  61. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/__init__.py +0 -0
  62. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/dataset.py +0 -0
  63. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/job.py +0 -0
  64. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/task_collection.py +0 -0
  65. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/workflow.py +0 -0
  66. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v1/workflowtask.py +0 -0
  67. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/__init__.py +0 -0
  68. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/dataset.py +0 -0
  69. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/images.py +0 -0
  70. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/job.py +0 -0
  71. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/project.py +0 -0
  72. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/status.py +0 -0
  73. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/workflow.py +0 -0
  74. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/api/v2/workflowtask.py +0 -0
  75. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/__init__.py +0 -0
  76. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/group.py +0 -0
  77. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/group_names.py +0 -0
  78. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/login.py +0 -0
  79. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/oauth.py +0 -0
  80. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/register.py +0 -0
  81. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/auth/router.py +0 -0
  82. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/aux/__init__.py +0 -0
  83. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/aux/_job.py +0 -0
  84. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/routes/aux/_runner.py +0 -0
  85. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/.gitignore +0 -0
  86. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/__init__.py +0 -0
  87. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/async_wrap.py +0 -0
  88. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/components.py +0 -0
  89. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/compress_folder.py +0 -0
  90. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/exceptions.py +0 -0
  91. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/__init__.py +0 -0
  92. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/__init__.py +0 -0
  93. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/_batching.py +0 -0
  94. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/_slurm_config.py +0 -0
  95. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/remote.py +0 -0
  96. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/ssh/__init__.py +0 -0
  97. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +0 -0
  98. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +0 -0
  99. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/ssh/executor.py +0 -0
  100. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/sudo/__init__.py +0 -0
  101. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +0 -0
  102. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +0 -0
  103. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/sudo/_subprocess_run_as_user.py +0 -0
  104. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/executors/slurm/sudo/executor.py +0 -0
  105. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/extract_archive.py +0 -0
  106. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/filenames.py +0 -0
  107. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/run_subprocess.py +0 -0
  108. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/set_start_and_last_task_index.py +0 -0
  109. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/shutdown.py +0 -0
  110. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/task_files.py +0 -0
  111. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/__init__.py +0 -0
  112. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_common.py +0 -0
  113. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_local/__init__.py +0 -0
  114. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_local/_local_config.py +0 -0
  115. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_local/_submit_setup.py +0 -0
  116. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_local/executor.py +0 -0
  117. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_slurm/__init__.py +0 -0
  118. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_slurm/_submit_setup.py +0 -0
  119. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/_slurm/get_slurm_config.py +0 -0
  120. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/common.py +0 -0
  121. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v1/handle_failed_job.py +0 -0
  122. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local/__init__.py +0 -0
  123. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local/_local_config.py +0 -0
  124. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local/_submit_setup.py +0 -0
  125. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local/executor.py +0 -0
  126. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local_experimental/__init__.py +0 -0
  127. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local_experimental/_local_config.py +0 -0
  128. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +0 -0
  129. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_local_experimental/executor.py +0 -0
  130. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_slurm_common/__init__.py +0 -0
  131. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_slurm_common/get_slurm_config.py +0 -0
  132. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_slurm_ssh/__init__.py +0 -0
  133. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +0 -0
  134. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_slurm_sudo/__init__.py +0 -0
  135. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py +0 -0
  136. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/deduplicate_list.py +0 -0
  137. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/handle_failed_job.py +0 -0
  138. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/merge_outputs.py +0 -0
  139. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/runner.py +0 -0
  140. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/runner_functions.py +0 -0
  141. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/runner_functions_low_level.py +0 -0
  142. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/v2/task_interface.py +0 -0
  143. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/runner/versions.py +0 -0
  144. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/_validators.py +0 -0
  145. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/user_group.py +0 -0
  146. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/__init__.py +0 -0
  147. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/applyworkflow.py +0 -0
  148. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/dataset.py +0 -0
  149. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/dumps.py +0 -0
  150. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/manifest.py +0 -0
  151. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/project.py +0 -0
  152. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/state.py +0 -0
  153. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/task.py +0 -0
  154. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/task_collection.py +0 -0
  155. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v1/workflow.py +0 -0
  156. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/__init__.py +0 -0
  157. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/dataset.py +0 -0
  158. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/dumps.py +0 -0
  159. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/job.py +0 -0
  160. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/manifest.py +0 -0
  161. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/project.py +0 -0
  162. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/status.py +0 -0
  163. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/task.py +0 -0
  164. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  165. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/workflow.py +0 -0
  166. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  167. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/data_migrations/README.md +0 -0
  168. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/gunicorn_fractal.py +0 -0
  169. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/images/__init__.py +0 -0
  170. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/images/models.py +0 -0
  171. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/images/tools.py +0 -0
  172. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/logger.py +0 -0
  173. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/main.py +0 -0
  174. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/README +0 -0
  175. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/env.py +0 -0
  176. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/naming_convention.py +0 -0
  177. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/script.py.mako +0 -0
  178. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  179. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  180. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  181. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  182. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  183. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  184. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  185. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  186. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  187. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  188. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  189. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  190. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  191. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  192. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  193. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  194. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  195. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  196. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/py.typed +0 -0
  197. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/ssh/__init__.py +0 -0
  198. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/ssh/_fabric.py +0 -0
  199. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/string_tools.py +0 -0
  200. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/syringe.py +0 -0
  201. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/__init__.py +0 -0
  202. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/utils.py +0 -0
  203. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v1/_TaskCollectPip.py +0 -0
  204. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v1/__init__.py +0 -0
  205. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v1/background_operations.py +0 -0
  206. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v1/endpoint_operations.py +0 -0
  207. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v1/get_collection_data.py +0 -0
  208. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v1/utils.py +0 -0
  209. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/_TaskCollectPip.py +0 -0
  210. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/__init__.py +0 -0
  211. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/_venv_pip.py +0 -0
  212. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/background_operations.py +0 -0
  213. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/endpoint_operations.py +0 -0
  214. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/templates/_1_create_venv.sh +0 -0
  215. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/templates/_2_upgrade_pip.sh +0 -0
  216. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/templates/_3_pip_install.sh +0 -0
  217. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/templates/_4_pip_freeze.sh +0 -0
  218. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/templates/_5_pip_show.sh +0 -0
  219. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/tasks/v2/utils.py +0 -0
  220. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/urls.py +0 -0
  221. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/utils.py +0 -0
  222. {fractal_server-2.5.2 → fractal_server-2.6.0a1}/fractal_server/zip_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 2.5.2
3
+ Version: 2.6.0a1
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
@@ -0,0 +1 @@
1
+ __VERSION__ = "2.6.0a1"
@@ -43,9 +43,17 @@ openapi_parser.add_argument(
43
43
  )
44
44
 
45
45
  # fractalctl set-db
46
- subparsers.add_parser(
46
+ set_db_parser = subparsers.add_parser(
47
47
  "set-db",
48
- description="Initialise the database and apply schema migrations",
48
+ description=(
49
+ "Initialise/upgrade database schemas and create first group&user."
50
+ ),
51
+ )
52
+ set_db_parser.add_argument(
53
+ "--skip-init-data",
54
+ action="store_true",
55
+ help="If set, do not try creating first group and user.",
56
+ default=False,
49
57
  )
50
58
 
51
59
  # fractalctl update-db-data
@@ -66,27 +74,34 @@ def save_openapi(dest="openapi.json"):
66
74
  json.dump(openapi_schema, f)
67
75
 
68
76
 
69
- def set_db():
77
+ def set_db(skip_init_data: bool = False):
70
78
  """
71
- Set-up / Upgrade database schema
79
+ Upgrade database schema *and* create first group/user
72
80
 
73
81
  Call alembic to upgrade to the latest migration.
74
-
75
82
  Ref: https://stackoverflow.com/a/56683030/283972
83
+
84
+ Arguments:
85
+ skip_init_data: If `True`, skip creation of first group and user.
76
86
  """
77
- import alembic.config
78
- from pathlib import Path
79
- import fractal_server
80
87
  from fractal_server.app.security import _create_first_user
81
88
  from fractal_server.app.security import _create_first_group
82
89
  from fractal_server.syringe import Inject
83
90
  from fractal_server.config import get_settings
84
91
 
92
+ import alembic.config
93
+ from pathlib import Path
94
+ import fractal_server
95
+
85
96
  alembic_ini = Path(fractal_server.__file__).parent / "alembic.ini"
86
97
  alembic_args = ["-c", alembic_ini.as_posix(), "upgrade", "head"]
87
98
  print(f"START: Run alembic.config, with argv={alembic_args}")
88
99
  alembic.config.main(argv=alembic_args)
89
100
  print("END: alembic.config")
101
+
102
+ if skip_init_data:
103
+ return
104
+
90
105
  # Insert default group
91
106
  print()
92
107
  _create_first_group()
@@ -179,7 +194,7 @@ def run():
179
194
  if args.cmd == "openapi":
180
195
  save_openapi(dest=args.openapi_file)
181
196
  elif args.cmd == "set-db":
182
- set_db()
197
+ set_db(skip_init_data=args.skip_init_data)
183
198
  elif args.cmd == "update-db-data":
184
199
  update_db_data()
185
200
  elif args.cmd == "start":
@@ -7,5 +7,6 @@ from .linkusergroup import LinkUserGroup # noqa: F401
7
7
  from .linkuserproject import LinkUserProject # noqa: F401
8
8
  from .linkuserproject import LinkUserProjectV2 # noqa: F401
9
9
  from .security import * # noqa
10
+ from .user_settings import UserSettings # noqa
10
11
  from .v1 import * # noqa
11
12
  from .v2 import * # noqa
@@ -20,6 +20,7 @@ from sqlmodel import Field
20
20
  from sqlmodel import Relationship
21
21
  from sqlmodel import SQLModel
22
22
 
23
+ from .user_settings import UserSettings
23
24
  from fractal_server.utils import get_timestamp
24
25
 
25
26
 
@@ -104,6 +105,13 @@ class UserOAuth(SQLModel, table=True):
104
105
  sa_relationship_kwargs={"lazy": "joined", "cascade": "all, delete"},
105
106
  )
106
107
 
108
+ user_settings_id: Optional[int] = Field(
109
+ foreign_key="user_settings.id", default=None
110
+ )
111
+ settings: Optional[UserSettings] = Relationship(
112
+ sa_relationship_kwargs=dict(lazy="selectin", cascade="all, delete")
113
+ )
114
+
107
115
  class Config:
108
116
  orm_mode = True
109
117
 
@@ -0,0 +1,38 @@
1
+ from typing import Optional
2
+
3
+ from sqlalchemy import Column
4
+ from sqlalchemy.types import JSON
5
+ from sqlmodel import Field
6
+ from sqlmodel import SQLModel
7
+
8
+
9
+ class UserSettings(SQLModel, table=True):
10
+ """
11
+ Comprehensive list of user settings.
12
+
13
+ Attributes:
14
+ id: ID of database object
15
+ slurm_accounts:
16
+ List of SLURM accounts, to be used upon Fractal job submission.
17
+ ssh_host: SSH-reachable host where a SLURM client is available.
18
+ ssh_username: User on `ssh_host`.
19
+ ssh_private_key_path: Path of private SSH key for `ssh_username`.
20
+ ssh_tasks_dir: Task-venvs base folder on `ssh_host`.
21
+ ssh_jobs_dir: Jobs base folder on `ssh_host`.
22
+ slurm_user: Local user, to be impersonated via `sudo -u`
23
+ cache_dir: Folder where `slurm_user` can write.
24
+ """
25
+
26
+ __tablename__ = "user_settings"
27
+
28
+ id: Optional[int] = Field(default=None, primary_key=True)
29
+ slurm_accounts: list[str] = Field(
30
+ sa_column=Column(JSON, server_default="[]", nullable=False)
31
+ )
32
+ ssh_host: Optional[str] = None
33
+ ssh_username: Optional[str] = None
34
+ ssh_private_key_path: Optional[str] = None
35
+ ssh_tasks_dir: Optional[str] = None
36
+ ssh_jobs_dir: Optional[str] = None
37
+ slurm_user: Optional[str] = None
38
+ cache_dir: Optional[str] = None
@@ -22,6 +22,7 @@ from ....models.v1 import Task
22
22
  from ....models.v1 import Workflow
23
23
  from ....models.v1 import WorkflowTask
24
24
  from ....schemas.v1 import JobStatusTypeV1
25
+ from ...aux.validate_user_settings import verify_user_has_settings
25
26
  from fractal_server.app.models import UserOAuth
26
27
 
27
28
 
@@ -367,7 +368,11 @@ async def _get_task_check_owner(
367
368
  ),
368
369
  )
369
370
  else:
370
- owner = user.username or user.slurm_user
371
+ if user.username:
372
+ owner = user.username
373
+ else:
374
+ verify_user_has_settings(user)
375
+ owner = user.settings.slurm_user
371
376
  if owner != task.owner:
372
377
  raise HTTPException(
373
378
  status_code=status.HTTP_403_FORBIDDEN,
@@ -34,6 +34,7 @@ from ....schemas.v1 import JobStatusTypeV1
34
34
  from ....schemas.v1 import ProjectCreateV1
35
35
  from ....schemas.v1 import ProjectReadV1
36
36
  from ....schemas.v1 import ProjectUpdateV1
37
+ from ...aux.validate_user_settings import validate_user_settings
37
38
  from ._aux_functions import _check_project_exists
38
39
  from ._aux_functions import _get_dataset_check_owner
39
40
  from ._aux_functions import _get_project_check_owner
@@ -321,25 +322,11 @@ async def apply_workflow(
321
322
  ),
322
323
  )
323
324
 
324
- # If backend is SLURM, check that the user has required attributes
325
- backend = settings.FRACTAL_RUNNER_BACKEND
326
- if backend == "slurm":
327
- if not user.slurm_user:
328
- raise HTTPException(
329
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
330
- detail=(
331
- f"FRACTAL_RUNNER_BACKEND={backend}, "
332
- f"but {user.slurm_user=}."
333
- ),
334
- )
335
- if not user.cache_dir:
336
- raise HTTPException(
337
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
338
- detail=(
339
- f"FRACTAL_RUNNER_BACKEND={backend}, "
340
- f"but {user.cache_dir=}."
341
- ),
342
- )
325
+ # Validate user settings
326
+ FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
327
+ user_settings = await validate_user_settings(
328
+ user=user, backend=FRACTAL_RUNNER_BACKEND, db=db
329
+ )
343
330
 
344
331
  # Check that datasets have the right number of resources
345
332
  if not input_dataset.resource_list:
@@ -386,7 +373,7 @@ async def apply_workflow(
386
373
  )
387
374
 
388
375
  if apply_workflow.slurm_account is not None:
389
- if apply_workflow.slurm_account not in user.slurm_accounts:
376
+ if apply_workflow.slurm_account not in user_settings.slurm_accounts:
390
377
  raise HTTPException(
391
378
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
392
379
  detail=(
@@ -395,8 +382,8 @@ async def apply_workflow(
395
382
  ),
396
383
  )
397
384
  else:
398
- if len(user.slurm_accounts) > 0:
399
- apply_workflow.slurm_account = user.slurm_accounts[0]
385
+ if len(user_settings.slurm_accounts) > 0:
386
+ apply_workflow.slurm_account = user_settings.slurm_accounts[0]
400
387
 
401
388
  # Add new ApplyWorkflow object to DB
402
389
  job = ApplyWorkflow(
@@ -480,8 +467,8 @@ async def apply_workflow(
480
467
  output_dataset_id=output_dataset.id,
481
468
  job_id=job.id,
482
469
  worker_init=apply_workflow.worker_init,
483
- slurm_user=user.slurm_user,
484
- user_cache_dir=user.cache_dir,
470
+ slurm_user=user_settings.slurm_user,
471
+ user_cache_dir=user_settings.cache_dir,
485
472
  )
486
473
  request.app.state.jobsV1.append(job.id)
487
474
  logger.info(
@@ -17,6 +17,7 @@ from ....models.v2 import TaskV2
17
17
  from ....schemas.v1 import TaskCreateV1
18
18
  from ....schemas.v1 import TaskReadV1
19
19
  from ....schemas.v1 import TaskUpdateV1
20
+ from ...aux.validate_user_settings import verify_user_has_settings
20
21
  from ._aux_functions import _get_task_check_owner
21
22
  from ._aux_functions import _raise_if_v1_is_read_only
22
23
  from fractal_server.app.models import UserOAuth
@@ -126,16 +127,18 @@ async def create_task(
126
127
  # Set task.owner attribute
127
128
  if user.username:
128
129
  owner = user.username
129
- elif user.slurm_user:
130
- owner = user.slurm_user
131
130
  else:
132
- raise HTTPException(
133
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
134
- detail=(
135
- "Cannot add a new task because current user does not "
136
- "have `username` or `slurm_user` attributes."
137
- ),
138
- )
131
+ verify_user_has_settings(user)
132
+ if user.settings.slurm_user:
133
+ owner = user.settings.slurm_user
134
+ else:
135
+ raise HTTPException(
136
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
137
+ detail=(
138
+ "Cannot add a new task because current user does not "
139
+ "have `username` or `slurm_user` attributes."
140
+ ),
141
+ )
139
142
 
140
143
  # Prepend owner to task.source
141
144
  task.source = f"{owner}:{task.source}"
@@ -21,6 +21,7 @@ from ....models.v2 import TaskV2
21
21
  from ....models.v2 import WorkflowTaskV2
22
22
  from ....models.v2 import WorkflowV2
23
23
  from ....schemas.v2 import JobStatusTypeV2
24
+ from ...aux.validate_user_settings import verify_user_has_settings
24
25
  from fractal_server.app.models import UserOAuth
25
26
  from fractal_server.images import Filters
26
27
 
@@ -362,7 +363,11 @@ async def _get_task_check_owner(
362
363
  ),
363
364
  )
364
365
  else:
365
- owner = user.username or user.slurm_user
366
+ if user.username:
367
+ owner = user.username
368
+ else:
369
+ verify_user_has_settings(user)
370
+ owner = user.settings.slurm_user
366
371
  if owner != task.owner:
367
372
  raise HTTPException(
368
373
  status_code=status.HTTP_403_FORBIDDEN,
@@ -27,6 +27,7 @@ from ....runner.v2 import submit_workflow
27
27
  from ....schemas.v2 import JobCreateV2
28
28
  from ....schemas.v2 import JobReadV2
29
29
  from ....schemas.v2 import JobStatusTypeV2
30
+ from ...aux.validate_user_settings import validate_user_settings
30
31
  from ._aux_functions import _get_dataset_check_owner
31
32
  from ._aux_functions import _get_workflow_check_owner
32
33
  from ._aux_functions import clean_app_job_list_v2
@@ -109,19 +110,11 @@ async def apply_workflow(
109
110
  ),
110
111
  )
111
112
 
112
- # If backend is SLURM, check that the user has required attributes
113
+ # Validate user settings
113
114
  FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
114
- if FRACTAL_RUNNER_BACKEND == "slurm":
115
- if not user.slurm_user:
116
- raise HTTPException(
117
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
118
- detail=f"{FRACTAL_RUNNER_BACKEND=}, but {user.slurm_user=}.",
119
- )
120
- if not user.cache_dir:
121
- raise HTTPException(
122
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
123
- detail=f"{FRACTAL_RUNNER_BACKEND=}, but {user.cache_dir=}.",
124
- )
115
+ user_settings = await validate_user_settings(
116
+ user=user, backend=FRACTAL_RUNNER_BACKEND, db=db
117
+ )
125
118
 
126
119
  # Check that no other job with the same dataset_id is SUBMITTED
127
120
  stm = (
@@ -140,7 +133,7 @@ async def apply_workflow(
140
133
  )
141
134
 
142
135
  if job_create.slurm_account is not None:
143
- if job_create.slurm_account not in user.slurm_accounts:
136
+ if job_create.slurm_account not in user_settings.slurm_accounts:
144
137
  raise HTTPException(
145
138
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
146
139
  detail=(
@@ -149,8 +142,8 @@ async def apply_workflow(
149
142
  ),
150
143
  )
151
144
  else:
152
- if len(user.slurm_accounts) > 0:
153
- job_create.slurm_account = user.slurm_accounts[0]
145
+ if len(user_settings.slurm_accounts) > 0:
146
+ job_create.slurm_account = user_settings.slurm_accounts[0]
154
147
 
155
148
  # Add new Job object to DB
156
149
  job = JobV2(
@@ -224,12 +217,11 @@ async def apply_workflow(
224
217
  WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
225
218
  elif FRACTAL_RUNNER_BACKEND == "slurm":
226
219
  WORKFLOW_DIR_REMOTE = (
227
- Path(user.cache_dir) / f"{WORKFLOW_DIR_LOCAL.name}"
220
+ Path(user_settings.cache_dir) / f"{WORKFLOW_DIR_LOCAL.name}"
228
221
  )
229
222
  elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
230
223
  WORKFLOW_DIR_REMOTE = (
231
- Path(settings.FRACTAL_SLURM_SSH_WORKING_BASE_DIR)
232
- / f"{WORKFLOW_DIR_LOCAL.name}"
224
+ Path(user_settings.ssh_jobs_dir) / f"{WORKFLOW_DIR_LOCAL.name}"
233
225
  )
234
226
 
235
227
  # Update job folders in the db
@@ -241,23 +233,27 @@ async def apply_workflow(
241
233
  # User appropriate FractalSSH object
242
234
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
243
235
  ssh_credentials = dict(
244
- user=settings.FRACTAL_SLURM_SSH_USER,
245
- host=settings.FRACTAL_SLURM_SSH_HOST,
246
- key_path=settings.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH,
236
+ user=user_settings.ssh_username,
237
+ host=user_settings.ssh_host,
238
+ key_path=user_settings.ssh_private_key_path,
247
239
  )
248
240
  fractal_ssh_list = request.app.state.fractal_ssh_list
249
241
  fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
250
242
  else:
251
243
  fractal_ssh = None
252
244
 
245
+ # Expunge user settings from db, to use in background task
246
+ db.expunge(user_settings)
247
+
253
248
  background_tasks.add_task(
254
249
  submit_workflow,
255
250
  workflow_id=workflow.id,
256
251
  dataset_id=dataset.id,
257
252
  job_id=job.id,
253
+ user_settings=user_settings,
258
254
  worker_init=job.worker_init,
259
- slurm_user=user.slurm_user,
260
- user_cache_dir=user.cache_dir,
255
+ slurm_user=user_settings.slurm_user,
256
+ user_cache_dir=user_settings.cache_dir,
261
257
  fractal_ssh=fractal_ssh,
262
258
  )
263
259
  request.app.state.jobsV2.append(job.id)
@@ -18,6 +18,7 @@ from ....models.v2 import WorkflowV2
18
18
  from ....schemas.v2 import TaskCreateV2
19
19
  from ....schemas.v2 import TaskReadV2
20
20
  from ....schemas.v2 import TaskUpdateV2
21
+ from ...aux.validate_user_settings import verify_user_has_settings
21
22
  from ._aux_functions import _get_task_check_owner
22
23
  from fractal_server.app.models import UserOAuth
23
24
  from fractal_server.app.routes.auth import current_active_user
@@ -150,16 +151,18 @@ async def create_task(
150
151
  # Set task.owner attribute
151
152
  if user.username:
152
153
  owner = user.username
153
- elif user.slurm_user:
154
- owner = user.slurm_user
155
154
  else:
156
- raise HTTPException(
157
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
158
- detail=(
159
- "Cannot add a new task because current user does not "
160
- "have `username` or `slurm_user` attributes."
161
- ),
162
- )
155
+ verify_user_has_settings(user)
156
+ if user.settings.slurm_user:
157
+ owner = user.settings.slurm_user
158
+ else:
159
+ raise HTTPException(
160
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
161
+ detail=(
162
+ "Cannot add a new task because current user does not "
163
+ "have `username` or `slurm_user` attributes."
164
+ ),
165
+ )
163
166
 
164
167
  # Prepend owner to task.source
165
168
  task.source = f"{owner}:{task.source}"
@@ -25,6 +25,7 @@ from ....schemas.v2 import CollectionStateReadV2
25
25
  from ....schemas.v2 import CollectionStatusV2
26
26
  from ....schemas.v2 import TaskCollectPipV2
27
27
  from ....schemas.v2 import TaskReadV2
28
+ from ...aux.validate_user_settings import validate_user_settings
28
29
  from fractal_server.app.models import UserOAuth
29
30
  from fractal_server.app.routes.auth import current_active_user
30
31
  from fractal_server.app.routes.auth import current_active_verified_user
@@ -41,7 +42,6 @@ from fractal_server.tasks.v2.endpoint_operations import download_package
41
42
  from fractal_server.tasks.v2.endpoint_operations import inspect_package
42
43
  from fractal_server.tasks.v2.utils import get_python_interpreter_v2
43
44
 
44
-
45
45
  router = APIRouter()
46
46
 
47
47
  logger = set_logger(__name__)
@@ -107,6 +107,11 @@ async def collect_tasks_pip(
107
107
  detail=f"Invalid task-collection object. Original error: {e}",
108
108
  )
109
109
 
110
+ # Validate user settings (backend-specific)
111
+ user_settings = await validate_user_settings(
112
+ user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
113
+ )
114
+
110
115
  # END of SSH/non-SSH common part
111
116
 
112
117
  if settings.FRACTAL_RUNNER_BACKEND == "slurm_ssh":
@@ -126,9 +131,9 @@ async def collect_tasks_pip(
126
131
 
127
132
  # User appropriate FractalSSH object
128
133
  ssh_credentials = dict(
129
- user=settings.FRACTAL_SLURM_SSH_USER,
130
- host=settings.FRACTAL_SLURM_SSH_HOST,
131
- key_path=settings.FRACTAL_SLURM_SSH_PRIVATE_KEY_PATH,
134
+ user=user_settings.ssh_username,
135
+ host=user_settings.ssh_host,
136
+ key_path=user_settings.ssh_private_key_path,
132
137
  )
133
138
  fractal_ssh_list = request.app.state.fractal_ssh_list
134
139
  fractal_ssh = fractal_ssh_list.get(**ssh_credentials)
@@ -138,6 +143,7 @@ async def collect_tasks_pip(
138
143
  state.id,
139
144
  task_pkg,
140
145
  fractal_ssh,
146
+ user_settings.ssh_tasks_dir,
141
147
  )
142
148
 
143
149
  response.status_code = status.HTTP_201_CREATED
@@ -18,6 +18,7 @@ from ....models.v2 import TaskV2
18
18
  from ....schemas.v2 import TaskCollectCustomV2
19
19
  from ....schemas.v2 import TaskCreateV2
20
20
  from ....schemas.v2 import TaskReadV2
21
+ from ...aux.validate_user_settings import verify_user_has_settings
21
22
  from fractal_server.app.models import UserOAuth
22
23
  from fractal_server.app.routes.auth import current_active_verified_user
23
24
  from fractal_server.string_tools import validate_cmd
@@ -112,7 +113,11 @@ async def collect_task_custom(
112
113
  package_root = Path(task_collect.package_root)
113
114
 
114
115
  # Set task.owner attribute
115
- owner = user.username or user.slurm_user
116
+ if user.username:
117
+ owner = user.username
118
+ else:
119
+ verify_user_has_settings(user)
120
+ owner = user.settings.slurm_user
116
121
  if owner is None:
117
122
  raise HTTPException(
118
123
  status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
@@ -3,11 +3,11 @@ from fastapi import status
3
3
  from sqlalchemy.ext.asyncio import AsyncSession
4
4
  from sqlmodel import select
5
5
 
6
- from ...models.linkusergroup import LinkUserGroup
7
- from ...models.security import UserGroup
8
- from ...models.security import UserOAuth
9
- from ...schemas.user import UserRead
10
- from ...schemas.user_group import UserGroupRead
6
+ from fractal_server.app.models.linkusergroup import LinkUserGroup
7
+ from fractal_server.app.models.security import UserGroup
8
+ from fractal_server.app.models.security import UserOAuth
9
+ from fractal_server.app.schemas.user import UserRead
10
+ from fractal_server.app.schemas.user_group import UserGroupRead
11
11
 
12
12
 
13
13
  async def _get_single_user_with_group_names(
@@ -11,8 +11,12 @@ from ...db import get_async_db
11
11
  from ...schemas.user import UserRead
12
12
  from ...schemas.user import UserUpdate
13
13
  from ...schemas.user import UserUpdateStrict
14
+ from ..aux.validate_user_settings import verify_user_has_settings
14
15
  from ._aux_auth import _get_single_user_with_group_names
15
16
  from fractal_server.app.models import UserOAuth
17
+ from fractal_server.app.models import UserSettings
18
+ from fractal_server.app.schemas import UserSettingsReadStrict
19
+ from fractal_server.app.schemas import UserSettingsUpdateStrict
16
20
  from fractal_server.app.security import get_user_manager
17
21
  from fractal_server.app.security import UserManager
18
22
 
@@ -62,3 +66,40 @@ async def patch_current_user(
62
66
  patched_user, db
63
67
  )
64
68
  return patched_user_with_groups
69
+
70
+
71
+ @router_current_user.get(
72
+ "/current-user/settings/", response_model=UserSettingsReadStrict
73
+ )
74
+ async def get_current_user_settings(
75
+ current_user: UserOAuth = Depends(current_active_user),
76
+ db: AsyncSession = Depends(get_async_db),
77
+ ) -> UserSettingsReadStrict:
78
+
79
+ verify_user_has_settings(current_user)
80
+ user_settings = await db.get(UserSettings, current_user.user_settings_id)
81
+ return user_settings
82
+
83
+
84
+ @router_current_user.patch(
85
+ "/current-user/settings/", response_model=UserSettingsReadStrict
86
+ )
87
+ async def patch_current_user_settings(
88
+ settings_update: UserSettingsUpdateStrict,
89
+ current_user: UserOAuth = Depends(current_active_user),
90
+ db: AsyncSession = Depends(get_async_db),
91
+ ) -> UserSettingsReadStrict:
92
+
93
+ verify_user_has_settings(current_user)
94
+ current_user_settings = await db.get(
95
+ UserSettings, current_user.user_settings_id
96
+ )
97
+
98
+ for k, v in settings_update.dict(exclude_unset=True).items():
99
+ setattr(current_user_settings, k, v)
100
+
101
+ db.add(current_user_settings)
102
+ await db.commit()
103
+ await db.refresh(current_user_settings)
104
+
105
+ return current_user_settings
@@ -19,11 +19,15 @@ from ...db import get_async_db
19
19
  from ...schemas.user import UserRead
20
20
  from ...schemas.user import UserUpdate
21
21
  from ...schemas.user import UserUpdateWithNewGroupIds
22
+ from ..aux.validate_user_settings import verify_user_has_settings
22
23
  from ._aux_auth import _get_single_user_with_group_ids
23
24
  from fractal_server.app.models import LinkUserGroup
24
25
  from fractal_server.app.models import UserGroup
25
26
  from fractal_server.app.models import UserOAuth
27
+ from fractal_server.app.models import UserSettings
26
28
  from fractal_server.app.routes.auth._aux_auth import _user_or_404
29
+ from fractal_server.app.schemas import UserSettingsRead
30
+ from fractal_server.app.schemas import UserSettingsUpdate
27
31
  from fractal_server.app.security import get_user_manager
28
32
  from fractal_server.app.security import UserManager
29
33
  from fractal_server.logger import set_logger
@@ -196,3 +200,41 @@ async def list_users(
196
200
  )
197
201
 
198
202
  return user_list
203
+
204
+
205
+ @router_users.get(
206
+ "/users/{user_id}/settings/", response_model=UserSettingsRead
207
+ )
208
+ async def get_user_settings(
209
+ user_id: int,
210
+ superuser: UserOAuth = Depends(current_active_superuser),
211
+ db: AsyncSession = Depends(get_async_db),
212
+ ) -> UserSettingsRead:
213
+
214
+ user = await _user_or_404(user_id=user_id, db=db)
215
+ verify_user_has_settings(user)
216
+ user_settings = await db.get(UserSettings, user.user_settings_id)
217
+ return user_settings
218
+
219
+
220
+ @router_users.patch(
221
+ "/users/{user_id}/settings/", response_model=UserSettingsRead
222
+ )
223
+ async def patch_user_settings(
224
+ user_id: int,
225
+ settings_update: UserSettingsUpdate,
226
+ superuser: UserOAuth = Depends(current_active_superuser),
227
+ db: AsyncSession = Depends(get_async_db),
228
+ ) -> UserSettingsRead:
229
+ user = await _user_or_404(user_id=user_id, db=db)
230
+ verify_user_has_settings(user)
231
+ user_settings = await db.get(UserSettings, user.user_settings_id)
232
+
233
+ for k, v in settings_update.dict(exclude_unset=True).items():
234
+ setattr(user_settings, k, v)
235
+
236
+ db.add(user_settings)
237
+ await db.commit()
238
+ await db.refresh(user_settings)
239
+
240
+ return user_settings