zenml-nightly 0.61.0.dev20240714__py3-none-any.whl → 0.62.0.dev20240719__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 (133) hide show
  1. README.md +1 -1
  2. RELEASE_NOTES.md +40 -0
  3. zenml/VERSION +1 -1
  4. zenml/__init__.py +2 -0
  5. zenml/cli/stack.py +87 -228
  6. zenml/cli/stack_components.py +5 -3
  7. zenml/constants.py +2 -0
  8. zenml/entrypoints/entrypoint.py +3 -1
  9. zenml/integrations/__init__.py +1 -0
  10. zenml/integrations/constants.py +1 -0
  11. zenml/integrations/databricks/__init__.py +52 -0
  12. zenml/integrations/databricks/flavors/__init__.py +30 -0
  13. zenml/integrations/databricks/flavors/databricks_model_deployer_flavor.py +118 -0
  14. zenml/integrations/databricks/flavors/databricks_orchestrator_flavor.py +147 -0
  15. zenml/integrations/databricks/model_deployers/__init__.py +20 -0
  16. zenml/integrations/databricks/model_deployers/databricks_model_deployer.py +249 -0
  17. zenml/integrations/databricks/orchestrators/__init__.py +20 -0
  18. zenml/integrations/databricks/orchestrators/databricks_orchestrator.py +498 -0
  19. zenml/integrations/databricks/orchestrators/databricks_orchestrator_entrypoint_config.py +97 -0
  20. zenml/integrations/databricks/services/__init__.py +19 -0
  21. zenml/integrations/databricks/services/databricks_deployment.py +407 -0
  22. zenml/integrations/databricks/utils/__init__.py +14 -0
  23. zenml/integrations/databricks/utils/databricks_utils.py +87 -0
  24. zenml/integrations/great_expectations/data_validators/ge_data_validator.py +12 -8
  25. zenml/integrations/huggingface/materializers/huggingface_datasets_materializer.py +88 -3
  26. zenml/integrations/huggingface/steps/accelerate_runner.py +1 -7
  27. zenml/integrations/kubernetes/orchestrators/manifest_utils.py +7 -0
  28. zenml/integrations/kubernetes/pod_settings.py +2 -0
  29. zenml/integrations/lightgbm/__init__.py +1 -0
  30. zenml/integrations/mlflow/__init__.py +1 -1
  31. zenml/integrations/mlflow/model_registries/mlflow_model_registry.py +6 -2
  32. zenml/integrations/mlflow/services/mlflow_deployment.py +1 -1
  33. zenml/integrations/skypilot_lambda/__init__.py +1 -1
  34. zenml/materializers/built_in_materializer.py +1 -1
  35. zenml/materializers/cloudpickle_materializer.py +1 -1
  36. zenml/model/model.py +1 -1
  37. zenml/models/v2/core/component.py +29 -0
  38. zenml/models/v2/misc/full_stack.py +32 -0
  39. zenml/orchestrators/__init__.py +4 -0
  40. zenml/orchestrators/wheeled_orchestrator.py +147 -0
  41. zenml/service_connectors/service_connector_utils.py +349 -0
  42. zenml/stack_deployments/gcp_stack_deployment.py +2 -4
  43. zenml/steps/base_step.py +7 -5
  44. zenml/utils/function_utils.py +1 -1
  45. zenml/utils/pipeline_docker_image_builder.py +8 -0
  46. zenml/zen_server/dashboard/assets/{404-DpJaNHKF.js → 404-B_YdvmwS.js} +1 -1
  47. zenml/zen_server/dashboard/assets/{@reactflow-DJfzkHO1.js → @reactflow-l_1hUr1S.js} +1 -1
  48. zenml/zen_server/dashboard/assets/{AwarenessChannel-BYDLT2xC.js → AwarenessChannel-CFg5iX4Z.js} +1 -1
  49. zenml/zen_server/dashboard/assets/{CodeSnippet-BkOuRmyq.js → CodeSnippet-Dvkx_82E.js} +1 -1
  50. zenml/zen_server/dashboard/assets/CollapsibleCard-opiuBHHc.js +1 -0
  51. zenml/zen_server/dashboard/assets/{Commands-ZvWR1BRs.js → Commands-DoN1xrEq.js} +1 -1
  52. zenml/zen_server/dashboard/assets/{CopyButton-DVwLkafa.js → CopyButton-Cr7xYEPb.js} +1 -1
  53. zenml/zen_server/dashboard/assets/{CsvVizualization-C2IiqX4I.js → CsvVizualization-Ck-nZ43m.js} +3 -3
  54. zenml/zen_server/dashboard/assets/{Error-CqX0VqW_.js → Error-kLtljEOM.js} +1 -1
  55. zenml/zen_server/dashboard/assets/{ExecutionStatus-BoLUXR9t.js → ExecutionStatus-DguLLgTK.js} +1 -1
  56. zenml/zen_server/dashboard/assets/{Helpbox-LFydyVwh.js → Helpbox-BXUMP21n.js} +1 -1
  57. zenml/zen_server/dashboard/assets/{Infobox-DnENC0sh.js → Infobox-DSt0O-dm.js} +1 -1
  58. zenml/zen_server/dashboard/assets/{InlineAvatar-CbJtYr0t.js → InlineAvatar-xsrsIGE-.js} +1 -1
  59. zenml/zen_server/dashboard/assets/Pagination-C6X-mifw.js +1 -0
  60. zenml/zen_server/dashboard/assets/{SetPassword-BYBdbQDo.js → SetPassword-BXGTWiwj.js} +1 -1
  61. zenml/zen_server/dashboard/assets/{SuccessStep-Nx743hll.js → SuccessStep-DZC60t0x.js} +1 -1
  62. zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DF9gSzE0.js → UpdatePasswordSchemas-DGvwFWO1.js} +1 -1
  63. zenml/zen_server/dashboard/assets/{chevron-right-double-BiEMg7rd.js → chevron-right-double-CZBOf6JM.js} +1 -1
  64. zenml/zen_server/dashboard/assets/cloud-only-C_yFCAkP.js +1 -0
  65. zenml/zen_server/dashboard/assets/index-BczVOqUf.js +55 -0
  66. zenml/zen_server/dashboard/assets/index-EpMIKgrI.css +1 -0
  67. zenml/zen_server/dashboard/assets/{login-mutation-BUnVASxp.js → login-mutation-CrHrndTI.js} +1 -1
  68. zenml/zen_server/dashboard/assets/logs-D8k8BVFf.js +1 -0
  69. zenml/zen_server/dashboard/assets/{not-found-B4VnX8gK.js → not-found-DYa4pC-C.js} +1 -1
  70. zenml/zen_server/dashboard/assets/{package-CsUhPmou.js → package-B3fWP-Dh.js} +1 -1
  71. zenml/zen_server/dashboard/assets/page-1h_sD1jz.js +1 -0
  72. zenml/zen_server/dashboard/assets/{page-Sxn82W-5.js → page-1iL8aMqs.js} +1 -1
  73. zenml/zen_server/dashboard/assets/{page-DMOYZppS.js → page-2grKx_MY.js} +1 -1
  74. zenml/zen_server/dashboard/assets/page-5NCOHOsy.js +1 -0
  75. zenml/zen_server/dashboard/assets/{page-JyfeDUfu.js → page-8a4UMKXZ.js} +1 -1
  76. zenml/zen_server/dashboard/assets/{page-Bx6o0ARS.js → page-B6h3iaHJ.js} +1 -1
  77. zenml/zen_server/dashboard/assets/page-BDns21Iz.js +1 -0
  78. zenml/zen_server/dashboard/assets/{page-3efNCDeb.js → page-BhgCDInH.js} +2 -2
  79. zenml/zen_server/dashboard/assets/{page-DKlIdAe5.js → page-Bi-wtWiO.js} +2 -2
  80. zenml/zen_server/dashboard/assets/{page-7zTHbhhI.js → page-BkeAAYwp.js} +1 -1
  81. zenml/zen_server/dashboard/assets/{page-CRTJ0UuR.js → page-BkuQDIf-.js} +1 -1
  82. zenml/zen_server/dashboard/assets/page-BnaevhnB.js +1 -0
  83. zenml/zen_server/dashboard/assets/{page-BEs6jK71.js → page-Bq0YxkLV.js} +1 -1
  84. zenml/zen_server/dashboard/assets/page-Bs2F4eoD.js +2 -0
  85. zenml/zen_server/dashboard/assets/{page-CUZIGO-3.js → page-C6-UGEbH.js} +1 -1
  86. zenml/zen_server/dashboard/assets/{page-Xu8JEjSU.js → page-CCNRIt_f.js} +1 -1
  87. zenml/zen_server/dashboard/assets/{page-DvCvroOM.js → page-CHNxpz3n.js} +1 -1
  88. zenml/zen_server/dashboard/assets/{page-BpSqIf4B.js → page-DgorQFqi.js} +1 -1
  89. zenml/zen_server/dashboard/assets/page-K8ebxVIs.js +1 -0
  90. zenml/zen_server/dashboard/assets/{page-Cx67M0QT.js → page-MFQyIJd3.js} +1 -1
  91. zenml/zen_server/dashboard/assets/page-TgCF0P_U.js +1 -0
  92. zenml/zen_server/dashboard/assets/page-ZnCEe-eK.js +9 -0
  93. zenml/zen_server/dashboard/assets/{page-Dc_7KMQE.js → page-uA5prJGY.js} +1 -1
  94. zenml/zen_server/dashboard/assets/persist-D7HJNBWx.js +1 -0
  95. zenml/zen_server/dashboard/assets/plus-C8WOyCzt.js +1 -0
  96. zenml/zen_server/dashboard/assets/stack-detail-query-Cficsl6d.js +1 -0
  97. zenml/zen_server/dashboard/assets/update-server-settings-mutation-7d8xi1tS.js +1 -0
  98. zenml/zen_server/dashboard/assets/{url-DuQMeqYA.js → url-D7mAQGUM.js} +1 -1
  99. zenml/zen_server/dashboard/index.html +4 -4
  100. zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
  101. zenml/zen_server/dashboard_legacy/index.html +1 -1
  102. zenml/zen_server/dashboard_legacy/{precache-manifest.c8c57fb0d2132b1d3c2119e776b7dfb3.js → precache-manifest.12246c7548e71e2c4438e496360de80c.js} +4 -4
  103. zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
  104. zenml/zen_server/dashboard_legacy/static/js/main.3b27024b.chunk.js +2 -0
  105. zenml/zen_server/dashboard_legacy/static/js/{main.382439a7.chunk.js.map → main.3b27024b.chunk.js.map} +1 -1
  106. zenml/zen_server/deploy/helm/Chart.yaml +1 -1
  107. zenml/zen_server/deploy/helm/README.md +2 -2
  108. zenml/zen_server/routers/service_connectors_endpoints.py +57 -0
  109. zenml/zen_stores/migrations/versions/0.62.0_release.py +23 -0
  110. zenml/zen_stores/rest_zen_store.py +4 -0
  111. zenml/zen_stores/schemas/component_schemas.py +14 -0
  112. {zenml_nightly-0.61.0.dev20240714.dist-info → zenml_nightly-0.62.0.dev20240719.dist-info}/METADATA +2 -2
  113. {zenml_nightly-0.61.0.dev20240714.dist-info → zenml_nightly-0.62.0.dev20240719.dist-info}/RECORD +116 -98
  114. zenml/zen_server/dashboard/assets/Pagination-DEbVUupy.js +0 -1
  115. zenml/zen_server/dashboard/assets/chevron-down-D_ZlKMqH.js +0 -1
  116. zenml/zen_server/dashboard/assets/cloud-only-DVbIeckv.js +0 -1
  117. zenml/zen_server/dashboard/assets/index-C_CrU4vI.js +0 -1
  118. zenml/zen_server/dashboard/assets/index-DK1ynKjA.js +0 -55
  119. zenml/zen_server/dashboard/assets/index-inApY3KQ.css +0 -1
  120. zenml/zen_server/dashboard/assets/page-C43QGHTt.js +0 -9
  121. zenml/zen_server/dashboard/assets/page-CR0OG7ss.js +0 -1
  122. zenml/zen_server/dashboard/assets/page-CaopxiU1.js +0 -1
  123. zenml/zen_server/dashboard/assets/page-D7Z399xy.js +0 -1
  124. zenml/zen_server/dashboard/assets/page-D93kd7Xj.js +0 -1
  125. zenml/zen_server/dashboard/assets/page-DMsSn3dv.js +0 -2
  126. zenml/zen_server/dashboard/assets/page-Hus2pr9T.js +0 -1
  127. zenml/zen_server/dashboard/assets/page-TKXERe16.js +0 -1
  128. zenml/zen_server/dashboard/assets/plus-DOeLmm7C.js +0 -1
  129. zenml/zen_server/dashboard/assets/update-server-settings-mutation-CR8e3Sir.js +0 -1
  130. zenml/zen_server/dashboard_legacy/static/js/main.382439a7.chunk.js +0 -2
  131. {zenml_nightly-0.61.0.dev20240714.dist-info → zenml_nightly-0.62.0.dev20240719.dist-info}/LICENSE +0 -0
  132. {zenml_nightly-0.61.0.dev20240714.dist-info → zenml_nightly-0.62.0.dev20240719.dist-info}/WHEEL +0 -0
  133. {zenml_nightly-0.61.0.dev20240714.dist-info → zenml_nightly-0.62.0.dev20240719.dist-info}/entry_points.txt +0 -0
