fractal-server 2.17.2__tar.gz → 2.18.0a0__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 (271) hide show
  1. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/PKG-INFO +3 -2
  2. fractal_server-2.18.0a0/fractal_server/__init__.py +1 -0
  3. fractal_server-2.18.0a0/fractal_server/app/models/linkuserproject.py +53 -0
  4. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/__init__.py +2 -0
  5. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/job.py +17 -6
  6. fractal_server-2.18.0a0/fractal_server/app/routes/admin/v2/sharing.py +103 -0
  7. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/task.py +1 -0
  8. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/__init__.py +2 -0
  9. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/_aux_functions.py +43 -17
  10. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/_aux_functions_history.py +8 -3
  11. fractal_server-2.18.0a0/fractal_server/app/routes/api/v2/_aux_functions_sharing.py +97 -0
  12. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/dataset.py +23 -17
  13. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/history.py +21 -11
  14. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/images.py +22 -8
  15. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/job.py +28 -12
  16. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/pre_submission_checks.py +13 -6
  17. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/project.py +37 -14
  18. fractal_server-2.18.0a0/fractal_server/app/routes/api/v2/sharing.py +312 -0
  19. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/status_legacy.py +7 -4
  20. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/submit.py +11 -5
  21. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task_version_update.py +7 -4
  22. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/workflow.py +23 -11
  23. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/workflow_import.py +14 -12
  24. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/workflowtask.py +41 -7
  25. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/__init__.py +7 -0
  26. fractal_server-2.18.0a0/fractal_server/app/schemas/v2/sharing.py +99 -0
  27. fractal_server-2.18.0a0/fractal_server/migrations/versions/bc0e8b3327a7_project_sharing.py +72 -0
  28. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/_batching.py +4 -10
  29. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_ssh/runner.py +1 -1
  30. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_sudo/runner.py +1 -1
  31. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/pyproject.toml +3 -3
  32. fractal_server-2.17.2/fractal_server/__init__.py +0 -1
  33. fractal_server-2.17.2/fractal_server/app/models/linkuserproject.py +0 -13
  34. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/LICENSE +0 -0
  35. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/README.md +0 -0
  36. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/__main__.py +0 -0
  37. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/alembic.ini +0 -0
  38. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/__init__.py +0 -0
  39. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/db/__init__.py +0 -0
  40. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/__init__.py +0 -0
  41. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/linkusergroup.py +0 -0
  42. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/security.py +0 -0
  43. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/__init__.py +0 -0
  44. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/accounting.py +0 -0
  45. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/dataset.py +0 -0
  46. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/history.py +0 -0
  47. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/job.py +0 -0
  48. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/profile.py +0 -0
  49. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/project.py +0 -0
  50. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/resource.py +0 -0
  51. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/task.py +0 -0
  52. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/task_group.py +0 -0
  53. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/workflow.py +0 -0
  54. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/models/v2/workflowtask.py +0 -0
  55. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/__init__.py +0 -0
  56. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/__init__.py +0 -0
  57. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/_aux_functions.py +0 -0
  58. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/accounting.py +0 -0
  59. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/impersonate.py +0 -0
  60. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/profile.py +0 -0
  61. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/resource.py +0 -0
  62. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/task_group.py +0 -0
  63. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/admin/v2/task_group_lifecycle.py +0 -0
  64. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/__init__.py +0 -0
  65. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +0 -0
  66. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/_aux_functions_task_version_update.py +0 -0
  67. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/_aux_functions_tasks.py +0 -0
  68. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/_aux_task_group_disambiguation.py +0 -0
  69. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task.py +0 -0
  70. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task_collection.py +0 -0
  71. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task_collection_custom.py +0 -0
  72. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task_collection_pixi.py +0 -0
  73. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task_group.py +0 -0
  74. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/api/v2/task_group_lifecycle.py +0 -0
  75. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/__init__.py +0 -0
  76. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/_aux_auth.py +0 -0
  77. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/current_user.py +0 -0
  78. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/group.py +0 -0
  79. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/login.py +0 -0
  80. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/oauth.py +0 -0
  81. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/register.py +0 -0
  82. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/router.py +0 -0
  83. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/auth/users.py +0 -0
  84. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/aux/__init__.py +0 -0
  85. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/aux/_job.py +0 -0
  86. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/aux/_runner.py +0 -0
  87. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/aux/validate_user_profile.py +0 -0
  88. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/routes/pagination.py +0 -0
  89. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/__init__.py +0 -0
  90. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/user.py +0 -0
  91. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/user_group.py +0 -0
  92. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/accounting.py +0 -0
  93. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/dataset.py +0 -0
  94. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/dumps.py +0 -0
  95. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/history.py +0 -0
  96. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/job.py +0 -0
  97. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/manifest.py +0 -0
  98. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/profile.py +0 -0
  99. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/project.py +0 -0
  100. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/resource.py +0 -0
  101. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/status_legacy.py +0 -0
  102. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/task.py +0 -0
  103. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/task_collection.py +0 -0
  104. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/task_group.py +0 -0
  105. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/workflow.py +0 -0
  106. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/schemas/v2/workflowtask.py +0 -0
  107. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/security/__init__.py +0 -0
  108. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/security/signup_email.py +0 -0
  109. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/app/shutdown.py +0 -0
  110. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/__init__.py +0 -0
  111. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/_data.py +0 -0
  112. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/_database.py +0 -0
  113. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/_email.py +0 -0
  114. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/_main.py +0 -0
  115. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/_oauth.py +0 -0
  116. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/config/_settings_config.py +0 -0
  117. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/data_migrations/README.md +0 -0
  118. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/data_migrations/tools.py +0 -0
  119. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/exceptions.py +0 -0
  120. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/gunicorn_fractal.py +0 -0
  121. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/images/__init__.py +0 -0
  122. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/images/models.py +0 -0
  123. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/images/status_tools.py +0 -0
  124. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/images/tools.py +0 -0
  125. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/logger.py +0 -0
  126. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/main.py +0 -0
  127. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/env.py +0 -0
  128. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/naming_convention.py +0 -0
  129. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/034a469ec2eb_task_groups.py +0 -0
  130. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/091b01f51f88_add_usergroup_and_linkusergroup_table.py +0 -0
  131. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/0f5f85bb2ae7_add_pre_pinned_packages.py +0 -0
  132. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/19eca0dd47a9_user_settings_project_dir.py +0 -0
  133. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/1a83a5260664_rename.py +0 -0
  134. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/1eac13a26c83_drop_v1_tables.py +0 -0
  135. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/316140ff7ee1_remove_usersettings_cache_dir.py +0 -0
  136. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/40d6d6511b20_add_index_to_history_models.py +0 -0
  137. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/45fbb391d7af_make_resource_id_fk_non_nullable.py +0 -0
  138. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +0 -0
  139. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/49d0856e9569_drop_table.py +0 -0
  140. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  141. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/4cedeb448a53_workflowtask_foreign_keys_not_nullables.py +0 -0
  142. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/501961cfcd85_remove_link_between_v1_and_v2_tasks_.py +0 -0
  143. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  144. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/5bf02391cfef_v2.py +0 -0
  145. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  146. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/71eefd1dd202_add_slurm_accounts.py +0 -0
  147. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/7673fe18c05d_remove_project_dir_server_default.py +0 -0
  148. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/791ce783d3d8_add_indices.py +0 -0
  149. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/83bc2ad3ffcc_2_17_0.py +0 -0
  150. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  151. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/8e8f227a3e36_update_taskv2_post_2_7_0.py +0 -0
  152. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  153. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/94a47ea2d3ff_remove_cache_dir_slurm_user_and_slurm_.py +0 -0
  154. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/969d84257cac_add_historyrun_task_id.py +0 -0
  155. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/97f444d47249_add_applyworkflow_project_dump.py +0 -0
  156. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/981d588fe248_add_executor_error_log.py +0 -0
  157. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  158. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/9c5ae74c9b98_add_user_settings_table.py +0 -0
  159. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +0 -0
  160. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/9fd26a2b0de4_add_workflow_timestamp_created.py +0 -0
  161. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  162. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +0 -0
  163. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/af8673379a5c_drop_old_filter_columns.py +0 -0
  164. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/b1e7f7a1ff71_task_group_for_pixi.py +0 -0
  165. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/b3ffb095f973_json_to_jsonb.py +0 -0
  166. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +0 -0
  167. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/caba9fb1ea5e_drop_useroauth_user_settings_id.py +0 -0
  168. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/d256a7379ab8_taskgroup_activity_and_venv_info_to_.py +0 -0
  169. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/d4fe3708d309_make_applyworkflow_workflow_dump_non_.py +0 -0
  170. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/da2cb2ac4255_user_group_viewer_paths.py +0 -0
  171. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/db09233ad13a_split_filters_and_keep_old_columns.py +0 -0
  172. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/e0e717ae2f26_delete_linkuserproject_ondelete_project.py +0 -0
  173. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  174. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +0 -0
  175. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/efa89c30e0a4_add_project_timestamp_created.py +0 -0
  176. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +0 -0
  177. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  178. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +0 -0
  179. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/py.typed +0 -0
  180. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/__init__.py +0 -0
  181. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/components.py +0 -0
  182. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/config/__init__.py +0 -0
  183. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/config/_local.py +0 -0
  184. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/config/_slurm.py +0 -0
  185. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/config/slurm_mem_to_MB.py +0 -0
  186. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/exceptions.py +0 -0
  187. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/__init__.py +0 -0
  188. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/base_runner.py +0 -0
  189. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/call_command_wrapper.py +0 -0
  190. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/local/__init__.py +0 -0
  191. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/local/get_local_config.py +0 -0
  192. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/local/runner.py +0 -0
  193. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/__init__.py +0 -0
  194. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/_job_states.py +0 -0
  195. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/base_slurm_runner.py +0 -0
  196. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/get_slurm_config.py +0 -0
  197. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/remote.py +0 -0
  198. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/slurm_config.py +0 -0
  199. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +0 -0
  200. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_ssh/__init__.py +0 -0
  201. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_ssh/run_subprocess.py +0 -0
  202. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_ssh/tar_commands.py +0 -0
  203. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_sudo/__init__.py +0 -0
  204. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +0 -0
  205. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/filenames.py +0 -0
  206. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/set_start_and_last_task_index.py +0 -0
  207. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/task_files.py +0 -0
  208. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/__init__.py +0 -0
  209. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/_local.py +0 -0
  210. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/_slurm_ssh.py +0 -0
  211. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/_slurm_sudo.py +0 -0
  212. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/db_tools.py +0 -0
  213. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/deduplicate_list.py +0 -0
  214. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/merge_outputs.py +0 -0
  215. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/runner.py +0 -0
  216. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/runner_functions.py +0 -0
  217. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/submit_workflow.py +0 -0
  218. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/v2/task_interface.py +0 -0
  219. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/runner/versions.py +0 -0
  220. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/ssh/__init__.py +0 -0
  221. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/ssh/_fabric.py +0 -0
  222. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/string_tools.py +0 -0
  223. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/syringe.py +0 -0
  224. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/__init__.py +0 -0
  225. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/config/__init__.py +0 -0
  226. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/config/_pixi.py +0 -0
  227. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/config/_python.py +0 -0
  228. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/utils.py +0 -0
  229. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/__init__.py +0 -0
  230. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/__init__.py +0 -0
  231. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/_utils.py +0 -0
  232. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/collect.py +0 -0
  233. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/collect_pixi.py +0 -0
  234. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/deactivate.py +0 -0
  235. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/deactivate_pixi.py +0 -0
  236. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/delete.py +0 -0
  237. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/reactivate.py +0 -0
  238. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/local/reactivate_pixi.py +0 -0
  239. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/__init__.py +0 -0
  240. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +0 -0
  241. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/_utils.py +0 -0
  242. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/collect.py +0 -0
  243. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/collect_pixi.py +0 -0
  244. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/deactivate.py +0 -0
  245. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/deactivate_pixi.py +0 -0
  246. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/delete.py +0 -0
  247. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/reactivate.py +0 -0
  248. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/ssh/reactivate_pixi.py +0 -0
  249. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/1_create_venv.sh +0 -0
  250. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/2_pip_install.sh +0 -0
  251. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/3_pip_freeze.sh +0 -0
  252. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/4_pip_show.sh +0 -0
  253. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/5_get_venv_size_and_file_number.sh +0 -0
  254. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/6_pip_install_from_freeze.sh +0 -0
  255. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/pixi_1_extract.sh +0 -0
  256. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/pixi_2_install.sh +0 -0
  257. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/templates/pixi_3_post_install.sh +0 -0
  258. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/utils_background.py +0 -0
  259. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/utils_database.py +0 -0
  260. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/utils_package_names.py +0 -0
  261. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/utils_pixi.py +0 -0
  262. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/utils_python_interpreter.py +0 -0
  263. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/tasks/v2/utils_templates.py +0 -0
  264. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/types/__init__.py +0 -0
  265. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/types/validators/__init__.py +0 -0
  266. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/types/validators/_common_validators.py +0 -0
  267. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/types/validators/_filter_validators.py +0 -0
  268. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/types/validators/_workflow_task_arguments_validators.py +0 -0
  269. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/urls.py +0 -0
  270. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/utils.py +0 -0
  271. {fractal_server-2.17.2 → fractal_server-2.18.0a0}/fractal_server/zip_tools.py +0 -0
