fractal-server 1.4.0a7__tar.gz → 1.4.0a9__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 (83) hide show
  1. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/PKG-INFO +5 -4
  2. fractal_server-1.4.0a9/fractal_server/__init__.py +1 -0
  3. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/db/__init__.py +11 -3
  4. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/job.py +40 -0
  5. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/auth.py +45 -14
  6. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/__init__.py +1 -0
  7. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/applyworkflow.py +11 -0
  8. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/user.py +27 -0
  9. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/security/__init__.py +12 -13
  10. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/pyproject.toml +5 -5
  11. fractal_server-1.4.0a7/fractal_server/__init__.py +0 -1
  12. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/LICENSE +0 -0
  13. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/README.md +0 -0
  14. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/__main__.py +0 -0
  15. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/alembic.ini +0 -0
  16. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/__init__.py +0 -0
  17. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/__init__.py +0 -0
  18. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/dataset.py +0 -0
  19. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/job.py +0 -0
  20. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/linkuserproject.py +0 -0
  21. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/project.py +0 -0
  22. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/security.py +0 -0
  23. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/state.py +0 -0
  24. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/task.py +0 -0
  25. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/models/workflow.py +0 -0
  26. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/__init__.py +0 -0
  27. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/admin.py +0 -0
  28. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/__init__.py +0 -0
  29. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/__init__.py +0 -0
  30. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/_aux_functions.py +0 -0
  31. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/dataset.py +0 -0
  32. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/project.py +0 -0
  33. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/task.py +0 -0
  34. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/task_collection.py +0 -0
  35. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/workflow.py +0 -0
  36. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/routes/api/v1/workflowtask.py +0 -0
  37. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/.gitignore +0 -0
  38. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/__init__.py +0 -0
  39. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_common.py +0 -0
  40. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_local/__init__.py +0 -0
  41. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_local/_local_config.py +0 -0
  42. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_local/_submit_setup.py +0 -0
  43. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_local/executor.py +0 -0
  44. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/.gitignore +0 -0
  45. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/__init__.py +0 -0
  46. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/_batching.py +0 -0
  47. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/_executor_wait_thread.py +0 -0
  48. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/_slurm_config.py +0 -0
  49. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/_submit_setup.py +0 -0
  50. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/_subprocess_run_as_user.py +0 -0
  51. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/executor.py +0 -0
  52. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/_slurm/remote.py +0 -0
  53. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/common.py +0 -0
  54. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/runner/handle_failed_job.py +0 -0
  55. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/_validators.py +0 -0
  56. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/dataset.py +0 -0
  57. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/json_schemas/manifest.json +0 -0
  58. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/manifest.py +0 -0
  59. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/project.py +0 -0
  60. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/state.py +0 -0
  61. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/task.py +0 -0
  62. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/task_collection.py +0 -0
  63. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/app/schemas/workflow.py +0 -0
  64. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/config.py +0 -0
  65. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/logger.py +0 -0
  66. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/main.py +0 -0
  67. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/README +0 -0
  68. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/env.py +0 -0
  69. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/script.py.mako +0 -0
  70. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/4c308bcaea2b_add_task_args_schema_and_task_args_.py +0 -0
  71. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/50a13d6138fd_initial_schema.py +0 -0
  72. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/70e77f1c38b0_add_applyworkflow_first_task_index_and_.py +0 -0
  73. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/84bf0fffde30_add_dumps_to_applyworkflow.py +0 -0
  74. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/8f79bd162e35_add_docs_info_and_docs_link_to_task_.py +0 -0
  75. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/99ea79d9e5d2_add_dataset_history.py +0 -0
  76. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/a7f4d6137b53_add_workflow_dump_to_applyworkflow.py +0 -0
  77. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/e75cac726012_make_applyworkflow_start_timestamp_not_.py +0 -0
  78. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/migrations/versions/f384e1c0cf5d_drop_task_default_args_columns.py +0 -0
  79. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/py.typed +0 -0
  80. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/syringe.py +0 -0
  81. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/tasks/__init__.py +0 -0
  82. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/tasks/collection.py +0 -0
  83. {fractal_server-1.4.0a7 → fractal_server-1.4.0a9}/fractal_server/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fractal-server
