zenml-nightly 0.58.2.dev20240626__py3-none-any.whl → 0.61.0.dev20240710__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 (213) hide show
  1. README.md +30 -9
  2. RELEASE_NOTES.md +240 -0
  3. zenml/VERSION +1 -1
  4. zenml/analytics/enums.py +3 -0
  5. zenml/cli/__init__.py +28 -0
  6. zenml/cli/artifact.py +1 -2
  7. zenml/cli/integration.py +9 -8
  8. zenml/cli/server.py +6 -0
  9. zenml/cli/stack.py +797 -39
  10. zenml/cli/stack_components.py +7 -0
  11. zenml/cli/text_utils.py +35 -1
  12. zenml/cli/utils.py +127 -10
  13. zenml/client.py +23 -14
  14. zenml/config/docker_settings.py +8 -5
  15. zenml/constants.py +5 -1
  16. zenml/container_registries/base_container_registry.py +1 -0
  17. zenml/enums.py +6 -0
  18. zenml/event_hub/event_hub.py +5 -8
  19. zenml/integrations/aws/__init__.py +1 -0
  20. zenml/integrations/azure/__init__.py +1 -0
  21. zenml/integrations/deepchecks/__init__.py +1 -0
  22. zenml/integrations/discord/__init__.py +1 -0
  23. zenml/integrations/evidently/__init__.py +1 -0
  24. zenml/integrations/facets/__init__.py +1 -0
  25. zenml/integrations/feast/__init__.py +1 -0
  26. zenml/integrations/gcp/__init__.py +3 -1
  27. zenml/integrations/gcp/google_credentials_mixin.py +1 -1
  28. zenml/integrations/gcp/service_connectors/gcp_service_connector.py +266 -58
  29. zenml/integrations/huggingface/__init__.py +1 -0
  30. zenml/integrations/integration.py +24 -0
  31. zenml/integrations/kubeflow/__init__.py +3 -0
  32. zenml/integrations/kubeflow/flavors/kubeflow_orchestrator_flavor.py +1 -1
  33. zenml/integrations/kubeflow/orchestrators/kubeflow_orchestrator.py +0 -1
  34. zenml/integrations/kubernetes/__init__.py +3 -1
  35. zenml/integrations/kubernetes/orchestrators/kube_utils.py +4 -1
  36. zenml/integrations/label_studio/annotators/label_studio_annotator.py +1 -0
  37. zenml/integrations/langchain/__init__.py +1 -0
  38. zenml/integrations/mlflow/__init__.py +3 -1
  39. zenml/integrations/neural_prophet/__init__.py +1 -0
  40. zenml/integrations/polars/__init__.py +1 -0
  41. zenml/integrations/prodigy/__init__.py +1 -0
  42. zenml/integrations/pycaret/__init__.py +6 -0
  43. zenml/integrations/registry.py +37 -0
  44. zenml/integrations/s3/artifact_stores/s3_artifact_store.py +17 -6
  45. zenml/integrations/seldon/__init__.py +1 -0
  46. zenml/integrations/seldon/model_deployers/seldon_model_deployer.py +1 -0
  47. zenml/integrations/skypilot/flavors/skypilot_orchestrator_base_vm_config.py +2 -2
  48. zenml/integrations/skypilot/orchestrators/skypilot_base_vm_orchestrator.py +1 -1
  49. zenml/integrations/skypilot/orchestrators/skypilot_orchestrator_entrypoint.py +2 -2
  50. zenml/integrations/skypilot_aws/__init__.py +2 -1
  51. zenml/integrations/skypilot_azure/__init__.py +1 -1
  52. zenml/integrations/skypilot_gcp/__init__.py +1 -1
  53. zenml/integrations/skypilot_lambda/__init__.py +1 -1
  54. zenml/integrations/skypilot_lambda/flavors/skypilot_orchestrator_lambda_vm_flavor.py +1 -1
  55. zenml/integrations/slack/__init__.py +1 -0
  56. zenml/integrations/tekton/__init__.py +1 -0
  57. zenml/integrations/tensorboard/__init__.py +0 -1
  58. zenml/integrations/tensorflow/__init__.py +18 -6
  59. zenml/integrations/wandb/__init__.py +1 -0
  60. zenml/models/__init__.py +9 -0
  61. zenml/models/v2/core/component.py +18 -0
  62. zenml/models/v2/core/model.py +1 -2
  63. zenml/models/v2/core/service_connector.py +17 -0
  64. zenml/models/v2/core/stack.py +31 -0
  65. zenml/models/v2/misc/full_stack.py +97 -0
  66. zenml/models/v2/misc/stack_deployment.py +66 -0
  67. zenml/new/pipelines/pipeline.py +1 -1
  68. zenml/orchestrators/input_utils.py +3 -6
  69. zenml/stack/stack.py +3 -6
  70. zenml/stack_deployments/__init__.py +14 -0
  71. zenml/stack_deployments/aws_stack_deployment.py +289 -0
  72. zenml/stack_deployments/stack_deployment.py +130 -0
  73. zenml/stack_deployments/utils.py +40 -0
  74. zenml/utils/function_utils.py +1 -1
  75. zenml/utils/pagination_utils.py +7 -5
  76. zenml/utils/pipeline_docker_image_builder.py +97 -68
  77. zenml/utils/pydantic_utils.py +6 -5
  78. zenml/zen_server/cloud_utils.py +18 -3
  79. zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-DpJaNHKF.js} +1 -1
  80. zenml/zen_server/dashboard/assets/@radix-CFOkMR_E.js +85 -0
  81. zenml/zen_server/dashboard/assets/{@react-router-DYovave8.js → @react-router-CO-OsFwI.js} +2 -2
  82. zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-DJfzkHO1.js} +2 -2
  83. zenml/zen_server/dashboard/assets/@tanstack-DYiOyJUL.js +22 -0
  84. zenml/zen_server/dashboard/assets/AwarenessChannel-BYDLT2xC.js +1 -0
  85. zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-BkOuRmyq.js} +2 -2
  86. zenml/zen_server/dashboard/assets/Commands-ZvWR1BRs.js +1 -0
  87. zenml/zen_server/dashboard/assets/CopyButton-DVwLkafa.js +2 -0
  88. zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-C2IiqX4I.js} +7 -7
  89. zenml/zen_server/dashboard/assets/DisplayDate-DYgIjlDF.js +1 -0
  90. zenml/zen_server/dashboard/assets/EmptyState-BMLnFVlB.js +1 -0
  91. zenml/zen_server/dashboard/assets/Error-CqX0VqW_.js +1 -0
  92. zenml/zen_server/dashboard/assets/ExecutionStatus-BoLUXR9t.js +1 -0
  93. zenml/zen_server/dashboard/assets/Helpbox-LFydyVwh.js +1 -0
  94. zenml/zen_server/dashboard/assets/Infobox-DnENC0sh.js +1 -0
  95. zenml/zen_server/dashboard/assets/InlineAvatar-CbJtYr0t.js +1 -0
  96. zenml/zen_server/dashboard/assets/{MarkdownVisualization-DsB2QZiK.js → MarkdownVisualization-xp3hhULl.js} +2 -2
  97. zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +1 -0
  98. zenml/zen_server/dashboard/assets/PasswordChecker-DUveqlva.js +1 -0
  99. zenml/zen_server/dashboard/assets/SetPassword-BYBdbQDo.js +1 -0
  100. zenml/zen_server/dashboard/assets/SuccessStep-Nx743hll.js +1 -0
  101. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-DF9gSzE0.js} +1 -1
  102. zenml/zen_server/dashboard/assets/{aws-t0gKCj_R.js → aws-BgKTfTfx.js} +1 -1
  103. zenml/zen_server/dashboard/assets/{check-circle-BVvhm5dy.js → check-circle-i56092KI.js} +1 -1
  104. zenml/zen_server/dashboard/assets/{chevron-down-zcvCWmyP.js → chevron-down-D_ZlKMqH.js} +1 -1
  105. zenml/zen_server/dashboard/assets/{chevron-right-double-CJ50E9Gr.js → chevron-right-double-BiEMg7rd.js} +1 -1
  106. zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.js +1 -0
  107. zenml/zen_server/dashboard/assets/{copy-BRhQz3j-.js → copy-BXNk6BjL.js} +1 -1
  108. zenml/zen_server/dashboard/assets/{database-CRRnyFWh.js → database-1xWSgZfO.js} +1 -1
  109. zenml/zen_server/dashboard/assets/{docker-BAonhm6G.js → docker-CQMVm_4d.js} +1 -1
  110. zenml/zen_server/dashboard/assets/{file-text-CbVERUON.js → file-text-CqD_iu6l.js} +1 -1
  111. zenml/zen_server/dashboard/assets/{help-B8rqCvqn.js → help-bu_DgLKI.js} +1 -1
  112. zenml/zen_server/dashboard/assets/index-C_CrU4vI.js +1 -0
  113. zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +55 -0
  114. zenml/zen_server/dashboard/assets/index-inApY3KQ.css +1 -0
  115. zenml/zen_server/dashboard/assets/index-rK_Wuy2W.js +1 -0
  116. zenml/zen_server/dashboard/assets/index.esm-Corw4lXQ.js +1 -0
  117. zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-BUnVASxp.js} +1 -1
  118. zenml/zen_server/dashboard/assets/not-found-B4VnX8gK.js +1 -0
  119. zenml/zen_server/dashboard/assets/package-CsUhPmou.js +1 -0
  120. zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-3efNCDeb.js} +2 -2
  121. zenml/zen_server/dashboard/assets/page-7zTHbhhI.js +1 -0
  122. zenml/zen_server/dashboard/assets/page-BEs6jK71.js +1 -0
  123. zenml/zen_server/dashboard/assets/page-BpSqIf4B.js +1 -0
  124. zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-Bx6o0ARS.js} +1 -1
  125. zenml/zen_server/dashboard/assets/page-C43QGHTt.js +9 -0
  126. zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +1 -0
  127. zenml/zen_server/dashboard/assets/page-CRTJ0UuR.js +1 -0
  128. zenml/zen_server/dashboard/assets/page-CUZIGO-3.js +1 -0
  129. zenml/zen_server/dashboard/assets/page-CaopxiU1.js +1 -0
  130. zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-Cx67M0QT.js} +1 -1
  131. zenml/zen_server/dashboard/assets/page-D7Z399xy.js +1 -0
  132. zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +1 -0
  133. zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-DKlIdAe5.js} +1 -1
  134. zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-DMOYZppS.js} +1 -1
  135. zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +2 -0
  136. zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-Dc_7KMQE.js} +1 -1
  137. zenml/zen_server/dashboard/assets/page-DvCvroOM.js +1 -0
  138. zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +1 -0
  139. zenml/zen_server/dashboard/assets/page-JyfeDUfu.js +1 -0
  140. zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-Sxn82W-5.js} +1 -1
  141. zenml/zen_server/dashboard/assets/page-TKXERe16.js +1 -0
  142. zenml/zen_server/dashboard/assets/page-Xu8JEjSU.js +1 -0
  143. zenml/zen_server/dashboard/assets/{play-circle-DK5QMJyp.js → play-circle-CNtZKDnW.js} +1 -1
  144. zenml/zen_server/dashboard/assets/plus-DOeLmm7C.js +1 -0
  145. zenml/zen_server/dashboard/assets/{terminal-B2ovgWuz.js → terminal-By9cErXc.js} +1 -1
  146. zenml/zen_server/dashboard/assets/{update-server-settings-mutation-0Wgz8pUE.js → update-server-settings-mutation-CR8e3Sir.js} +1 -1
  147. zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-DuQMeqYA.js} +1 -1
  148. zenml/zen_server/dashboard/assets/{zod-DrZvVLjd.js → zod-BhoGpZ63.js} +1 -1
  149. zenml/zen_server/dashboard/index.html +7 -7
  150. zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
  151. zenml/zen_server/dashboard_legacy/index.html +1 -1
  152. zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.c8c57fb0d2132b1d3c2119e776b7dfb3.js} +4 -4
  153. zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
  154. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js → main.382439a7.chunk.js} +2 -2
  155. zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.382439a7.chunk.js.map} +1 -1
  156. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  157. zenml/zen_server/deploy/helm/README.md +2 -2
  158. zenml/zen_server/feature_gate/zenml_cloud_feature_gate.py +11 -5
  159. zenml/zen_server/pipeline_deployment/utils.py +57 -44
  160. zenml/zen_server/rbac/zenml_cloud_rbac.py +11 -5
  161. zenml/zen_server/routers/stack_deployment_endpoints.py +144 -0
  162. zenml/zen_server/routers/workspaces_endpoints.py +64 -0
  163. zenml/zen_server/zen_server_api.py +2 -0
  164. zenml/zen_stores/migrations/utils.py +1 -1
  165. zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
  166. zenml/zen_stores/migrations/versions/0.61.0_release.py +23 -0
  167. zenml/zen_stores/migrations/versions/0d707865f404_adding_labels_to_stacks.py +30 -0
  168. zenml/zen_stores/rest_zen_store.py +117 -0
  169. zenml/zen_stores/schemas/stack_schemas.py +10 -0
  170. zenml/zen_stores/schemas/step_run_schemas.py +27 -11
  171. zenml/zen_stores/sql_zen_store.py +283 -0
  172. zenml/zen_stores/zen_store_interface.py +79 -0
  173. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/METADATA +32 -10
  174. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/RECORD +177 -162
  175. zenml/zen_server/dashboard/assets/@radix-C9DBgJhe.js +0 -77
  176. zenml/zen_server/dashboard/assets/@tanstack-CEbkxrhX.js +0 -30
  177. zenml/zen_server/dashboard/assets/AwarenessChannel-nXGpmj_f.js +0 -1
  178. zenml/zen_server/dashboard/assets/Cards-nwsvQLVS.js +0 -1
  179. zenml/zen_server/dashboard/assets/Commands-DuIWKg_Q.js +0 -1
  180. zenml/zen_server/dashboard/assets/CopyButton-B_YSm-Ds.js +0 -2
  181. zenml/zen_server/dashboard/assets/DisplayDate-BdguISQF.js +0 -1
  182. zenml/zen_server/dashboard/assets/EmptyState-BkooiGtL.js +0 -1
  183. zenml/zen_server/dashboard/assets/Error-B6M0dPph.js +0 -1
  184. zenml/zen_server/dashboard/assets/Helpbox-BQoqCm04.js +0 -1
  185. zenml/zen_server/dashboard/assets/Infobox-Ce9mefqU.js +0 -1
  186. zenml/zen_server/dashboard/assets/InlineAvatar-DGf3dVhV.js +0 -1
  187. zenml/zen_server/dashboard/assets/PageHeader-DGaemzjc.js +0 -1
  188. zenml/zen_server/dashboard/assets/Pagination-DVYfBCCc.js +0 -1
  189. zenml/zen_server/dashboard/assets/PasswordChecker-DSLBp7Vl.js +0 -1
  190. zenml/zen_server/dashboard/assets/SetPassword-B5s7DJug.js +0 -1
  191. zenml/zen_server/dashboard/assets/SuccessStep-ZzczaM7g.js +0 -1
  192. zenml/zen_server/dashboard/assets/cloud-only-Ba_ShBR5.js +0 -1
  193. zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
  194. zenml/zen_server/dashboard/assets/index-QORVVTMN.js +0 -55
  195. zenml/zen_server/dashboard/assets/index.esm-F7nqy9zY.js +0 -1
  196. zenml/zen_server/dashboard/assets/not-found-Dh2la7kh.js +0 -1
  197. zenml/zen_server/dashboard/assets/page-B-5jAKoO.js +0 -1
  198. zenml/zen_server/dashboard/assets/page-B-vWk8a6.js +0 -1
  199. zenml/zen_server/dashboard/assets/page-B0BrqfS8.js +0 -1
  200. zenml/zen_server/dashboard/assets/page-BQxVFlUl.js +0 -1
  201. zenml/zen_server/dashboard/assets/page-ByrHy6Ss.js +0 -1
  202. zenml/zen_server/dashboard/assets/page-CPtY4Kv_.js +0 -1
  203. zenml/zen_server/dashboard/assets/page-CmmukLsl.js +0 -1
  204. zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
  205. zenml/zen_server/dashboard/assets/page-DAQQyLxT.js +0 -1
  206. zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
  207. zenml/zen_server/dashboard/assets/page-DZCbwOEs.js +0 -2
  208. zenml/zen_server/dashboard/assets/page-DdaIt20-.js +0 -1
  209. zenml/zen_server/dashboard/assets/page-LqLs24Ot.js +0 -1
  210. zenml/zen_server/dashboard/assets/page-lebv0c7C.js +0 -1
  211. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/LICENSE +0 -0
  212. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/WHEEL +0 -0
  213. {zenml_nightly-0.58.2.dev20240626.dist-info → zenml_nightly-0.61.0.dev20240710.dist-info}/entry_points.txt +0 -0