@@ -1,16 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fractal-server
3
- Version: 2.17.2
3
+ Version: 2.18.0a0
4
4
  Summary: Backend component of the Fractal analytics platform
5
5
  License-Expression: BSD-3-Clause
6
6
  License-File: LICENSE
7
7
  Author: Tommaso Comparin
8
8
  Author-email: tommaso.comparin@exact-lab.it
9
- Requires-Python: >=3.11,<3.14
9
+ Requires-Python: >=3.11,<3.15
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
14
15
  Requires-Dist: alembic (>=1.13.1,<2.0.0)
15
16
  Requires-Dist: fabric (>=3.2.2,<3.3.0)
16
17
  Requires-Dist: fastapi (>=0.120.0,<0.121.0)
@@ -0,0 +1 @@
1
+ __VERSION__ = "2.18.0a0"
@@ -0,0 +1,53 @@
1
+ from sqlmodel import BOOLEAN
2
+ from sqlmodel import CheckConstraint
3
+ from sqlmodel import Column
4
+ from sqlmodel import Field
5
+ from sqlmodel import Index
6
+ from sqlmodel import SQLModel
7
+ from sqlmodel import String
8
+ from sqlmodel import column
9
+
10
+
11
+ class LinkUserProjectV2(SQLModel, table=True):
12
+ """
13
+ Crossing table between User and ProjectV2
14
+ """
15
+
16
+ project_id: int = Field(
17
+ foreign_key="projectv2.id", primary_key=True, ondelete="CASCADE"
18
+ )
19
+ user_id: int = Field(foreign_key="user_oauth.id", primary_key=True)
20
+
21
+ # TODO-2.18.1 drop server_default
22
+ is_owner: bool = Field(
23
+ sa_column=Column(BOOLEAN, server_default="true", nullable=False)
24
+ )
25
+ # TODO-2.18.1 drop server_default
26
+ is_verified: bool = Field(
27
+ sa_column=Column(BOOLEAN, server_default="true", nullable=False)
28
+ )
29
+ # TODO-2.18.1 drop server_default
30
+ permissions: str = Field(
31
+ sa_column=Column(String, server_default="'rwx'", nullable=False)
32
+ )
33
+
34
+ __table_args__ = (
35
+ Index(
36
+ "ix_linkuserprojectv2_one_owner_per_project",
37
+ "project_id",
38
+ unique=True,
39
+ postgresql_where=column("is_owner").is_(True),
40
+ ),
41
+ CheckConstraint(
42
+ "NOT (is_owner AND NOT is_verified)",
43
+ name="owner_is_verified",
44
+ ),
45
+ CheckConstraint(
46
+ "NOT (is_owner AND permissions <> 'rwx')",
47
+ name="owner_full_permissions",
48
+ ),
49
+ CheckConstraint(
50
+ "permissions IN ('r', 'rw', 'rwx')",
51
+ name="valid_permissions",
52
+ ),
53
+ )
@@ -9,6 +9,7 @@ from .impersonate import router as impersonate_router
9
9
  from .job import router as job_router