3
- Version: 1.4.0a7
3
+ Version: 1.4.0a9
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
@@ -12,12 +12,13 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
15
16
  Provides-Extra: gunicorn
16
17
  Provides-Extra: postgres
17
18
  Provides-Extra: slurm
18
19
  Requires-Dist: aiosqlite (>=0.17.0,<0.18.0)
19
20
  Requires-Dist: alembic (>=1.9.1,<2.0.0)
20
- Requires-Dist: asyncpg (>=0.27.0,<0.28.0) ; extra == "postgres"
21
+ Requires-Dist: asyncpg (>=0.29.0,<0.30.0) ; extra == "postgres"
21
22
  Requires-Dist: cloudpickle (>=2.2.1,<2.3.0) ; extra == "slurm"
22
23
  Requires-Dist: clusterfutures (>=0.5,<0.6) ; extra == "slurm"
23
24
  Requires-Dist: fastapi (>=0.103.0,<0.104.0)
@@ -26,8 +27,8 @@ Requires-Dist: gunicorn (>=20.1.0,<21.0.0) ; extra == "gunicorn"
26
27
  Requires-Dist: psycopg2 (>=2.9.5,<3.0.0) ; extra == "postgres"
27
28
  Requires-Dist: pydantic (>=1.10.8,<2)
28
29
  Requires-Dist: python-dotenv (>=0.20.0,<0.21.0)
29
- Requires-Dist: sqlalchemy[asyncio] (>=1.4,<2.0)
30
- Requires-Dist: sqlmodel (>=0.0.11,<0.0.12)
30
+ Requires-Dist: sqlalchemy[asyncio] (>=2.0.23,<2.1)
31
+ Requires-Dist: sqlmodel (>=0.0.12,<0.0.13)
31
32
  Requires-Dist: uvicorn (>=0.20.0,<0.21.0)
32
33
  Project-URL: Changelog, https://github.com/fractal-analytics-platform/fractal-server/blob/main/CHANGELOG.md
33
34
  Project-URL: Documentation, https://fractal-analytics-platform.github.io/fractal-server
@@ -0,0 +1 @@
1
+ __VERSION__ = "1.4.0a9"
@@ -63,7 +63,9 @@ class DB:
63
63
  connect_args={"check_same_thread": False},
64
64
  )
65
65
  else:
66
- engine_kwargs_async = {}
66
+ engine_kwargs_async = {
67
+ "pool_pre_ping": True,
68
+ }
67
69
  engine_kwargs_sync = {}
68
70
 
69
71
  cls._engine_async = create_async_engine(
@@ -80,11 +82,17 @@ class DB:
80
82
  )
81
83
 
82
84
  cls._async_session_maker = sessionmaker(
83
- cls._engine_async, class_=AsyncSession, expire_on_commit=False
85
+ cls._engine_async,
86
+ class_=AsyncSession,
87
+ expire_on_commit=False,
88
+ future=True,
84
89
  )
85
90
 
86
91
  cls._sync_session_maker = sessionmaker(
87
- bind=cls._engine_sync, autocommit=False, autoflush=False
92
+ bind=cls._engine_sync,
93
+ autocommit=False,
94
+ autoflush=False,
95
+ future=True,
88
96
  )
89
97
 
90
98
  @event.listens_for(cls._engine_sync, "connect")
@@ -15,8 +15,12 @@ from .....config import get_settings
15
15
  from .....syringe import Inject
16
16
  from ....db import AsyncSession
17
17
  from ....db import get_db
18
+ from ....models import ApplyWorkflow
18
19
  from ....runner._common import SHUTDOWN_FILENAME
19
20
  from ....schemas import ApplyWorkflowRead
21
+ from ....schemas import ApplyWorkflowUpdate
22
+ from ....schemas import JobStatusType
23
+ from ....security import current_active_superuser
20
24
  from ....security import current_active_user