@@ -43,7 +43,8 @@ from google.auth._default import (
43
43
  _get_external_account_credentials,
44
44
  )
45
45
  from google.auth.transport.requests import Request
46
- from google.cloud import container_v1, storage
46
+ from google.cloud import artifactregistry_v1, container_v1, storage
47
+ from google.cloud.location import locations_pb2
47
48
  from google.oauth2 import credentials as gcp_credentials
48
49
  from google.oauth2 import service_account as gcp_service_account
49
50
  from pydantic import Field, field_validator, model_validator
@@ -351,24 +352,72 @@ class GCPOAuth2Token(AuthenticationConfig):
351
352
  class GCPBaseConfig(AuthenticationConfig):
352
353
  """GCP base configuration."""
353
354
 
355
+ @property
356
+ def gcp_project_id(self) -> str:
357
+ """Get the GCP project ID.
358
+
359
+ This method must be implemented by subclasses to ensure that the GCP
360
+ project ID is always available.
361
+
362
+ Raises:
363
+ NotImplementedError: If the method is not implemented.
364
+ """
365
+ raise NotImplementedError
366
+
367
+
368
+ class GCPBaseProjectIDConfig(GCPBaseConfig):
369
+ """GCP base configuration with included project ID."""
370
+
354
371
  project_id: str = Field(
355
372
  title="GCP Project ID where the target resource is located.",
356
373
  )