10
10
  from .profile import router as profile_router
11
11
  from .resource import router as resource_router
12
+ from .sharing import router as sharing_router
12
13
  from .task import router as task_router
13
14
  from .task_group import router as task_group_router
14
15
  from .task_group_lifecycle import router as task_group_lifecycle_router
@@ -25,3 +26,4 @@ router_admin_v2.include_router(
25
26
  router_admin_v2.include_router(impersonate_router, prefix="/impersonate")
26
27
  router_admin_v2.include_router(resource_router, prefix="/resource")
27
28
  router_admin_v2.include_router(profile_router, prefix="/profile")
29
+ router_admin_v2.include_router(sharing_router, prefix="/linkuserproject")
@@ -56,6 +56,7 @@ async def view_job(
56
56
 
57
57
  Args:
58
58
  id: If not `None`, select a given `applyworkflow.id`.
59
+ user_id:
59
60
  project_id: If not `None`, select a given `applyworkflow.project_id`.
60
61
  dataset_id: If not `None`, select a given
61
62
  `applyworkflow.input_dataset_id`.
@@ -84,12 +85,22 @@ async def view_job(
84
85
  stm = stm.where(JobV2.id == id)
85
86
  stm_count = stm_count.where(JobV2.id == id)
86
87
  if user_id is not None:
87
- stm = stm.join(
88
- LinkUserProjectV2, LinkUserProjectV2.project_id == JobV2.project_id
89
- ).where(LinkUserProjectV2.user_id == user_id)
90
- stm_count = stm_count.join(
91
- LinkUserProjectV2, LinkUserProjectV2.project_id == JobV2.project_id
92
- ).where(LinkUserProjectV2.user_id == user_id)
88
+ stm = (
89
+ stm.join(
90
+ LinkUserProjectV2,
91
+ LinkUserProjectV2.project_id == JobV2.project_id,
92
+ )
93
+ .where(LinkUserProjectV2.user_id == user_id)
94
+ .where(LinkUserProjectV2.is_owner.is_(True))
95
+ )
96
+ stm_count = (
97
+ stm_count.join(
98
+ LinkUserProjectV2,
99
+ LinkUserProjectV2.project_id == JobV2.project_id,
100
+ )
101
+ .where(LinkUserProjectV2.user_id == user_id)
102
+ .where(LinkUserProjectV2.is_owner.is_(True))
103
+ )
93
104
  if project_id is not None:
94
105
  stm = stm.where(JobV2.project_id == project_id)
95
106
  stm_count = stm_count.where(JobV2.project_id == project_id)
@@ -0,0 +1,103 @@
1
+ from fastapi import APIRouter
2
+ from fastapi import Depends
3
+ from sqlalchemy import func
4
+ from sqlmodel import select
5
+
6
+ from fractal_server.app.db import AsyncSession
7
+ from fractal_server.app.db import get_async_db
8
+ from fractal_server.app.models import LinkUserProjectV2
9
+ from fractal_server.app.models import UserOAuth
10
+ from fractal_server.app.models.v2 import ProjectV2
11
+ from fractal_server.app.routes.auth import current_superuser_act
12
+ from fractal_server.app.routes.pagination import PaginationRequest
13
+ from fractal_server.app.routes.pagination import PaginationResponse
14
+ from fractal_server.app.routes.pagination import get_pagination_params
15
+ from fractal_server.app.schemas.v2 import LinkUserProjectRead
16
+
17
+ router = APIRouter()
18
+
19
+
20
+ @router.get("/", response_model=PaginationResponse[LinkUserProjectRead])
21
+ async def view_link_user_project(
22
+ # User info
23
+ user_id: int | None = None,
24
+ # Project info
25
+ project_id: int | None = None,
26
+ project_name: str | None = None,
27
+ # Permissions
28
+ is_owner: bool | None = None,
29
+ is_verified: bool | None = None,
30
+ # -----
31
+ pagination: PaginationRequest = Depends(get_pagination_params),
32
+ superuser: UserOAuth = Depends(current_superuser_act),
33
+ db: AsyncSession = Depends(get_async_db),
34
+ ) -> PaginationResponse[LinkUserProjectRead]:
35
+ page = pagination.page
36
+ page_size = pagination.page_size
37
+
38
+ stm = (
39
+ select(
40
+ LinkUserProjectV2,
41
+ UserOAuth.email,
42
+ ProjectV2.name,
43
+ )
44
+ .join(UserOAuth, UserOAuth.id == LinkUserProjectV2.user_id)
45
+ .join(ProjectV2, ProjectV2.id == LinkUserProjectV2.project_id)
46
+ .order_by(UserOAuth.email, ProjectV2.name)
47
+ )
48
+ stm_count = (
49
+ select(func.count())
50
+ .select_from(LinkUserProjectV2)
51
+ .join(UserOAuth, UserOAuth.id == LinkUserProjectV2.user_id)
52
+ .join(ProjectV2, ProjectV2.id == LinkUserProjectV2.project_id)
53
+ )
54
+
55
+ if project_id is not None:
56
+ stm = stm.where(LinkUserProjectV2.project_id == project_id)
57
+ stm_count = stm_count.where(LinkUserProjectV2.project_id == project_id)
58
+ if project_name is not None:
59
+ stm = stm.where(ProjectV2.name.icontains(project_name))
60
+ stm_count = stm_count.where(ProjectV2.name.icontains(project_name))
61
+ if user_id is not None:
62
+ stm = stm.where(LinkUserProjectV2.user_id == user_id)
63
+ stm_count = stm_count.where(LinkUserProjectV2.user_id == user_id)
64
+ if is_owner is not None:
65
+ stm = stm.where(LinkUserProjectV2.is_owner == is_owner)
66
+ stm_count = stm_count.where(LinkUserProjectV2.is_owner == is_owner)
67
+ if is_verified is not None:
68
+ stm = stm.where(LinkUserProjectV2.is_verified == is_verified)
69
+ stm_count = stm_count.where(
70
+ LinkUserProjectV2.is_verified == is_verified
71
+ )
72
+
73
+ res_total_count = await db.execute(stm_count)
74
+
75
+ total_count = res_total_count.scalar()
76
+ if page_size is None:
77
+ page_size = total_count
78
+ else:
79
+ stm = stm.offset((page - 1) * page_size).limit(page_size)
80
+
81
+ res = await db.execute(stm)
82
+ items = res.all()
83
+
84
+ return PaginationResponse[LinkUserProjectRead](
85
+ total_count=total_count,
86
+ page_size=page_size,
87
+ current_page=page,
88
+ items=[
89
+ dict(
90
+ # User info
91
+ user_id=linkuserproject.user_id,
92
+ user_email=user_email,
93
+ # Project info
94
+ project_id=linkuserproject.project_id,
95
+ project_name=project_name,
96
+ # Permissions
97
+ is_verified=linkuserproject.is_verified,
98
+ is_owner=linkuserproject.is_owner,
99
+ permissions=linkuserproject.permissions,
100
+ )
101
+ for linkuserproject, user_email, project_name in items
102
+ ],
103
+ )
@@ -148,6 +148,7 @@ async def query_tasks(
148
148
  LinkUserProjectV2.user_id == UserOAuth.id,
149
149
  )
150
150
  .where(LinkUserProjectV2.project_id == project_id)
151
+ .where(LinkUserProjectV2.is_owner.is_(True))
151
152
  )
152
153
  project_users[project_id] = [
153
154
  ProjectUser(id=p_user[0], email=p_user[1])
@@ -13,6 +13,7 @@ from .images import router as images_routes_v2
13
13
  from .job import router as job_router_v2
14
14
  from .pre_submission_checks import router as pre_submission_checks_router
15
15
  from .project import router as project_router_v2
16
+ from .sharing import router as sharing_router_v2
16
17
  from .status_legacy import router as status_legacy_router_v2
17
18
  from .submit import router as submit_job_router_v2
18
19
  from .task import router as task_router_v2
@@ -32,6 +33,7 @@ router_api_v2.include_router(dataset_router_v2, tags=["V2 Dataset"])
32
33
  router_api_v2.include_router(pre_submission_checks_router, tags=["V2 Job"])
33
34
  router_api_v2.include_router(job_router_v2, tags=["V2 Job"])
34
35
  router_api_v2.include_router(images_routes_v2, tags=["V2 Images"])
36
+ router_api_v2.include_router(sharing_router_v2, tags=["Project Sharing"])
35
37
  router_api_v2.include_router(project_router_v2, tags=["V2 Project"])
36
38
  router_api_v2.include_router(submit_job_router_v2, tags=["V2 Job"])
37
39
  router_api_v2.include_router(history_router_v2, tags=["V2 History"])
@@ -24,15 +24,17 @@ from fractal_server.app.models.v2 import TaskV2
24
24
  from fractal_server.app.models.v2 import WorkflowTaskV2
25
25
  from fractal_server.app.models.v2 import WorkflowV2
26
26
  from fractal_server.app.schemas.v2 import JobStatusTypeV2
27
+ from fractal_server.app.schemas.v2 import ProjectPermissions
27
28
  from fractal_server.logger import set_logger
28
29
 
29
30
  logger = set_logger(__name__)
30
31
 
31
32
 
32
- async def _get_project_check_owner(
33
+ async def _get_project_check_access(
33
34
  *,
34
35
  project_id: int,
35
36
  user_id: int,
37
+ required_permissions: ProjectPermissions,
36
38
  db: AsyncSession,
37
39
  ) -> ProjectV2:
38
40
  """
@@ -41,6 +43,7 @@ async def _get_project_check_owner(
41
43
  Args:
42
44
  project_id:
43
45
  user_id:
46
+ required_permissions:
44
47
  db:
45
48
 
46
49
  Returns:
@@ -48,31 +51,42 @@ async def _get_project_check_owner(
48
51
 
49
52
  Raises:
50
53
  HTTPException(status_code=403_FORBIDDEN):
51
- If the user is not a member of the project
54
+ - If the user is not a member of the project;
55
+ - If the user has not accepted the invitation yet;
56
+ - If the user has not the target permissions.
52
57
  HTTPException(status_code=404_NOT_FOUND):
53
58
  If the project does not exist
54
59
  """
55
60
  project = await db.get(ProjectV2, project_id)
56
-
57
- link_user_project = await db.get(LinkUserProjectV2, (project_id, user_id))
58
- if not project:
61
+ if project is None:
59
62
  raise HTTPException(
60
63
  status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
61
64
  )
62
- if not link_user_project:
65
+
66
+ link_user_project = await db.get(LinkUserProjectV2, (project_id, user_id))
67
+ if (
68
+ link_user_project is None
69
+ or not link_user_project.is_verified
70
+ or required_permissions not in link_user_project.permissions
71
+ ):
63
72
  raise HTTPException(
64
73
  status_code=status.HTTP_403_FORBIDDEN,
65
- detail=f"Not allowed on project {project_id}",
74
+ detail=(
75
+ "You are not authorized to perform this action. "
76
+ "If you think this is by mistake, "
77
+ "please contact the project owner."
78
+ ),
66
79
  )
67
80
 
68
81
  return project
69
82
 
70
83
 
71
- async def _get_workflow_check_owner(
84
+ async def _get_workflow_check_access(
72
85
  *,
73
86
  workflow_id: int,
74
87
  project_id: int,
75
88
  user_id: int,
89
+ required_permissions: ProjectPermissions,
76
90
  db: AsyncSession,
77
91
  ) -> WorkflowV2:
78
92
  """
@@ -96,8 +110,11 @@ async def _get_workflow_check_owner(
96
110
  """
97
111
 
98
112
  # Access control for project
99
- await _get_project_check_owner(
100
- project_id=project_id, user_id=user_id, db=db
113
+ await _get_project_check_access(
114
+ project_id=project_id,
115
+ user_id=user_id,
116
+ required_permissions=required_permissions,
117
+ db=db,
101
118
  )
102
119
 
103
120
  res = await db.execute(
@@ -116,12 +133,13 @@ async def _get_workflow_check_owner(
116
133
  return workflow
117
134
 
118
135
 
119
- async def _get_workflow_task_check_owner(
136
+ async def _get_workflow_task_check_access(
120
137
  *,
121
138
  project_id: int,
122
139
  workflow_id: int,
123
140
  workflow_task_id: int,
124
141
  user_id: int,
142
+ required_permissions: ProjectPermissions,
125
143
  db: AsyncSession,
126
144
  ) -> tuple[WorkflowTaskV2, WorkflowV2]:
127
145
  """
@@ -146,10 +164,11 @@ async def _get_workflow_task_check_owner(
146
164
  """
147
165
 
148
166
  # Access control for workflow
149
- workflow = await _get_workflow_check_owner(
167
+ workflow = await _get_workflow_check_access(
150
168
  workflow_id=workflow_id,
151
169
  project_id=project_id,
152
170
  user_id=user_id,
171
+ required_permissions=required_permissions,
153
172
  db=db,
154
173
  )
155
174
 
@@ -223,6 +242,7 @@ async def _check_project_exists(
223
242
  .join(LinkUserProjectV2, LinkUserProjectV2.project_id == ProjectV2.id)
224
243
  .where(ProjectV2.name == project_name)
225
244
  .where(LinkUserProjectV2.user_id == user_id)
245
+ .where(LinkUserProjectV2.is_owner.is_(True))
226
246
  )
227
247
  res = await db.execute(stm)
228
248
  if res.scalars().all():
@@ -232,11 +252,12 @@ async def _check_project_exists(
232
252
  )
233
253
 
234
254
 
235
- async def _get_dataset_check_owner(
255
+ async def _get_dataset_check_access(
236
256
  *,
237
257
  project_id: int,
238
258
  dataset_id: int,
239
259
  user_id: int,
260
+ required_permissions: ProjectPermissions,
240
261
  db: AsyncSession,
241
262
  ) -> dict[Literal["dataset", "project"], DatasetV2 | ProjectV2]:
242
263
  """
@@ -260,8 +281,11 @@ async def _get_dataset_check_owner(
260
281
  If the user is not a member of the project
261
282
  """
262
283
  # Access control for project
263
- project = await _get_project_check_owner(
264
- project_id=project_id, user_id=user_id, db=db
284
+ project = await _get_project_check_access(
285
+ project_id=project_id,
286
+ user_id=user_id,
287
+ required_permissions=required_permissions,
288
+ db=db,
265
289
  )
266
290
 
267
291
  res = await db.execute(
@@ -280,11 +304,12 @@ async def _get_dataset_check_owner(
280
304
  return dict(dataset=dataset, project=project)
281
305
 
282
306
 
283
- async def _get_job_check_owner(
307
+ async def _get_job_check_access(
284
308
  *,
285
309
  project_id: int,
286
310
  job_id: int,
287
311
  user_id: int,
312
+ required_permissions: ProjectPermissions,
288
313
  db: AsyncSession,
289
314
  ) -> dict[Literal["job", "project"], JobV2 | ProjectV2]:
290
315
  """
@@ -308,9 +333,10 @@ async def _get_job_check_owner(
308
333
  If the user is not a member of the project
309
334
  """
310
335
  # Access control for project
311
- project = await _get_project_check_owner(
336
+ project = await _get_project_check_access(
312
337
  project_id=project_id,
313
338
  user_id=user_id,
339
+ required_permissions=required_permissions,
314
340
  db=db,
315
341
  )
316
342
 
@@ -13,12 +13,13 @@ from fractal_server.app.models.v2 import HistoryUnit
13
13
  from fractal_server.app.models.v2 import WorkflowV2
14
14
  from fractal_server.app.routes.api.v2._aux_functions import _get_dataset_or_404
15
15
  from fractal_server.app.routes.api.v2._aux_functions import (
16
- _get_project_check_owner,
16
+ _get_project_check_access,
17
17
  )
18
18
  from fractal_server.app.routes.api.v2._aux_functions import _get_workflow_or_404
19
19
  from fractal_server.app.routes.api.v2._aux_functions import (
20
20
  _get_workflowtask_or_404,
21
21
  )
22
+ from fractal_server.app.schemas.v2.sharing import ProjectPermissions
22
23
  from fractal_server.logger import set_logger
23
24
  from fractal_server.zip_tools import _read_single_file_from_zip
24
25
 
@@ -119,6 +120,7 @@ async def _verify_workflow_and_dataset_access(
119
120
  workflow_id: int,
120
121
  dataset_id: int,
121
122
  user_id: int,
123
+ required_permissions: ProjectPermissions,
122
124
  db: AsyncSession,
123
125
  ) -> dict[Literal["dataset", "workflow"], DatasetV2 | WorkflowV2]:
124
126
  """
@@ -131,9 +133,10 @@ async def _verify_workflow_and_dataset_access(
131
133
  user_id:
132
134
  db:
133
135
  """
134
- await _get_project_check_owner(
136
+ await _get_project_check_access(
135
137
  project_id=project_id,
136
138
  user_id=user_id,
139
+ required_permissions=required_permissions,
137
140
  db=db,
138
141
  )
139
142
  workflow = await _get_workflow_or_404(
@@ -158,12 +161,13 @@ async def _verify_workflow_and_dataset_access(
158
161
  return dict(dataset=dataset, workflow=workflow)
159
162
 
160
163
 
161
- async def get_wftask_check_owner(
164
+ async def get_wftask_check_access(
162
165
  *,
163
166
  project_id: int,
164
167
  dataset_id: int,
165
168
  workflowtask_id: int,
166
169
  user_id: int,
170
+ required_permissions: ProjectPermissions,
167
171
  db: AsyncSession,
168
172
  ) -> WorkflowTaskV2:
169
173
  """
@@ -184,6 +188,7 @@ async def get_wftask_check_owner(
184
188
  project_id=project_id,
185
189
  dataset_id=dataset_id,
186
190
  workflow_id=wftask.workflow_id,
191
+ required_permissions=required_permissions,
187
192
  user_id=user_id,
188
193
  db=db,
189
194
  )
@@ -0,0 +1,97 @@
1
+ from fastapi import HTTPException
2
+ from fastapi import status
3
+ from sqlmodel import select
4
+
5
+ from fractal_server.app.db import AsyncSession
6
+ from fractal_server.app.models import UserOAuth
7
+ from fractal_server.app.models.v2 import LinkUserProjectV2
8
+
9
+
10
+ async def raise_403_if_not_owner(
11
+ *,
12
+ user_id: int,
13
+ project_id: int,
14
+ db: AsyncSession,
15
+ ) -> None:
16
+ """
17
+ Raises 403 if User[`user_id`] is not owner of Project[`project_id`],
18
+ regardless of whether the User or Project exists.
19
+ """
20
+ res = await db.execute(
21
+ select(LinkUserProjectV2)
22
+ .where(LinkUserProjectV2.project_id == project_id)
23
+ .where(LinkUserProjectV2.user_id == user_id)
24
+ .where(LinkUserProjectV2.is_owner.is_(True))
25
+ )
26
+ link = res.scalars().one_or_none()
27
+ if link is None:
28
+ raise HTTPException(
29
+ status_code=status.HTTP_403_FORBIDDEN,
30
+ detail="Current user is not the project owner.",
31
+ )
32
+ return link
33
+
34
+
35
+ async def get_link_or_404(
36
+ *, user_id: int, project_id: int, db: AsyncSession
37
+ ) -> LinkUserProjectV2:
38
+ """
39
+ Raises 404 if User[`user_id`] is not linked to Project[`project_id`],
40
+ regardless of whether the User or Project exists.
41
+ """
42
+ link = await db.get(LinkUserProjectV2, (project_id, user_id))
43
+ if link is None:
44
+ raise HTTPException(
45
+ status_code=status.HTTP_404_NOT_FOUND,
46
+ detail="User is not linked to project.",
47
+ )
48
+ return link
49
+
50
+
51
+ async def get_pending_invitation_or_404(
52
+ *, user_id: int, project_id: int, db: AsyncSession
53
+ ) -> LinkUserProjectV2:
54
+ """
55
+ Raises 404 if User[`user_id`] has not a pending invitation to
56
+ Project[`project_id`], regardless of whether the User or Project exists.
57
+ """
58
+ link = await get_link_or_404(user_id=user_id, project_id=project_id, db=db)
59
+ if link.is_verified:
60
+ raise HTTPException(
61
+ status_code=status.HTTP_404_NOT_FOUND,
62
+ detail="No pending invitation for user on this project.",
63
+ )
64
+ return link
65
+
66
+
67
+ async def raise_422_if_link_exists(
68
+ *, user_id: int, project_id: int, db: AsyncSession
69
+ ) -> None:
70
+ """
71
+ Raises 422 if User[`user_id`] is linked Project[`project_id`], regardless
72
+ of whether the User or Project exists.
73
+ """
74
+ link = await db.get(LinkUserProjectV2, (project_id, user_id))
75
+ if link is not None:
76
+ raise HTTPException(
77
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
78
+ detail="User is already associated to project.",
79
+ )
80
+ return
81
+
82
+
83
+ async def get_user_id_from_email_or_404(
84
+ *, user_email: str, db: AsyncSession
85
+ ) -> int:
86
+ """
87
+ Raises 404 if there is no User with email `user_email`.
88
+ """
89
+ res = await db.execute(
90
+ select(UserOAuth.id).where(UserOAuth.email == user_email)
91
+ )
92
+ user_id = res.scalar_one_or_none()
93
+ if user_id is None:
94
+ raise HTTPException(
95
+ status_code=status.HTTP_404_NOT_FOUND, detail="User not found."
96
+ )
97
+ return user_id