@@ -33,6 +33,7 @@ class KubernetesPodSettings(BaseSettings):
33
33
  volumes: Volumes to mount in the pod.
34
34
  volume_mounts: Volume mounts to apply to the pod containers.
35
35
  host_ipc: Whether to enable host IPC for the pod.
36
+ image_pull_secrets: Image pull secrets to use for the pod.
36
37
  """
37
38
 
38
39
  node_selectors: Dict[str, str] = {}
@@ -43,6 +44,7 @@ class KubernetesPodSettings(BaseSettings):
43
44
  volumes: List[Dict[str, Any]] = []
44
45
  volume_mounts: List[Dict[str, Any]] = []
45
46
  host_ipc: bool = False
47
+ image_pull_secrets: List[str] = []
46
48
 
47
49
  @field_validator("volumes", mode="before")
48
50
  @classmethod
@@ -22,6 +22,7 @@ class LightGBMIntegration(Integration):
22
22
 
23
23
  NAME = LIGHTGBM
24
24
  REQUIREMENTS = ["lightgbm>=1.0.0"]
25
+ APT_PACKAGES = ["libgomp1"]
25
26
 
26
27
  @classmethod
27
28
  def activate(cls) -> None:
@@ -33,7 +33,7 @@ class MlflowIntegration(Integration):
33
33
  NAME = MLFLOW
34
34
 
35
35
  REQUIREMENTS = [
36
- "mlflow>=2.1.1,<=2.14.1",
36
+ "mlflow>=2.1.1,<=2.14.2",
37
37
  "mlserver>=1.3.3",
38
38
  "mlserver-mlflow>=1.3.3",
39
39
  # TODO: remove this requirement once rapidjson is fixed
@@ -371,7 +371,7 @@ class MLFlowModelRegistry(BaseModelRegistry):
371
371
  self.get_model(name=name)
372
372
  except KeyError:
373
373
  logger.info(
374
- f"No registered model with name {name} found. Creating a new"
374
+ f"No registered model with name {name} found. Creating a new "
375
375
  "registered model."
376
376
  )
377
377
  self.register_model(
@@ -615,7 +615,11 @@ class MLFlowModelRegistry(BaseModelRegistry):
615
615
  for mlflow_model_version in mlflow_model_versions:
616
616
  # check if given MlFlow model version matches the given request
617
617
  # before casting it
618
- if stage and not mlflow_model_version.current_stage == str(stage):
618
+ if (
619
+ stage
620
+ and not ModelVersionStage(mlflow_model_version.current_stage)
621
+ == stage
622
+ ):
619
623
  continue
620
624
  if created_after and not (
621
625
  mlflow_model_version.creation_timestamp
@@ -260,7 +260,7 @@ class MLFlowDeploymentService(LocalDaemonService, BaseDeploymentService):
260
260
  )
261
261
 
262
262
  if self.endpoint.prediction_url is not None:
263
- if type(request) == pd.DataFrame:
263
+ if type(request) is pd.DataFrame:
264
264
  response = requests.post( # nosec
265
265
  self.endpoint.prediction_url,
266
266
  json={"instances": request.to_dict("records")},
@@ -31,7 +31,7 @@ class SkypilotLambdaIntegration(Integration):
31
31
  """Definition of Skypilot Lambda Integration for ZenML."""
32
32
 
33
33
  NAME = SKYPILOT_LAMBDA
34
- REQUIREMENTS = ["skypilot[lambda]<=0.6.0"]
34
+ REQUIREMENTS = ["skypilot[lambda]~=0.6.0"]
35
35
 
36
36
  @classmethod
37
37
  def flavors(cls) -> List[Type[Flavor]]:
@@ -80,7 +80,7 @@ class BuiltInMaterializer(BaseMaterializer):
80
80
  The data read.
81
81
  """