21
25
  from ....security import User
22
26
  from ._aux_functions import _get_job_check_owner
@@ -196,3 +200,39 @@ async def stop_job(
196
200
  f.write(f"Trigger executor shutdown for {job.id=}, {project_id=}.")
197
201
 
198
202
  return Response(status_code=status.HTTP_204_NO_CONTENT)
203
+
204
+
205
+ @router.patch(
206
+ "/job/{job_id}/",
207
+ response_model=ApplyWorkflowRead,
208
+ )
209
+ async def update_job(
210
+ job_update: ApplyWorkflowUpdate,
211
+ job_id: int,
212
+ user: User = Depends(current_active_superuser),
213
+ db: AsyncSession = Depends(get_db),
214
+ ) -> Optional[ApplyWorkflowRead]:
215
+ """
216
+ Change the status of an existing job.
217
+
218
+ This endpoint is only open to superusers, and it does not apply
219
+ project-based access-control to jobs.
220
+ """
221
+ job = await db.get(ApplyWorkflow, job_id)
222
+ if job is None:
223
+ raise HTTPException(
224
+ status_code=status.HTTP_404_NOT_FOUND,
225
+ detail=f"Job {job_id} not found",
226
+ )
227
+
228
+ if job_update.status != JobStatusType.FAILED:
229
+ raise HTTPException(
230
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
231
+ detail=f"Cannot set job status to {job_update.status}",
232
+ )
233
+
234
+ setattr(job, "status", job_update.status)
235
+ await db.commit()
236
+ await db.refresh(job)
237
+ await db.close()
238
+ return job
@@ -3,6 +3,11 @@ Definition of `/auth` routes.
3
3
  """
4
4
  from fastapi import APIRouter
5
5
  from fastapi import Depends
6
+ from fastapi import HTTPException
7
+ from fastapi import status
8
+ from fastapi_users import exceptions
9
+ from fastapi_users import schemas
10
+ from fastapi_users.router.common import ErrorCode
6
11
  from sqlalchemy.ext.asyncio import AsyncSession
7
12
  from sqlmodel import select
8
13
 
@@ -13,12 +18,14 @@ from ..models.security import UserOAuth as User
13
18
  from ..schemas.user import UserCreate
14
19
  from ..schemas.user import UserRead
15
20
  from ..schemas.user import UserUpdate
21
+ from ..schemas.user import UserUpdateStrict
16
22
  from ..security import cookie_backend
17
23
  from ..security import current_active_superuser
18
24
  from ..security import current_active_user
19
25
  from ..security import fastapi_users
26
+ from ..security import get_user_manager
20
27
  from ..security import token_backend
21
-
28
+ from ..security import UserManager
22
29
 
23
30
  router_auth = APIRouter()
24
31
 
@@ -33,18 +40,22 @@ router_auth.include_router(
33
40
  fastapi_users.get_register_router(UserRead, UserCreate),
34
41
  dependencies=[Depends(current_active_superuser)],
35
42
  )
36
- router_auth.include_router(
37
- fastapi_users.get_reset_password_router(),
38
- )
39
- router_auth.include_router(
40
- fastapi_users.get_verify_router(UserRead),
41
- )
42
43
 
43
- # Include users routes, after removing DELETE endpoint (ref
44
- # https://github.com/fastapi-users/fastapi-users/discussions/606)
45
44
  users_router = fastapi_users.get_users_router(UserRead, UserUpdate)
45
+
46
+ # We remove `/auth/users/me` endpoints to implement our own
47
+ # at `/auth/current-user/`.
48
+ # We also remove `DELETE /auth/users/{user_id}`
49
+ # (ref https://github.com/fastapi-users/fastapi-users/discussions/606)
46
50
  users_router.routes = [
47
- route for route in users_router.routes if route.name != "users:delete_user"
51
+ route
52
+ for route in users_router.routes
53
+ if route.name
54
+ not in [
55
+ "users:current_user",
56
+ "users:delete_user",
57
+ "users:patch_current_user",
58
+ ]
48
59
  ]
49
60
  router_auth.include_router(
50
61
  users_router,
@@ -53,17 +64,37 @@ router_auth.include_router(
53
64
  )
54
65
 
55
66
 
56
- @router_auth.get("/whoami", response_model=UserRead)
57
- async def whoami(
58
- user: User = Depends(current_active_user),
67
+ @router_auth.patch("/current-user/", response_model=UserRead)
68
+ async def patch_current_user(
69
+ user_update: UserUpdateStrict,
70
+ current_user: User = Depends(current_active_user),
71
+ user_manager: UserManager = Depends(get_user_manager),
59
72
  ):
73
+
74
+ update = UserUpdate(**user_update.dict(exclude_unset=True))
75
+
76
+ try:
77
+ user = await user_manager.update(update, current_user, safe=True)
78
+ except exceptions.InvalidPasswordException as e:
79
+ raise HTTPException(
80
+ status_code=status.HTTP_400_BAD_REQUEST,
81
+ detail={
82
+ "code": ErrorCode.UPDATE_USER_INVALID_PASSWORD,
83
+ "reason": e.reason,
84
+ },
85
+ )
86
+ return schemas.model_validate(User, user)
87
+
88
+
89
+ @router_auth.get("/current-user/", response_model=UserRead)
90
+ async def get_current_user(user: User = Depends(current_active_user)):
60
91
  """
61
92
  Return current user
62
93
  """
63
94
  return user
64
95
 
65
96
 
66
- @router_auth.get("/userlist", response_model=list[UserRead])
97
+ @router_auth.get("/users/", response_model=list[UserRead])
67
98
  async def list_users(
68
99
  user: User = Depends(current_active_superuser),
69
100
  db: AsyncSession = Depends(get_db),
@@ -3,6 +3,7 @@ Schemas for API request/response bodies
3
3
  """
4
4
  from .applyworkflow import ApplyWorkflowCreate # noqa: F401
5
5
  from .applyworkflow import ApplyWorkflowRead # noqa: F401
6
+ from .applyworkflow import ApplyWorkflowUpdate # noqa: F401
6
7
  from .applyworkflow import JobStatusType # noqa: F401
7
8
  from .dataset import DatasetCreate # noqa: F401
8
9
  from .dataset import DatasetRead # noqa: F401
@@ -146,3 +146,14 @@ class ApplyWorkflowRead(_ApplyWorkflowBase):
146
146
  d["start_timestamp"] = str(self.start_timestamp)
147
147
  d["end_timestamp"] = str(self.end_timestamp)
148
148
  return d
149
+
150
+
151
+ class ApplyWorkflowUpdate(BaseModel):
152
+ """
153
+ Class for updating a job status.
154
+
155
+ Attributes:
156
+ status: New job status.
157
+ """
158
+
159
+ status: JobStatusType
@@ -1,6 +1,8 @@
1
1
  from typing import Optional
2
2
 
3
3
  from fastapi_users import schemas
4
+ from pydantic import BaseModel
5
+ from pydantic import Extra
4
6
  from pydantic import validator
5
7
 
6
8
  from ._validators import val_absolute_path
@@ -52,6 +54,31 @@ class UserUpdate(schemas.BaseUserUpdate):
52
54
  val_absolute_path("cache_dir")
53
55
  )