357
374
 
375
+ @property
376
+ def gcp_project_id(self) -> str:
377
+ """Get the GCP project ID.
378
+
379
+ Returns:
380
+ The GCP project ID.
381
+ """
382
+ return self.project_id
358
383
 
359
- class GCPUserAccountConfig(GCPBaseConfig, GCPUserAccountCredentials):
384
+
385
+ class GCPUserAccountConfig(GCPBaseProjectIDConfig, GCPUserAccountCredentials):
360
386
  """GCP user account configuration."""
361
387
 
362
388
 
363
389
  class GCPServiceAccountConfig(GCPBaseConfig, GCPServiceAccountCredentials):
364
390
  """GCP service account configuration."""
365
391
 
392
+ _project_id: Optional[str] = None
393
+
394
+ @property
395
+ def gcp_project_id(self) -> str:
396
+ """Get the GCP project ID.
397
+
398
+ When a service account JSON is provided, the project ID can be extracted
399
+ from it instead of being provided explicitly.
400
+
401
+ Returns:
402
+ The GCP project ID.
403
+ """
404
+ if self._project_id is None:
405
+ self._project_id = json.loads(
406
+ self.service_account_json.get_secret_value()
407
+ )["project_id"]
408
+ # Guaranteed by the field validator
409
+ assert self._project_id is not None
410
+
411
+ return self._project_id
412
+
366
413
 