82
82
  contents = yaml_utils.read_json(self.data_path)
83
- if type(contents) != data_type:
83
+ if type(contents) is not data_type:
84
84
  # TODO [ENG-142]: Raise error or try to coerce
85
85
  logger.debug(
86
86
  f"Contents {contents} was type {type(contents)} but expected "
@@ -94,7 +94,7 @@ class CloudpickleMaterializer(BaseMaterializer):
94
94
  """
95
95
  # Log a warning if this materializer was not explicitly specified for
96
96
  # the given data type.
97
- if type(self) == CloudpickleMaterializer:
97
+ if type(self) is CloudpickleMaterializer:
98
98
  logger.warning(
99
99
  f"No materializer is registered for type `{type(data)}`, so "
100
100
  "the default Pickle materializer was used. Pickle is not "
zenml/model/model.py CHANGED
@@ -518,7 +518,7 @@ class Model(BaseModel):
518
518
  and not suppress_class_validation_warnings
519
519
  ):
520
520
  logger.info(
521
- f"`version` `{version}` matches one of the possible "
521
+ f"Version `{version}` matches one of the possible "
522
522
  "`ModelStages` and will be fetched using stage."
523
523
  )
524
524
  if str(version).isnumeric() and not suppress_class_validation_warnings:
@@ -191,6 +191,17 @@ class ComponentResponseBody(WorkspaceScopedResponseBody):
191
191
  title="The flavor of the stack component.",
192
192
  max_length=STR_FIELD_MAX_LENGTH,
193
193
  )
194
+ integration: Optional[str] = Field(
195
+ default=None,
196
+ title="The name of the integration that the component's flavor "
197
+ "belongs to.",
198
+ max_length=STR_FIELD_MAX_LENGTH,
199
+ )
200
+ logo_url: Optional[str] = Field(
201
+ default=None,
202
+ title="Optionally, a url pointing to a png,"
203
+ "svg or jpg can be attached.",
204
+ )
194
205
 
195
206
 
196
207
  class ComponentResponseMetadata(WorkspaceScopedResponseMetadata):
@@ -285,6 +296,24 @@ class ComponentResponse(
285
296
  """
286
297
  return self.get_body().flavor
287
298
 
299
+ @property
300
+ def integration(self) -> Optional[str]:
301
+ """The `integration` property.
302
+
303
+ Returns:
304
+ the value of the property.
305
+ """
306
+ return self.get_body().integration
307
+
308
+ @property
309
+ def logo_url(self) -> Optional[str]:
310
+ """The `logo_url` property.
311
+
312
+ Returns:
313
+ the value of the property.
314
+ """
315
+ return self.get_body().logo_url
316
+
288
317
  @property
289
318
  def configuration(self) -> Dict[str, Any]:
290
319
  """The `configuration` property.
@@ -21,6 +21,7 @@ from pydantic import BaseModel, Field, model_validator
21
21
  from zenml.constants import STR_FIELD_MAX_LENGTH
22
22
  from zenml.enums import StackComponentType
23
23
  from zenml.models.v2.base.base import BaseRequest
24
+ from zenml.models.v2.core.component import ComponentResponse
24
25
 
25
26
 
26
27
  class ServiceConnectorInfo(BaseModel):
@@ -95,3 +96,34 @@ class FullStackRequest(BaseRequest):
95
96
  "the position in the list of service connectors."
96
97
  )
97
98
  return self
99
+
100
+
101
+ class ResourcesInfo(BaseModel):
102
+ """Information about the resources needed for CLI and UI."""
103
+
104
+ flavor: str
105
+ flavor_display_name: str
106
+ required_configuration: Dict[str, str] = {}
107
+ use_resource_value_as_fixed_config: bool = False
108
+
109
+ accessible_by_service_connector: List[str]
110
+ connected_through_service_connector: List[ComponentResponse]
111
+
112
+ @model_validator(mode="after")
113
+ def _validate_resource_info(self) -> "ResourcesInfo":
114
+ if (
115
+ self.use_resource_value_as_fixed_config
116
+ and len(self.required_configuration) > 1
117
+ ):
118
+ raise ValueError(
119
+ "Cannot use resource value as fixed config if more than one required configuration key is provided."
120
+ )
121
+ return self
122
+
123
+
124
+ class ServiceConnectorResourcesInfo(BaseModel):
125
+ """Information about the service connector resources needed for CLI and UI."""
126
+
127
+ connector_type: str
128
+
129
+ components_resources_info: Dict[StackComponentType, List[ResourcesInfo]]
@@ -30,6 +30,9 @@ from zenml.orchestrators.base_orchestrator import (
30
30
  from zenml.orchestrators.containerized_orchestrator import (
31
31
  ContainerizedOrchestrator,
32
32
  )
33
+ from zenml.orchestrators.wheeled_orchestrator import (
34
+ WheeledOrchestrator,
35
+ )
33
36
  from zenml.orchestrators.local.local_orchestrator import (
34
37
  LocalOrchestrator,
35
38
  LocalOrchestratorFlavor,
@@ -44,6 +47,7 @@ __all__ = [
44
47
  "BaseOrchestratorConfig",
45
48
  "BaseOrchestratorFlavor",
46
49
  "ContainerizedOrchestrator",
50
+ "WheeledOrchestrator",
47
51
  "LocalOrchestrator",
48
52
  "LocalOrchestratorFlavor",
49
53
  "LocalDockerOrchestrator",
@@ -0,0 +1,147 @@
1
+ # Copyright (c) ZenML GmbH 2021. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at:
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12
+ # or implied. See the License for the specific language governing
13
+ # permissions and limitations under the License.
14
+ """Wheeled orchestrator class."""
15
+
16
+ import os
17
+ import re
18
+ import subprocess
19
+ import tempfile
20
+ from abc import ABC
21
+
22
+ from zenml import __version__
23
+ from zenml.io import fileio
24
+ from zenml.logger import get_logger
25
+ from zenml.orchestrators import BaseOrchestrator
26
+ from zenml.utils.io_utils import copy_dir
27
+ from zenml.utils.source_utils import get_source_root
28
+
29
+ logger = get_logger(__name__)
30
+
31
+ DEFAULT_PACKAGE_NAME = "zenmlproject"
32
+
33
+
34
+ class WheeledOrchestrator(BaseOrchestrator, ABC):
35
+ """Base class for wheeled orchestrators."""
36
+
37
+ package_name = DEFAULT_PACKAGE_NAME
38
+ package_version = __version__
39
+
40
+ def copy_repository_to_temp_dir_and_add_setup_py(self) -> str:
41
+ """Copy the repository to a temporary directory and add a setup.py file.
42
+
43
+ Returns:
44
+ Path to the temporary directory containing the copied repository.
45
+ """
46
+ repo_path = get_source_root()
47
+
48
+ self.package_name = f"{DEFAULT_PACKAGE_NAME}_{self.sanitize_name(os.path.basename(repo_path))}"
49
+
50
+ # Create a temporary folder
51
+ temp_dir = tempfile.mkdtemp(prefix="zenml-temp-")
52
+
53
+ # Create a folder within the temporary directory
54
+ temp_repo_path = os.path.join(temp_dir, self.package_name)
55
+ fileio.mkdir(temp_repo_path)
56
+
57
+ # Copy the repository to the temporary directory
58
+ copy_dir(repo_path, temp_repo_path)
59
+
60
+ # Create init file in the copied directory
61
+ init_file_path = os.path.join(temp_repo_path, "__init__.py")
62
+ with fileio.open(init_file_path, "w") as f:
63
+ f.write("")
64
+
65
+ # Create a setup.py file
66
+ setup_py_content = f"""
67
+ from setuptools import setup, find_packages
68
+
69
+ setup(
70
+ name="{self.package_name}",
71
+ version="{self.package_version}",
72
+ packages=find_packages(),
73
+ )
74
+ """
75
+ setup_py_path = os.path.join(temp_dir, "setup.py")
76
+ with fileio.open(setup_py_path, "w") as f:
77
+ f.write(setup_py_content)
78
+
79
+ return temp_dir
80
+
81
+ def create_wheel(self, temp_dir: str) -> str:
82
+ """Create a wheel for the package in the given temporary directory.
83
+
84
+ Args:
85
+ temp_dir (str): Path to the temporary directory containing the package.
86
+
87
+ Raises:
88
+ RuntimeError: If the wheel file could not be created.
89
+
90
+ Returns:
91
+ str: Path to the created wheel file.
92
+ """
93
+ # Change to the temporary directory
94
+ original_dir = os.getcwd()
95
+ os.chdir(temp_dir)
96
+
97
+ try:
98
+ # Run the `pip wheel` command to create the wheel
99
+ result = subprocess.run(
100
+ ["pip", "wheel", "."], check=True, capture_output=True
101
+ )
102
+ logger.debug(f"Wheel creation stdout: {result.stdout.decode()}")
103
+ logger.debug(f"Wheel creation stderr: {result.stderr.decode()}")
104
+
105
+ # Find the created wheel file
106
+ wheel_file = next(
107
+ (
108
+ file
109
+ for file in os.listdir(temp_dir)
110
+ if file.endswith(".whl")
111
+ ),
112
+ None,
113
+ )
114
+
115
+ if wheel_file is None:
116
+ raise RuntimeError("Failed to create wheel file.")
117
+
118
+ wheel_path = os.path.join(temp_dir, wheel_file)
119
+
120
+ # Verify the wheel file is a valid zip file
121
+ import zipfile
122
+
123
+ if not zipfile.is_zipfile(wheel_path):
124
+ raise RuntimeError(
125
+ f"The file {wheel_path} is not a valid zip file."
126
+ )
127
+
128
+ return wheel_path
129
+ finally:
130
+ # Change back to the original directory
131
+ os.chdir(original_dir)
132
+
133
+ def sanitize_name(self, name: str) -> str:
134
+ """Sanitize the value to be used in a cluster name.
135
+
136
+ Args:
137
+ name: Arbitrary input cluster name.
138
+
139
+ Returns:
140
+ Sanitized cluster name.
141
+ """
142
+ name = re.sub(
143
+ r"[^a-z0-9-]", "-", name.lower()
144
+ ) # replaces any character that is not a lowercase letter, digit, or hyphen with a hyphen
145
+ name = re.sub(r"^[-]+", "", name) # trim leading hyphens
146
+ name = re.sub(r"[-]+$", "", name) # trim trailing hyphens
147
+ return name