54
56
 
57
+ @validator(
58
+ "is_active",
59
+ "is_verified",
60
+ "is_superuser",
61
+ "email",
62
+ "password",
63
+ always=False,
64
+ )
65
+ def cant_set_none(cls, v, field):
66
+ if v is None:
67
+ raise ValueError(f"Cannot set {field.name}=None")
68
+ return v
69
+
70
+
71
+ class UserUpdateStrict(BaseModel, extra=Extra.forbid):
72
+ """
73
+ Attributes that every user can self-edit
74
+ """
75
+
76
+ cache_dir: Optional[str]
77
+
78
+ _cache_dir = validator("cache_dir", allow_reuse=True)(
79
+ val_absolute_path("cache_dir")
80
+ )
81
+
55
82
 
56
83
  class UserCreate(schemas.BaseUserCreate):
57
84
  """
@@ -35,8 +35,6 @@ from typing import Type
35
35
 
36
36
  from fastapi import Depends
37
37
  from fastapi import HTTPException
38
- from fastapi import Request
39
- from fastapi import Response
40
38
  from fastapi import status
41
39
  from fastapi_users import BaseUserManager
42
40
  from fastapi_users import FastAPIUsers
@@ -46,6 +44,7 @@ from fastapi_users.authentication import BearerTransport
46
44
  from fastapi_users.authentication import CookieTransport
47
45
  from fastapi_users.authentication import JWTStrategy
48
46
  from fastapi_users.db.base import BaseUserDatabase
47
+ from fastapi_users.exceptions import InvalidPasswordException
49
48
  from fastapi_users.models import ID
50
49
  from fastapi_users.models import OAP
51
50
  from fastapi_users.models import UP
@@ -176,17 +175,17 @@ async def get_user_db(
176
175
 
177
176
 
178
177
  class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
179
- async def on_after_login(
180
- self,
181
- user: User,
182
- request: Optional[Request] = None,
183
- response: Optional[Response] = None,
184
- ) -> None:
185
- """
186
- Perform logic after user login.
187
- *You should overload this method to add your own logic.*
188
- """
189
- pass
178
+ async def validate_password(self, password: str, user: User) -> None:
179
+ # check password length
180
+ min_length, max_length = 4, 100
181
+ if len(password) < min_length:
182
+ raise InvalidPasswordException(
183
+ f"The password is too short (minimum length: {min_length})."
184
+ )
185
+ elif len(password) > max_length:
186
+ raise InvalidPasswordException(
187
+ f"The password is too long (maximum length: {min_length})."
188
+ )
190
189
 
191
190
 
192
191
  async def get_user_manager(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "fractal-server"
3
- version = "1.4.0a7"
3
+ version = "1.4.0a9"
4
4
  description = "Server component of the Fractal analytics platform"
5
5
  authors = [
6
6
  "Jacopo Nespolo <jacopo.nespolo@exact-lab.it>",
@@ -24,18 +24,18 @@ exclude = [
24
24
  python = "^3.9"
25
25
  python-dotenv = "^0.20.0"
26
26
  fastapi = "^0.103.0"
27
- sqlmodel = "^0.0.11"
27
+ sqlmodel = "^0.0.12"
28
+ sqlalchemy = {extras = ["asyncio"], version = ">=2.0.23,<2.1"}
28
29
  aiosqlite = "^0.17.0"
29
30
  fastapi-users = {extras = ["oauth"], version = "^12.1.0"}
30
31
  alembic = "^1.9.1"
31
32
  uvicorn = "^0.20.0"
32
- sqlalchemy = {extras = ["asyncio"], version = "^1.4"}
33
33
  pydantic = ">=1.10.8,<2"
34
34
 
35
35
  clusterfutures = { version = "^0.5", optional = true}
36
36
  cloudpickle = {version = ">=2.2.1,<2.3.0", optional = true}
37
37
 
38
- asyncpg = { version = "^0.27.0", optional = true }
38
+ asyncpg = { version = "^0.29.0", optional = true }
39
39
  psycopg2 = { version = "^2.9.5", optional = true }
40
40
  gunicorn = { version = "^20.1.0", optional = true }
41
41
 
@@ -82,7 +82,7 @@ filterwarnings = [
82
82
  ]
83
83
 
84
84
  [tool.bumpver]
85
- current_version = "1.4.0a7"
85
+ current_version = "1.4.0a9"
86
86
  version_pattern = "MAJOR.MINOR.PATCH[PYTAGNUM]"
87
87
  commit_message = "bump version {old_version} -> {new_version}"
88
88
  commit = true
@@ -1 +0,0 @@
1
- __VERSION__ = "1.4.0a7"