367
- class GCPExternalAccountConfig(GCPBaseConfig, GCPExternalAccountCredentials):
414
+ class GCPExternalAccountConfig(
415
+ GCPBaseProjectIDConfig, GCPExternalAccountCredentials
416
+ ):
368
417
  """GCP external account configuration."""
369
418
 
370
419
 
371
- class GCPOAuth2TokenConfig(GCPBaseConfig, GCPOAuth2Token):
420
+ class GCPOAuth2TokenConfig(GCPBaseProjectIDConfig, GCPOAuth2Token):
372
421
  """GCP OAuth 2.0 configuration."""
373
422
 
374
423
  service_account_email: Optional[str] = Field(
@@ -540,7 +589,7 @@ resources in the specified project. When used remotely in a GCP workload, the
540
589
  configured project has to be the same as the project of the attached service
541
590
  account.
542
591
  """,
543
- config_class=GCPBaseConfig,
592
+ config_class=GCPBaseProjectIDConfig,
544
593
  ),
545
594
  AuthenticationMethodModel(
546
595
  name="GCP User Account",
@@ -786,14 +835,53 @@ GKE clusters in the GCP project that it is configured to use.
786
835
  emoji=":cyclone:",
787
836
  ),
788
837
  ResourceTypeModel(
789
- name="GCP GCR container registry",
838
+ name="GCP GAR container registry",
790
839
  resource_type=DOCKER_REGISTRY_RESOURCE_TYPE,
791
840
  description="""
792
- Allows Stack Components to access a GCR registry as a standard
841
+ Allows Stack Components to access a Google Artifact Registry as a standard
793
842
  Docker registry resource. When used by Stack Components, they are provided a
794
843
  pre-authenticated Python Docker client instance.
795
844
 
796
- The configured credentials must have at least the following [GCP permissions](https://cloud.google.com/iam/docs/permissions-reference):
845
+ The configured credentials must have at least the following [GCP permissions](https://cloud.google.com/iam/docs/understanding-roles#artifact-registry-roles):
846
+
847
+ - `artifactregistry.repositories.createOnPush`
848
+ - `artifactregistry.repositories.downloadArtifacts`
849
+ - `artifactregistry.repositories.get`
850
+ - `artifactregistry.repositories.list`
851
+ - `artifactregistry.repositories.readViaVirtualRepository`
852
+ - `artifactregistry.repositories.uploadArtifacts`
853
+ - `artifactregistry.locations.list`
854
+
855
+ The Artifact Registry Create-on-Push Writer role includes all of the above
856
+ permissions.
857
+
858
+ This resource type also includes legacy GCR container registry support.
859
+
860
+ **Important Notice: Google Container Registry** [**is being replaced by Artifact Registry**](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr).
861
+ Please start using Artifact Registry for your containers. As per Google's
862
+ documentation, *"after May 15, 2024, Artifact Registry will host images for the
863
+ gcr.io domain in Google Cloud projects without previous Container Registry
864
+ usage. After March 18, 2025, Container Registry will be shut down."*.
865
+
866
+ Support for legacy GCR registries is still included in the GCP service
867
+ connector. Users that already have GCP service connectors configured to access
868
+ GCR registries may continue to use them without taking any action. However, it
869
+ is recommended to transition to Google Artifact Registries as soon as possible
870
+ by following [the GCP guide on this subject](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)
871
+ and making the following updates to ZenML GCP Service Connectors that are used
872
+ to access GCR resources:
873
+
874
+ * add the IAM permissions documented here to the GCP Service Connector
875
+ credentials to enable them to access the Artifact Registries.
876
+ * users may keep the gcr.io GCR URLs already configured in the GCP Service
877
+ Connectors as well as those used in linked Container Registry stack components
878
+ given that these domains are redirected by Google to GAR as covered in the GCR
879
+ transition guide. Alternatively, users may update the GCP Service Connector
880
+ configuration and/or the Container Registry stack components to use the
881
+ replacement Artifact Registry URLs.
882
+
883
+ When used with GCR registries, the configured credentials must have at least the
884
+ following [GCP permissions](https://cloud.google.com/iam/docs/understanding-roles#cloud-storage-roles):
797
885
 
798
886
  - `storage.buckets.get`
799
887
  - `storage.multipartUploads.abort`
@@ -807,17 +895,21 @@ The configured credentials must have at least the following [GCP permissions](ht
807
895
  The Storage Legacy Bucket Writer role includes all of the above permissions
808
896
  while at the same time restricting access to only the GCR buckets.
809
897
 
810
- The resource name associated with this resource type identifies the GCR
811
- container registry associated with the GCP project (the repository name is
812
- optional):
898
+ If set, the resource name must identify a GAR or GCR registry using one of the
899
+ following formats:
900
+
901
+ - Google Artifact Registry repository URI: `[https://]<region>-docker.pkg.dev/<project-id>/<registry-id>[/<repository-name>]`
902
+ - Google Artifact Registry name: `projects/<project-id>/locations/<location>/repositories/<repository-id>`
903
+ - (legacy) GCR repository URI: `[https://][us.|eu.|asia.]gcr.io/<project-id>[/<repository-name>]`
813
904
 
814
- - GCR repository URI: `[https://]gcr.io/{project-id}[/{repository-name}]
905
+ The connector can only be used to access GAR and GCR registries in the GCP
906
+ project that it is configured to use.
815
907
  """,
816
908
  auth_methods=GCPAuthenticationMethods.values(),
817
- # Does not support instances, given that the connector
818
- # provides access to the entire GCR container registry
819
- # for the configured GCP project.
820
- supports_instances=False,
909
+ # The connector provides access to the entire GCR container registry
910
+ # for the configured GCP project as well as any number of artifact
911
+ # registry repositories.
912
+ supports_instances=True,
821
913
  logo_url="https://public-flavor-logos.s3.eu-central-1.amazonaws.com/container_registry/docker.png",
822
914
  emoji=":whale:",
823
915
  ),
@@ -1006,6 +1098,7 @@ class GCPServiceConnector(ServiceConnector):
1006
1098
  # service account authentication)
1007
1099
 
1008
1100
  assert isinstance(cfg, GCPServiceAccountConfig)
1101
+
1009
1102
  credentials = (
1010
1103
  gcp_service_account.Credentials.from_service_account_info(
1011
1104
  json.loads(
@@ -1093,60 +1186,101 @@ class GCPServiceConnector(ServiceConnector):
1093
1186
 
1094
1187
  return bucket_name
1095
1188
 
1096
- def _parse_gcr_resource_id(
1189
+ def _parse_gar_resource_id(
1097
1190
  self,
1098
1191
  resource_id: str,
1099
- ) -> str:
1100
- """Validate and convert an GCR resource ID to an GCR registry ID.
1192
+ ) -> Tuple[str, Optional[str]]:
1193
+ """Validate and convert a GAR resource ID to a Google Artifact Registry ID and name.
1101
1194
 
1102
1195
  Args:
1103
1196
  resource_id: The resource ID to convert.
1104
1197
 
1105
1198
  Returns:
1106
- The GCR registry ID.
1199
+ The Google Artifact Registry ID and name. The name is omitted if the
1200
+ resource ID is a GCR repository URI.
1107
1201
 
1108
1202
  Raises:
1109
- ValueError: If the provided resource ID is not a valid GCR
1110
- repository URI.
1203
+ ValueError: If the provided resource ID is not a valid GAR
1204
+ or GCR repository URI.
1111
1205
  """
1112
1206
  # The resource ID could mean different things:
1113
1207
  #
1114
- # - an GCR repository URI
1208
+ # - a GAR repository URI
1209
+ # - a GAR repository name
1210
+ # - a GCR repository URI (backwards-compatibility)
1115
1211
  #
1116
1212
  # We need to extract the project ID and registry ID from
1117
1213
  # the provided resource ID
1118
- config_project_id = self.config.project_id
1214
+ config_project_id = self.config.gcp_project_id
1119
1215
  project_id: Optional[str] = None
1120
- # A GCR repository URI uses one of several hostnames (gcr.io, us.gcr.io,
1121
- # eu.gcr.io, asia.gcr.io etc.) and the project ID is the first part of
1122
- # the URL path
1123
- if re.match(
1124
- r"^(https://)?([a-z]+.)*gcr.io/[a-z0-9-]+(/.+)*$",
1216
+ canonical_url: str
1217
+ registry_name: Optional[str] = None
1218
+
1219
+ # A Google Artifact Registry URI uses the <location>-docker-pkg.dev
1220
+ # domain format with the project ID as the first part of the URL path
1221
+ # and the registry name as the second part of the URL path
1222
+ if match := re.match(
1223
+ r"^(https://)?(([a-z0-9-]+)-docker\.pkg\.dev/([a-z0-9-]+)/([a-z0-9-.]+))(/.+)*$",
1125
1224
  resource_id,
1126
1225
  ):
1127
- # The resource ID is a GCR repository URI
1128
- if resource_id.startswith("https://"):
1129
- project_id = resource_id.split("/")[3]
1130
- else:
1131
- project_id = resource_id.split("/")[1]
1226
+ # The resource ID is a Google Artifact Registry URI
1227
+ project_id = match[4]
1228
+ location = match[3]
1229
+ repository = match[5]
1230
+
1231
+ # Return the GAR URL without the image name and without the protocol
1232
+ canonical_url = match[2]
1233
+ registry_name = f"projects/{project_id}/locations/{location}/repositories/{repository}"
1234
+
1235
+ # Alternatively, the Google Artifact Registry name uses the
1236
+ # projects/<project-id>/locations/<location>/repositories/<repository-id>
1237
+ # format
1238
+ elif match := re.match(
1239
+ r"^projects/([a-z0-9-]+)/locations/([a-z0-9-]+)/repositories/([a-z0-9-.]+)$",
1240
+ resource_id,
1241
+ ):
1242
+ # The resource ID is a Google Artifact Registry name
1243
+ project_id = match[1]
1244
+ location = match[2]
1245
+ repository = match[3]
1246
+
1247
+ # Return the GAR URL
1248
+ canonical_url = (
1249
+ f"{location}-docker.pkg.dev/{project_id}/{repository}"
1250
+ )
1251
+ registry_name = resource_id
1252
+
1253
+ # A legacy GCR repository URI uses one of several hostnames (gcr.io,
1254
+ # us.gcr.io, eu.gcr.io, asia.gcr.io) and the project ID is the
1255
+ # first part of the URL path
1256
+ elif match := re.match(
1257
+ r"^(https://)?(((us|eu|asia)\.)?gcr\.io/[a-z0-9-]+)(/.+)*$",
1258
+ resource_id,
1259
+ ):
1260
+ # The resource ID is a legacy GCR repository URI.
1261
+ # Return the GAR URL without the image name and without the protocol
1262
+ canonical_url = match[2]
1263
+
1132
1264
  else:
1133
1265
  raise ValueError(
1134
- f"Invalid resource ID for a GCR registry: {resource_id}. "
1135
- f"Supported formats are:\n"
1266
+ f"Invalid resource ID for a Google Artifact Registry: "
1267
+ f"{resource_id}. Supported formats are:\n"
1268
+ f"Google Artifact Registry URI: [https://]<region>-docker.pkg.dev/<project-id>/<registry-id>[/<repository-name>]\n"
1269
+ f"Google Artifact Registry name: projects/<project-id>/locations/<location>/repositories/<repository-id>\n"
1136
1270
  f"GCR repository URI: [https://][us.|eu.|asia.]gcr.io/<project-id>[/<repository-name>]"
1137
1271
  )
1138
1272
 
1139
1273
  # If the connector is configured with a project and the resource ID
1140
- # is an GCR repository URI that specifies a different project,
1274
+ # is a GAR repository URI that specifies a different project,
1141
1275
  # we raise an error
1142
1276
  if project_id and project_id != config_project_id:
1143
1277
  raise ValueError(
1144
- f"The GCP project for the {resource_id} GCR repository "
1145
- f"'{project_id}' does not match the project configured in "
1146
- f"the connector: '{config_project_id}'."
1278
+ f"The GCP project for the {resource_id} Google Artifact "
1279
+ f"Registry '{project_id}' does not match the project "
1280
+ f"configured in the connector: '{config_project_id}'."
1147
1281
  )
1148
1282
 
1149
- return f"gcr.io/{project_id}"
1283
+ return canonical_url, registry_name
1150
1284
 
1151
1285
  def _parse_gke_resource_id(self, resource_id: str) -> str:
1152
1286
  """Validate and convert an GKE resource ID to a GKE cluster name.
@@ -1195,7 +1329,7 @@ class GCPServiceConnector(ServiceConnector):
1195
1329
  cluster_name = self._parse_gke_resource_id(resource_id)
1196
1330
  return cluster_name
1197
1331
  elif resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
1198
- registry_id = self._parse_gcr_resource_id(
1332
+ registry_id, _ = self._parse_gar_resource_id(
1199
1333
  resource_id,
1200
1334
  )
1201
1335
  return registry_id
@@ -1219,9 +1353,7 @@ class GCPServiceConnector(ServiceConnector):
1219
1353
  authorized.
1220
1354
  """
1221
1355
  if resource_type == GCP_RESOURCE_TYPE:
1222
- return self.config.project_id
1223
- elif resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
1224
- return f"gcr.io/{self.config.project_id}"
1356
+ return self.config.gcp_project_id
1225
1357
 
1226
1358
  raise RuntimeError(
1227
1359
  f"Default resource ID not supported for '{resource_type}' resource "
@@ -1278,7 +1410,7 @@ class GCPServiceConnector(ServiceConnector):
1278
1410
 
1279
1411
  # Create an GCS client for the bucket
1280
1412
  client = storage.Client(
1281
- project=self.config.project_id, credentials=credentials
1413
+ project=self.config.gcp_project_id, credentials=credentials
1282
1414
  )
1283
1415
  return client
1284
1416
 
@@ -1384,7 +1516,7 @@ class GCPServiceConnector(ServiceConnector):
1384
1516
  "config",
1385
1517
  "set",
1386
1518
  "project",
1387
- self.config.project_id,
1519
+ self.config.gcp_project_id,
1388
1520
  ],
1389
1521
  check=True,
1390
1522
  stderr=subprocess.STDOUT,
@@ -1488,7 +1620,7 @@ class GCPServiceConnector(ServiceConnector):
1488
1620
  )
1489
1621
 
1490
1622
  if auth_method == GCPAuthenticationMethods.IMPLICIT:
1491
- auth_config = GCPBaseConfig(
1623
+ auth_config = GCPBaseProjectIDConfig(
1492
1624
  project_id=project_id,
1493
1625
  )
1494
1626
  elif auth_method == GCPAuthenticationMethods.OAUTH2_TOKEN:
@@ -1697,7 +1829,7 @@ class GCPServiceConnector(ServiceConnector):
1697
1829
 
1698
1830
  if resource_type == GCS_RESOURCE_TYPE:
1699
1831
  gcs_client = storage.Client(
1700
- project=self.config.project_id, credentials=credentials
1832
+ project=self.config.gcp_project_id, credentials=credentials
1701
1833
  )
1702
1834
  if not resource_id:
1703
1835
  # List all GCS buckets
@@ -1722,11 +1854,87 @@ class GCPServiceConnector(ServiceConnector):
1722
1854
  raise AuthorizationException(msg) from e
1723
1855
 
1724
1856
  if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
1725
- assert resource_id is not None
1857
+ # Get a GAR client
1858
+ gar_client = artifactregistry_v1.ArtifactRegistryClient(
1859
+ credentials=credentials
1860
+ )
1726
1861
 
1727
- # No way to verify a GCR registry without attempting to
1728
- # connect to it via Docker/OCI, so just return the resource ID.
1729
- return [resource_id]
1862
+ if resource_id:
1863
+ registry_id, registry_name = self._parse_gar_resource_id(
1864
+ resource_id
1865
+ )
1866
+
1867
+ if registry_name is None:
1868
+ # This is a legacy GCR repository URI. We can't verify
1869
+ # the repository access without attempting to connect to it
1870
+ # via Docker/OCI, so just return the resource ID.
1871
+ return [registry_id]
1872
+
1873
+ # Check if the specified GAR registry exists
1874
+ try:
1875
+ repository = gar_client.get_repository(
1876
+ name=registry_name,
1877
+ )
1878
+ if repository.format_.name != "DOCKER":
1879
+ raise AuthorizationException(
1880
+ f"Google Artifact Registry '{resource_id}' is not a "
1881
+ "Docker registry."
1882
+ )
1883
+ return [registry_id]
1884
+ except google.api_core.exceptions.GoogleAPIError as e:
1885
+ msg = f"Failed to fetch Google Artifact Registry '{registry_id}': {e}"
1886
+ logger.error(msg)
1887
+ raise AuthorizationException(msg) from e
1888
+
1889
+ # For backwards compatibility, we initialize the list of resource
1890
+ # IDs with all GCR supported registries for the configured GCP
1891
+ # project
1892
+ resource_ids: List[str] = [
1893
+ f"{location}gcr.io/{self.config.gcp_project_id}"
1894
+ for location in ["", "us.", "eu.", "asia."]
1895
+ ]
1896
+
1897
+ # List all Google Artifact Registries
1898
+ try:
1899
+ # First, we need to fetch all the Artifact Registry supported
1900
+ # locations
1901
+ locations = gar_client.list_locations(
1902
+ request=locations_pb2.ListLocationsRequest(
1903
+ name=f"projects/{self.config.gcp_project_id}"
1904
+ )
1905
+ )
1906
+ location_names = [
1907
+ locations.locations[i].location_id
1908
+ for i in range(len(locations.locations))
1909
+ ]
1910
+
1911
+ # Then, we need to fetch all the repositories in each location
1912
+ repository_names: List[str] = []
1913
+ for location in location_names:
1914
+ repositories = gar_client.list_repositories(
1915
+ parent=f"projects/{self.config.gcp_project_id}/locations/{location}"
1916
+ )
1917
+ repository_names.extend(
1918
+ [
1919
+ repository.name
1920
+ for repository in repositories
1921
+ if repository.format_.name == "DOCKER"
1922
+ ]
1923
+ )
1924
+
1925
+ for repository_name in repository_names:
1926
+ # Convert the repository name to a canonical GAR URL
1927
+ resource_ids.append(
1928
+ self._parse_gar_resource_id(repository_name)[0]
1929
+ )
1930
+
1931
+ except google.api_core.exceptions.GoogleAPIError as e:
1932
+ msg = f"Failed to list Google Artifact Registries: {e}"
1933
+ logger.error(msg)
1934
+ # TODO: enable when GCR is no longer supported:
1935
+ # raise AuthorizationException(msg) from e
1936
+
1937
+ return resource_ids
1730
1938
 
1731
1939
  if resource_type == KUBERNETES_CLUSTER_RESOURCE_TYPE:
1732
1940
  gke_client = container_v1.ClusterManagerClient(
@@ -1736,7 +1944,7 @@ class GCPServiceConnector(ServiceConnector):
1736
1944
  # List all GKE clusters
1737
1945
  try:
1738
1946
  clusters = gke_client.list_clusters(
1739
- parent=f"projects/{self.config.project_id}/locations/-"
1947
+ parent=f"projects/{self.config.gcp_project_id}/locations/-"
1740
1948
  )
1741
1949
  cluster_names = [cluster.name for cluster in clusters.clusters]
1742
1950
  except google.api_core.exceptions.GoogleAPIError as e:
@@ -1810,7 +2018,7 @@ class GCPServiceConnector(ServiceConnector):
1810
2018
  # object
1811
2019
  auth_method: str = GCPAuthenticationMethods.OAUTH2_TOKEN
1812
2020
  config: GCPBaseConfig = GCPOAuth2TokenConfig(
1813
- project_id=self.config.project_id,
2021
+ project_id=self.config.gcp_project_id,
1814
2022
  token=credentials.token,
1815
2023
  service_account_email=credentials.signer_email
1816
2024
  if hasattr(credentials, "signer_email")
@@ -1855,7 +2063,7 @@ class GCPServiceConnector(ServiceConnector):
1855
2063
  if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
1856
2064
  assert resource_id is not None
1857
2065
 
1858
- registry_id = self._parse_gcr_resource_id(resource_id)
2066
+ registry_id, _ = self._parse_gar_resource_id(resource_id)
1859
2067
 
1860
2068
  # Create a client-side Docker connector instance with the temporary
1861
2069
  # Docker credentials
@@ -1884,7 +2092,7 @@ class GCPServiceConnector(ServiceConnector):
1884
2092
  # List all GKE clusters
1885
2093
  try:
1886
2094
  clusters = gke_client.list_clusters(
1887
- parent=f"projects/{self.config.project_id}/locations/-"
2095
+ parent=f"projects/{self.config.gcp_project_id}/locations/-"
1888
2096
  )
1889
2097
  cluster_map = {
1890
2098
  cluster.name: cluster for cluster in clusters.clusters
@@ -1928,7 +2136,7 @@ class GCPServiceConnector(ServiceConnector):
1928
2136
  auth_method=KubernetesAuthenticationMethods.TOKEN,
1929
2137
  resource_type=resource_type,
1930
2138
  config=KubernetesTokenConfig(
1931
- cluster_name=f"gke_{self.config.project_id}_{cluster_name}",
2139
+ cluster_name=f"gke_{self.config.gcp_project_id}_{cluster_name}",
1932
2140
  certificate_authority=cluster_ca_cert,
1933
2141
  server=f"https://{cluster_server}",
1934
2142
  token=bearer_token,
@@ -39,6 +39,7 @@ class HuggingfaceIntegration(Integration):
39
39
  # TODO try relaxing it back going forward
40
40
  "fsspec<=2023.12.0",
41
41
  ]
42
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = ["fsspec"]
42
43
 
43
44
  @classmethod
44
45
  def activate(cls) -> None:
@@ -60,6 +60,7 @@ class Integration(metaclass=IntegrationMeta):
60
60
 
61
61
  REQUIREMENTS: List[str] = []
62
62
  APT_PACKAGES: List[str] = []
63
+ REQUIREMENTS_IGNORED_ON_UNINSTALL: List[str] = []
63
64
 
64
65
  @classmethod
65
66
  def check_installation(cls) -> bool:
@@ -143,6 +144,29 @@ class Integration(metaclass=IntegrationMeta):
143
144
  """
144
145
  return cls.REQUIREMENTS
145
146
 
147
+ @classmethod
148
+ def get_uninstall_requirements(
149
+ cls, target_os: Optional[str] = None
150
+ ) -> List[str]:
151
+ """Method to get the uninstall requirements for the integration.
152
+
153
+ Args:
154
+ target_os: The target operating system to get the requirements for.
155
+
156
+ Returns:
157
+ A list of requirements.
158
+ """
159
+ ret = []
160
+ for each in cls.get_requirements(target_os=target_os):
161
+ is_ignored = False
162
+ for ignored in cls.REQUIREMENTS_IGNORED_ON_UNINSTALL:
163
+ if each.startswith(ignored):
164
+ is_ignored = True
165
+ break
166
+ if not is_ignored:
167
+ ret.append(each)
168
+ return ret
169
+
146
170
  @classmethod
147
171
  def activate(cls) -> None:
148
172
  """Abstract method to activate the integration."""
@@ -31,6 +31,9 @@ class KubeflowIntegration(Integration):
31
31
 
32
32
  NAME = KUBEFLOW
33
33
  REQUIREMENTS = ["kfp>=2.6.0", "kfp-kubernetes>=1.1.0"] # Only 1.x version that supports pyyaml 6
34
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = [
35
+ "kfp", # it is used by GCP as well
36
+ ]
34
37
 
35
38
  @classmethod
36
39
  def flavors(cls) -> List[Type[Flavor]]:
@@ -149,7 +149,7 @@ class KubeflowOrchestratorConfig(
149
149
 
150
150
  kubeflow_hostname: Optional[str] = None
151
151
  kubeflow_namespace: str = "kubeflow"
152
- kubernetes_context: Optional[str] # TODO: Potential setting
152
+ kubernetes_context: Optional[str] = None # TODO: Potential setting
153
153
 
154
154
  @model_validator(mode="before")
155
155
  @classmethod
@@ -433,7 +433,6 @@ class KubeflowOrchestrator(ContainerizedOrchestrator):
433
433
  The dynamic container component.
434
434
  """
435
435
 
436
- @dsl.container_component # type: ignore[misc]
437
436
  def dynamic_container_component() -> dsl.ContainerSpec:
438
437
  """Dynamic container component.
439
438
 
@@ -31,7 +31,9 @@ class KubernetesIntegration(Integration):
31
31
 
32
32
  NAME = KUBERNETES
33
33
  REQUIREMENTS = ["kubernetes>=21.7,<26"]
34
-
34
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = [
35
+ "kfp", # it is used by many others
36
+ ]
35
37
  @classmethod
36
38
  def flavors(cls) -> List[Type[Flavor]]:
37
39
  """Declare the stack component flavors for the Kubernetes integration.
@@ -225,8 +225,11 @@ def wait_pod(
225
225
  response = core_api.read_namespaced_pod_log(
226
226
  name=pod_name,
227
227
  namespace=namespace,
228
+ _preload_content=False,
228
229
  )
229
- logs = response.splitlines()
230
+ raw_data = response.data
231
+ decoded_log = raw_data.decode("utf-8", errors="replace")
232
+ logs = decoded_log.splitlines()
230
233
  if len(logs) > logged_lines:
231
234
  for line in logs[logged_lines:]:
232
235
  logger.info(line)
@@ -527,6 +527,7 @@ class LabelStudioAnnotator(BaseAnnotator, AuthenticationMixin):
527
527
  aws_access_key_id,
528
528
  aws_secret_access_key,
529
529
  aws_session_token,
530
+ _,
530
531
  ) = artifact_store.get_credentials()
531
532
 
532
533
  if aws_access_key_id and aws_secret_access_key:
@@ -30,6 +30,7 @@ class LangchainIntegration(Integration):
30
30
  "pyyaml>=6.0.1",
31
31
  "tenacity!=8.4.0", # https://github.com/jd/tenacity/issues/471
32
32
  ]
33
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = ["pyyaml","tenacity"]
33
34
 
34
35
  @classmethod
35
36
  def activate(cls) -> None:
@@ -45,9 +45,11 @@ class MlflowIntegration(Integration):
45
45
  # This downgrades pydantic to v1 even though mlflow does not have
46
46
  # any issues with v2. This is why we have to pin it here so a downgrade
47
47
  # will not happen.
48
- "pydantic>=2.7.0,<2.8.0"
48
+ "pydantic>=2.7.0,<2.8.0",
49
49
  ]
50
50
 
51
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = ["python-rapidjson", "pydantic"]
52
+
51
53
  @classmethod
52
54
  def activate(cls) -> None:
53
55
  """Activate the MLflow integration."""
@@ -26,6 +26,7 @@ class NeuralProphetIntegration(Integration):
26
26
  "holidays>=0.4.1,<0.25.0",
27
27
  "tenacity!=8.4.0", # https://github.com/jd/tenacity/issues/471
28
28
  ]
29
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = ["tenacity"]
29
30
 
30
31
  @classmethod
31
32
  def activate(cls) -> None:
@@ -25,6 +25,7 @@ class PolarsIntegration(Integration):
25
25
  "polars>=0.19.5",
26
26
  "pyarrow>=12.0.0"
27
27
  ]
28
+ REQUIREMENTS_IGNORED_ON_UNINSTALL = ["pyarrow"]
28
29
 
29
30
  @classmethod
30
31
  def activate(cls) -> None: