zenml-nightly 0.80.0.dev20250326__py3-none-any.whl → 0.80.0.dev20250328__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. zenml/VERSION +1 -1
  2. zenml/cli/project.py +41 -4
  3. zenml/client.py +7 -2
  4. zenml/config/global_config.py +15 -15
  5. zenml/integrations/databricks/__init__.py +6 -3
  6. zenml/integrations/deepchecks/__init__.py +5 -2
  7. zenml/integrations/evidently/__init__.py +5 -2
  8. zenml/integrations/facets/__init__.py +5 -2
  9. zenml/integrations/feast/__init__.py +4 -2
  10. zenml/integrations/great_expectations/__init__.py +4 -2
  11. zenml/integrations/huggingface/__init__.py +4 -2
  12. zenml/integrations/integration.py +6 -1
  13. zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py +1 -1
  14. zenml/integrations/langchain/materializers/openai_embedding_materializer.py +5 -9
  15. zenml/integrations/langchain/materializers/vector_store_materializer.py +3 -7
  16. zenml/integrations/llama_index/materializers/document_materializer.py +2 -6
  17. zenml/integrations/llama_index/materializers/gpt_index_materializer.py +2 -7
  18. zenml/integrations/mlflow/__init__.py +15 -4
  19. zenml/integrations/seldon/__init__.py +4 -2
  20. zenml/integrations/tensorboard/__init__.py +3 -1
  21. zenml/integrations/tensorflow/__init__.py +4 -3
  22. zenml/integrations/whylogs/__init__.py +4 -2
  23. zenml/models/v2/core/user.py +17 -0
  24. zenml/utils/requirements_utils.py +12 -3
  25. zenml/zen_server/routers/users_endpoints.py +169 -173
  26. zenml/zen_server/template_execution/utils.py +14 -5
  27. zenml/zen_stores/base_zen_store.py +46 -14
  28. zenml/zen_stores/migrations/versions/1f1105204aaf_add_user_default_project.py +51 -0
  29. zenml/zen_stores/rest_zen_store.py +1 -3
  30. zenml/zen_stores/schemas/user_schemas.py +11 -0
  31. zenml/zen_stores/sql_zen_store.py +1 -5
  32. {zenml_nightly-0.80.0.dev20250326.dist-info → zenml_nightly-0.80.0.dev20250328.dist-info}/METADATA +1 -1
  33. {zenml_nightly-0.80.0.dev20250326.dist-info → zenml_nightly-0.80.0.dev20250328.dist-info}/RECORD +36 -35
  34. {zenml_nightly-0.80.0.dev20250326.dist-info → zenml_nightly-0.80.0.dev20250328.dist-info}/LICENSE +0 -0
  35. {zenml_nightly-0.80.0.dev20250326.dist-info → zenml_nightly-0.80.0.dev20250328.dist-info}/WHEEL +0 -0
  36. {zenml_nightly-0.80.0.dev20250326.dist-info → zenml_nightly-0.80.0.dev20250328.dist-info}/entry_points.txt +0 -0
@@ -50,11 +50,13 @@ class WhylogsIntegration(Integration):
50
50
  return [WhylogsDataValidatorFlavor]
51
51
 
52
52
  @classmethod
53
- def get_requirements(cls, target_os: Optional[str] = None) -> List[str]:
53
+ def get_requirements(cls, target_os: Optional[str] = None, python_version: Optional[str] = None
54
+ ) -> List[str]:
54
55
  """Method to get the requirements for the integration.
55
56
 
56
57
  Args:
57
58
  target_os: The target operating system to get the requirements for.
59
+ python_version: The Python version to use for the requirements.
58
60
 
59
61
  Returns:
60
62
  A list of requirements.
@@ -62,6 +64,6 @@ class WhylogsIntegration(Integration):
62
64
  from zenml.integrations.pandas import PandasIntegration
63
65
 
64
66
  return cls.REQUIREMENTS + \
65
- PandasIntegration.get_requirements(target_os=target_os)
67
+ PandasIntegration.get_requirements(target_os=target_os, python_version=python_version)
66
68
 
67
69
 
@@ -203,6 +203,10 @@ class UserUpdate(UserBase, BaseUpdate):
203
203
  "accounts. Required when updating the password.",
204
204
  max_length=STR_FIELD_MAX_LENGTH,
205
205
  )
206
+ default_project_id: Optional[UUID] = Field(
207
+ default=None,
208
+ title="The default project ID for the user.",
209
+ )
206
210
 
207
211
  @model_validator(mode="after")
208
212
  def user_email_updates(self) -> "UserUpdate":
@@ -279,6 +283,10 @@ class UserResponseBody(BaseDatedResponseBody):
279
283
  is_admin: bool = Field(
280
284
  title="Whether the account is an administrator.",
281
285
  )
286
+ default_project_id: Optional[UUID] = Field(
287
+ default=None,
288
+ title="The default project ID for the user.",
289
+ )
282
290
 
283
291
 
284
292
  class UserResponseMetadata(BaseResponseMetadata):
@@ -422,6 +430,15 @@ class UserResponse(
422
430
  """
423
431
  return self.get_metadata().user_metadata
424
432
 
433
+ @property
434
+ def default_project_id(self) -> Optional[UUID]:
435
+ """The `default_project_id` property.
436
+
437
+ Returns:
438
+ the value of the property.
439
+ """
440
+ return self.get_body().default_project_id
441
+
425
442
  # Helper methods
426
443
  @classmethod
427
444
  def _get_crypt_context(cls) -> "CryptContext":
@@ -13,7 +13,7 @@
13
13
  # permissions and limitations under the License.
14
14
  """Requirement utils."""
15
15
 
16
- from typing import TYPE_CHECKING, List, Set, Tuple
16
+ from typing import TYPE_CHECKING, List, Optional, Set, Tuple
17
17
 
18
18
  from zenml.integrations.utils import get_integration_for_module
19
19
 
@@ -23,11 +23,13 @@ if TYPE_CHECKING:
23
23
 
24
24
  def get_requirements_for_stack(
25
25
  stack: "StackResponse",
26
+ python_version: Optional[str] = None,
26
27
  ) -> Tuple[List[str], List[str]]:
27
28
  """Get requirements for a stack model.
28
29
 
29
30
  Args:
30
31
  stack: The stack for which to get the requirements.
32
+ python_version: The Python version to use for the requirements.
31
33
 
32
34
  Returns:
33
35
  Tuple of PyPI and APT requirements of the stack.
@@ -41,7 +43,10 @@ def get_requirements_for_stack(
41
43
  (
42
44
  component_pypi_requirements,
43
45
  component_apt_packages,
44
- ) = get_requirements_for_component(component=component)
46
+ ) = get_requirements_for_component(
47
+ component=component,
48
+ python_version=python_version,
49
+ )
45
50
  pypi_requirements = pypi_requirements.union(
46
51
  component_pypi_requirements
47
52
  )
@@ -52,11 +57,13 @@ def get_requirements_for_stack(
52
57
 
53
58
  def get_requirements_for_component(
54
59
  component: "ComponentResponse",
60
+ python_version: Optional[str] = None,
55
61
  ) -> Tuple[List[str], List[str]]:
56
62
  """Get requirements for a component model.
57
63
 
58
64
  Args:
59
65
  component: The component for which to get the requirements.
66
+ python_version: The Python version to use for the requirements.
60
67
 
61
68
  Returns:
62
69
  Tuple of PyPI and APT requirements of the component.
@@ -66,6 +73,8 @@ def get_requirements_for_component(
66
73
  )
67
74
 
68
75
  if integration:
69
- return integration.get_requirements(), integration.APT_PACKAGES
76
+ return integration.get_requirements(
77
+ python_version=python_version
78
+ ), integration.APT_PACKAGES
70
79
  else:
71
80
  return [], []
@@ -65,6 +65,11 @@ from zenml.zen_server.utils import (
65
65
 
66
66
  logger = get_logger(__name__)
67
67
 
68
+ pass_change_limiter = RequestLimiter(
69
+ day_limit=server_config().login_rate_limit_day,
70
+ minute_limit=server_config().login_rate_limit_minute,
71
+ )
72
+
68
73
  router = APIRouter(
69
74
  prefix=API + VERSION_1 + USERS,
70
75
  tags=["users"],
@@ -223,179 +228,6 @@ def get_user(
223
228
  # When the auth scheme is set to EXTERNAL, users cannot be updated via the
224
229
  # API.
225
230
  if server_config().auth_scheme != AuthScheme.EXTERNAL:
226
- pass_change_limiter = RequestLimiter(
227
- day_limit=server_config().login_rate_limit_day,
228
- minute_limit=server_config().login_rate_limit_minute,
229
- )
230
-
231
- @router.put(
232
- "/{user_name_or_id}",
233
- responses={
234
- 401: error_response,
235
- 404: error_response,
236
- 422: error_response,
237
- },
238
- )
239
- @handle_exceptions
240
- def update_user(
241
- user_name_or_id: Union[str, UUID],
242
- user_update: UserUpdate,
243
- request: Request,
244
- auth_context: AuthContext = Security(authorize),
245
- ) -> UserResponse:
246
- """Updates a specific user.
247
-
248
- Args:
249
- user_name_or_id: Name or ID of the user.
250
- user_update: the user to use for the update.
251
- request: The request object.
252
- auth_context: Authentication context.
253
-
254
- Returns:
255
- The updated user.
256
-
257
- Raises:
258
- IllegalOperationError: if the user tries change admin status,
259
- while not an admin, if the user tries to change the password
260
- of another user, or if the user tries to change their own
261
- password without providing the old password or providing
262
- an incorrect old password.
263
- """
264
- user = zen_store().get_user(user_name_or_id)
265
-
266
- # Use a separate object to compute the update that will be applied to
267
- # the user to avoid giving the API requester direct control over the
268
- # user attributes that are updated.
269
- #
270
- # Exclude attributes that cannot be updated through this endpoint:
271
- #
272
- # - activation_token
273
- # - external_user_id
274
- # - old_password
275
- #
276
- # Exclude things that are not always safe to update and need to be
277
- # validated first:
278
- #
279
- # - admin
280
- # - active
281
- # - password
282
- # - email_opted_in + email
283
- #
284
- safe_user_update = user_update.create_copy(
285
- exclude={
286
- "activation_token",
287
- "external_user_id",
288
- "is_admin",
289
- "active",
290
- "password",
291
- "old_password",
292
- "email_opted_in",
293
- "email",
294
- },
295
- )
296
-
297
- if user.id != auth_context.user.id:
298
- verify_admin_status_if_no_rbac(
299
- auth_context.user.is_admin, "update other user account"
300
- )
301
- # verify_permission_for_model(
302
- # user,
303
- # action=Action.UPDATE,
304
- # )
305
-
306
- # Validate a password change
307
- if user_update.password is not None:
308
- if user.id != auth_context.user.id:
309
- raise IllegalOperationError(
310
- "Users cannot change the password of other users. Use the "
311
- "account deactivation and activation flow instead."
312
- )
313
-
314
- # If the user is updating their own password, we need to verify
315
- # the old password
316
- if user_update.old_password is None:
317
- raise IllegalOperationError(
318
- "The current password must be supplied when changing the "
319
- "password."
320
- )
321
-
322
- with pass_change_limiter.limit_failed_requests(request):
323
- auth_user = zen_store().get_auth_user(user_name_or_id)
324
- if not UserAuthModel.verify_password(
325
- user_update.old_password, auth_user
326
- ):
327
- raise IllegalOperationError(
328
- "The current password is incorrect."
329
- )
330
-
331
- # Accept the password update
332
- safe_user_update.password = user_update.password
333
-
334
- # Validate an admin status change
335
- if (
336
- user_update.is_admin is not None
337
- and user.is_admin != user_update.is_admin
338
- ):
339
- if user.id == auth_context.user.id:
340
- raise IllegalOperationError(
341
- "Cannot change the admin status of your own user account."
342
- )
343
-
344
- if (
345
- user.id != auth_context.user.id
346
- and not auth_context.user.is_admin
347
- ):
348
- raise IllegalOperationError(
349
- "Only admins are allowed to change the admin status of "
350
- "other user accounts."
351
- )
352
-
353
- # Accept the admin status update
354
- safe_user_update.is_admin = user_update.is_admin
355
-
356
- # Validate an active status change
357
- if (
358
- user_update.active is not None
359
- and user.active != user_update.active
360
- ):
361
- if user.id == auth_context.user.id:
362
- raise IllegalOperationError(
363
- "Cannot change the active status of your own user account."
364
- )
365
-
366
- if (
367
- user.id != auth_context.user.id
368
- and not auth_context.user.is_admin
369
- ):
370
- raise IllegalOperationError(
371
- "Only admins are allowed to change the active status of "
372
- "other user accounts."
373
- )
374
-
375
- # Accept the admin status update
376
- safe_user_update.is_admin = user_update.is_admin
377
-
378
- # Validate changes to private user account information
379
- if (
380
- user_update.email_opted_in is not None
381
- or user_update.email is not None
382
- ):
383
- if user.id != auth_context.user.id:
384
- raise IllegalOperationError(
385
- "Cannot change the private user account information for "
386
- "another user account."
387
- )
388
-
389
- # Accept the private user account information update
390
- if safe_user_update.email_opted_in is not None:
391
- safe_user_update.email_opted_in = user_update.email_opted_in
392
- safe_user_update.email = user_update.email
393
-
394
- updated_user = zen_store().update_user(
395
- user_id=user.id,
396
- user_update=safe_user_update,
397
- )
398
- return dehydrate_response_model(updated_user)
399
231
 
400
232
  @activation_router.put(
401
233
  "/{user_name_or_id}" + ACTIVATE,
@@ -599,6 +431,170 @@ if server_config().auth_scheme != AuthScheme.EXTERNAL:
599
431
  )
600
432
 
601
433
 
434
+ @router.put(
435
+ "/{user_name_or_id}",
436
+ responses={
437
+ 401: error_response,
438
+ 404: error_response,
439
+ 422: error_response,
440
+ },
441
+ )
442
+ @handle_exceptions
443
+ def update_user(
444
+ user_name_or_id: Union[str, UUID],
445
+ user_update: UserUpdate,
446
+ request: Request,
447
+ auth_context: AuthContext = Security(authorize),
448
+ ) -> UserResponse:
449
+ """Updates a specific user.
450
+
451
+ Args:
452
+ user_name_or_id: Name or ID of the user.
453
+ user_update: the user to use for the update.
454
+ request: The request object.
455
+ auth_context: Authentication context.
456
+
457
+ Returns:
458
+ The updated user.
459
+
460
+ Raises:
461
+ IllegalOperationError: if the user tries change admin status,
462
+ while not an admin, if the user tries to change the password
463
+ of another user, or if the user tries to change their own
464
+ password without providing the old password or providing
465
+ an incorrect old password.
466
+ """
467
+ if server_config().auth_scheme == AuthScheme.EXTERNAL:
468
+ # For external auth, we only allow updating the default project
469
+ user_update = UserUpdate(
470
+ default_project_id=user_update.default_project_id,
471
+ )
472
+
473
+ user = zen_store().get_user(user_name_or_id)
474
+
475
+ # Use a separate object to compute the update that will be applied to
476
+ # the user to avoid giving the API requester direct control over the
477
+ # user attributes that are updated.
478
+ #
479
+ # Exclude attributes that cannot be updated through this endpoint:
480
+ #
481
+ # - activation_token
482
+ # - external_user_id
483
+ # - old_password
484
+ #
485
+ # Exclude things that are not always safe to update and need to be
486
+ # validated first:
487
+ #
488
+ # - admin
489
+ # - active
490
+ # - password
491
+ # - email_opted_in + email
492
+ #
493
+ safe_user_update = user_update.create_copy(
494
+ exclude={
495
+ "activation_token",
496
+ "external_user_id",
497
+ "is_admin",
498
+ "active",
499
+ "password",
500
+ "old_password",
501
+ "email_opted_in",
502
+ "email",
503
+ },
504
+ )
505
+
506
+ if user.id != auth_context.user.id:
507
+ verify_admin_status_if_no_rbac(
508
+ auth_context.user.is_admin, "update other user account"
509
+ )
510
+ # verify_permission_for_model(
511
+ # user,
512
+ # action=Action.UPDATE,
513
+ # )
514
+
515
+ # Validate a password change
516
+ if user_update.password is not None:
517
+ if user.id != auth_context.user.id:
518
+ raise IllegalOperationError(
519
+ "Users cannot change the password of other users. Use the "
520
+ "account deactivation and activation flow instead."
521
+ )
522
+
523
+ # If the user is updating their own password, we need to verify
524
+ # the old password
525
+ if user_update.old_password is None:
526
+ raise IllegalOperationError(
527
+ "The current password must be supplied when changing the "
528
+ "password."
529
+ )
530
+
531
+ with pass_change_limiter.limit_failed_requests(request):
532
+ auth_user = zen_store().get_auth_user(user_name_or_id)
533
+ if not UserAuthModel.verify_password(
534
+ user_update.old_password, auth_user
535
+ ):
536
+ raise IllegalOperationError(
537
+ "The current password is incorrect."
538
+ )
539
+
540
+ # Accept the password update
541
+ safe_user_update.password = user_update.password
542
+
543
+ # Validate an admin status change
544
+ if (
545
+ user_update.is_admin is not None
546
+ and user.is_admin != user_update.is_admin
547
+ ):
548
+ if user.id == auth_context.user.id:
549
+ raise IllegalOperationError(
550
+ "Cannot change the admin status of your own user account."
551
+ )
552
+
553
+ if user.id != auth_context.user.id and not auth_context.user.is_admin:
554
+ raise IllegalOperationError(
555
+ "Only admins are allowed to change the admin status of "
556
+ "other user accounts."
557
+ )
558
+
559
+ # Accept the admin status update
560
+ safe_user_update.is_admin = user_update.is_admin
561
+
562
+ # Validate an active status change
563
+ if user_update.active is not None and user.active != user_update.active:
564
+ if user.id == auth_context.user.id:
565
+ raise IllegalOperationError(
566
+ "Cannot change the active status of your own user account."
567
+ )
568
+
569
+ if user.id != auth_context.user.id and not auth_context.user.is_admin:
570
+ raise IllegalOperationError(
571
+ "Only admins are allowed to change the active status of "
572
+ "other user accounts."
573
+ )
574
+
575
+ # Accept the admin status update
576
+ safe_user_update.is_admin = user_update.is_admin
577
+
578
+ # Validate changes to private user account information
579
+ if user_update.email_opted_in is not None or user_update.email is not None:
580
+ if user.id != auth_context.user.id:
581
+ raise IllegalOperationError(
582
+ "Cannot change the private user account information for "
583
+ "another user account."
584
+ )
585
+
586
+ # Accept the private user account information update
587
+ if safe_user_update.email_opted_in is not None:
588
+ safe_user_update.email_opted_in = user_update.email_opted_in
589
+ safe_user_update.email = user_update.email
590
+
591
+ updated_user = zen_store().update_user(
592
+ user_id=user.id,
593
+ user_update=safe_user_update,
594
+ )
595
+ return dehydrate_response_model(updated_user)
596
+
597
+
602
598
  @current_user_router.get(
603
599
  "/current-user",
604
600
  responses={401: error_response, 404: error_response, 422: error_response},
@@ -149,11 +149,6 @@ def run_template(
149
149
  )
150
150
 
151
151
  def _task() -> None:
152
- (
153
- pypi_requirements,
154
- apt_packages,
155
- ) = requirements_utils.get_requirements_for_stack(stack=stack)
156
-
157
152
  if build.python_version:
158
153
  version_info = version.parse(build.python_version)
159
154
  python_version = f"{version_info.major}.{version_info.minor}"
@@ -162,6 +157,13 @@ def run_template(
162
157
  f"{sys.version_info.major}.{sys.version_info.minor}"
163
158
  )
164
159
 
160
+ (
161
+ pypi_requirements,
162
+ apt_packages,
163
+ ) = requirements_utils.get_requirements_for_stack(
164
+ stack=stack, python_version=python_version
165
+ )
166
+
165
167
  dockerfile = generate_dockerfile(
166
168
  pypi_requirements=pypi_requirements,
167
169
  apt_packages=apt_packages,
@@ -169,6 +171,10 @@ def run_template(
169
171
  python_version=python_version,
170
172
  )
171
173
 
174
+ # building a docker image with requirements and apt packages from the
175
+ # stack only (no code). Ideally, only orchestrator requirements should
176
+ # be added to the docker image, but we have to instantiate the entire
177
+ # stack to get the orchestrator to run pipelines.
172
178
  image_hash = generate_image_hash(dockerfile=dockerfile)
173
179
 
174
180
  runner_image = workload_manager().build_and_push_image(
@@ -182,6 +188,9 @@ def run_template(
182
188
  workload_id=new_deployment.id,
183
189
  message="Starting pipeline run.",
184
190
  )
191
+
192
+ # could do this same thing with a step operator, but we need some
193
+ # minor changes to the abstract interface to support that.
185
194
  workload_manager().run(
186
195
  workload_id=new_deployment.id,
187
196
  image=runner_image,
@@ -22,7 +22,6 @@ from typing import (
22
22
  Optional,
23
23
  Tuple,
24
24
  Type,
25
- Union,
26
25
  )
27
26
  from uuid import UUID
28
27
 
@@ -46,6 +45,7 @@ from zenml.enums import (
46
45
  from zenml.exceptions import IllegalOperationError
47
46
  from zenml.logger import get_logger
48
47
  from zenml.models import (
48
+ ProjectFilter,
49
49
  ProjectResponse,
50
50
  ServerDatabaseType,
51
51
  ServerDeploymentType,
@@ -291,7 +291,7 @@ class BaseZenStore(
291
291
 
292
292
  def validate_active_config(
293
293
  self,
294
- active_project_name_or_id: Optional[Union[str, UUID]] = None,
294
+ active_project_id: Optional[UUID] = None,
295
295
  active_stack_id: Optional[UUID] = None,
296
296
  config_name: str = "",
297
297
  ) -> Tuple[Optional[ProjectResponse], StackResponse]:
@@ -306,7 +306,7 @@ class BaseZenStore(
306
306
  stack will be returned in their stead.
307
307
 
308
308
  Args:
309
- active_project_name_or_id: The name or ID of the active project.
309
+ active_project_id: The ID of the active project.
310
310
  active_stack_id: The ID of the active stack.
311
311
  config_name: The name of the configuration to validate (used in the
312
312
  displayed logs/messages).
@@ -316,30 +316,62 @@ class BaseZenStore(
316
316
  """
317
317
  active_project: Optional[ProjectResponse] = None
318
318
 
319
- if active_project_name_or_id:
319
+ if active_project_id:
320
320
  try:
321
- active_project = self.get_project(active_project_name_or_id)
321
+ active_project = self.get_project(active_project_id)
322
322
  except (KeyError, IllegalOperationError):
323
- active_project_name_or_id = None
323
+ active_project_id = None
324
324
  logger.warning(
325
325
  f"The current {config_name} active project is no longer "
326
326
  f"available."
327
327
  )
328
328
 
329
+ if active_project is None:
330
+ user = self.get_user()
331
+ if user.default_project_id:
332
+ try:
333
+ active_project = self.get_project(user.default_project_id)
334
+ except (KeyError, IllegalOperationError):
335
+ logger.warning(
336
+ "The default project %s for the active user is no longer "
337
+ "available.",
338
+ user.default_project_id,
339
+ )
340
+ else:
341
+ logger.info(
342
+ f"Setting the {config_name} active project "
343
+ f"to '{active_project.name}'."
344
+ )
345
+
329
346
  if active_project is None:
330
347
  try:
331
- active_project = self._get_default_project()
332
- except (KeyError, IllegalOperationError):
348
+ projects = self.list_projects(
349
+ project_filter_model=ProjectFilter()
350
+ )
351
+ except Exception:
352
+ # There was some failure, we force the user to set the active
353
+ # project manually
333
354
  logger.warning(
334
355
  "An active project is not set. Please set the active "
335
- "project by running `zenml project set "
336
- "<project-name>`."
356
+ "project by running `zenml project set <NAME>`."
337
357
  )
338
358
  else:
339
- logger.info(
340
- f"Setting the {config_name} active project "
341
- f"to '{active_project.name}'."
342
- )
359
+ if len(projects) == 0:
360
+ logger.warning(
361
+ "No available projects. Please create a project by "
362
+ "running `zenml project register <NAME> --set`."
363
+ )
364
+ elif len(projects) == 1:
365
+ active_project = projects.items[0]
366
+ logger.info(
367
+ f"Setting the {config_name} active project "
368
+ f"to '{active_project.name}'."
369
+ )
370
+ else:
371
+ logger.warning(
372
+ "Multiple projects are available. Please set the "
373
+ "active project by running `zenml project set <NAME>`."
374
+ )
343
375
 
344
376
  active_stack: StackResponse
345
377
 
@@ -0,0 +1,51 @@
1
+ """Add user default project [1f1105204aaf].
2
+
3
+ Revision ID: 1f1105204aaf
4
+ Revises: 0.80.0
5
+ Create Date: 2025-03-25 11:11:13.480745
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ import sqlmodel
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "1f1105204aaf"
15
+ down_revision = "0.80.0"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ """Upgrade database schema and/or data, creating a new revision."""
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("user", schema=None) as batch_op:
24
+ batch_op.add_column(
25
+ sa.Column(
26
+ "default_project_id",
27
+ sqlmodel.sql.sqltypes.GUID(),
28
+ nullable=True,
29
+ )
30
+ )
31
+ batch_op.create_foreign_key(
32
+ "fk_user_default_project_id_project",
33
+ "project",
34
+ ["default_project_id"],
35
+ ["id"],
36
+ ondelete="SET NULL",
37
+ )
38
+
39
+ # ### end Alembic commands ###
40
+
41
+
42
+ def downgrade() -> None:
43
+ """Downgrade database schema and/or data back to the previous revision."""
44
+ # ### commands auto generated by Alembic - please adjust! ###
45
+ with op.batch_alter_table("user", schema=None) as batch_op:
46
+ batch_op.drop_constraint(
47
+ "fk_user_default_project_id_project", type_="foreignkey"
48
+ )
49
+ batch_op.drop_column("default_project_id")
50
+
51
+ # ### end Alembic commands ###