zenml-nightly 0.58.2.dev20240625__py3-none-any.whl → 0.60.0.dev20240627__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.
- README.md +1 -1
- RELEASE_NOTES.md +206 -0
- zenml/VERSION +1 -1
- zenml/artifact_stores/base_artifact_store.py +7 -1
- zenml/artifacts/utils.py +13 -10
- zenml/cli/service_connectors.py +1 -0
- zenml/client.py +8 -3
- zenml/integrations/gcp/google_credentials_mixin.py +1 -1
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py +64 -15
- zenml/integrations/s3/artifact_stores/s3_artifact_store.py +76 -3
- zenml/logging/step_logging.py +54 -51
- zenml/stack/stack_component.py +4 -0
- zenml/zen_server/dashboard/assets/{404-CDPQCl4D.js → 404-C1mcUujL.js} +1 -1
- zenml/zen_server/dashboard/assets/{@reactflow-CHBapDaj.js → @reactflow-DYIyhCfd.js} +1 -1
- zenml/zen_server/dashboard/assets/{AwarenessChannel-nXGpmj_f.js → AwarenessChannel-B2KR83Tr.js} +1 -1
- zenml/zen_server/dashboard/assets/{Cards-nwsvQLVS.js → Cards-DSEdjsk8.js} +1 -1
- zenml/zen_server/dashboard/assets/{CodeSnippet-BidtnWOi.js → CodeSnippet-WEzpO0az.js} +1 -1
- zenml/zen_server/dashboard/assets/{Commands-DuIWKg_Q.js → Commands-CTlhyic5.js} +1 -1
- zenml/zen_server/dashboard/assets/{CopyButton-B_YSm-Ds.js → CopyButton-CTrzKmUO.js} +1 -1
- zenml/zen_server/dashboard/assets/{CsvVizualization-BOuez-fG.js → CsvVizualization-Bx931j4U.js} +1 -1
- zenml/zen_server/dashboard/assets/{Error-B6M0dPph.js → Error-4sKxHad4.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-BQoqCm04.js → Helpbox-DW21i5LD.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-Ce9mefqU.js → Infobox-C7bf70VS.js} +1 -1
- zenml/zen_server/dashboard/assets/{InlineAvatar-DGf3dVhV.js → InlineAvatar-Dxrtafpg.js} +1 -1
- zenml/zen_server/dashboard/assets/{PageHeader-DGaemzjc.js → PageHeader-B0pUife2.js} +1 -1
- zenml/zen_server/dashboard/assets/{Pagination-DVYfBCCc.js → Pagination-B9WG_9cJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{SetPassword-B5s7DJug.js → SetPassword-CiNhT15a.js} +1 -1
- zenml/zen_server/dashboard/assets/{SuccessStep-ZzczaM7g.js → SuccessStep-CykrFndS.js} +1 -1
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-DnM-c11H.js → UpdatePasswordSchemas-CKrd3UZz.js} +1 -1
- zenml/zen_server/dashboard/assets/{cloud-only-Ba_ShBR5.js → cloud-only-Bkawp7CJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-QORVVTMN.js → index-BawkpTlr.js} +3 -3
- zenml/zen_server/dashboard/assets/index-CRmm7QhS.css +1 -0
- zenml/zen_server/dashboard/assets/{login-mutation-wzzl23C6.js → login-mutation-Bk2tn324.js} +1 -1
- zenml/zen_server/dashboard/assets/{not-found-Dh2la7kh.js → not-found-BAuhP4Jb.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DdaIt20-.js → page--5YvAHg3.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-AQKopn_4.js → page-8vRWJ5b8.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-LqLs24Ot.js → page-B0RAq4s_.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-ByrHy6Ss.js → page-BePtEPHl.js} +1 -1
- zenml/zen_server/dashboard/assets/page-C1pra1Bc.js +9 -0
- zenml/zen_server/dashboard/assets/{page-BW6Ket3a.js → page-C6v3o0Qj.js} +1 -1
- zenml/zen_server/dashboard/assets/page-CBuSUrE9.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CuT1SUik.js → page-CCtCgG-x.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-yN4rZ-ZS.js → page-CH26py0a.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-Bi5AI0S7.js → page-COafKNbw.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DAQQyLxT.js → page-CSs4C9jL.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CmmukLsl.js → page-Cf2XSej0.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BQxVFlUl.js → page-ClPUAE_f.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BmkSiYeQ.js → page-D12Rvf0j.js} +2 -2
- zenml/zen_server/dashboard/assets/{page-B0BrqfS8.js → page-D8pf2vis.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B-vWk8a6.js → page-DHKMmIQH.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-lebv0c7C.js → page-DMZ0VOda.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-CPtY4Kv_.js → page-Dcg-yQv_.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-B-5jAKoO.js → page-DoAK5FSB.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BzVZGExK.js → page-Dw9-aJV6.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DZCbwOEs.js → page-iXiDqE0J.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-0Wgz8pUE.js → update-server-settings-mutation-bKxf7U9h.js} +1 -1
- zenml/zen_server/dashboard/assets/{url-6_xv0WJS.js → url-CgvM-IVM.js} +1 -1
- zenml/zen_server/dashboard/index.html +4 -4
- zenml/zen_server/dashboard_legacy/asset-manifest.json +4 -4
- zenml/zen_server/dashboard_legacy/index.html +1 -1
- zenml/zen_server/dashboard_legacy/{precache-manifest.f4abc5b7cfa7d90c1caf5521918e29a8.js → precache-manifest.e7c29295aae591541ef59d1734d79387.js} +4 -4
- zenml/zen_server/dashboard_legacy/service-worker.js +1 -1
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js → main.53857d8b.chunk.js} +2 -2
- zenml/zen_server/dashboard_legacy/static/js/{main.ac2f17d0.chunk.js.map → main.53857d8b.chunk.js.map} +1 -1
- zenml/zen_server/deploy/helm/Chart.yaml +1 -1
- zenml/zen_server/deploy/helm/README.md +2 -2
- zenml/zen_stores/migrations/versions/0.60.0_release.py +23 -0
- {zenml_nightly-0.58.2.dev20240625.dist-info → zenml_nightly-0.60.0.dev20240627.dist-info}/METADATA +3 -3
- {zenml_nightly-0.58.2.dev20240625.dist-info → zenml_nightly-0.60.0.dev20240627.dist-info}/RECORD +72 -71
- zenml/zen_server/dashboard/assets/index-CWJ3xbIf.css +0 -1
- zenml/zen_server/dashboard/assets/page-D2D-7qyr.js +0 -9
- zenml/zen_server/dashboard/assets/page-DHkUMl_E.js +0 -1
- {zenml_nightly-0.58.2.dev20240625.dist-info → zenml_nightly-0.60.0.dev20240627.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.58.2.dev20240625.dist-info → zenml_nightly-0.60.0.dev20240627.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.58.2.dev20240625.dist-info → zenml_nightly-0.60.0.dev20240627.dist-info}/entry_points.txt +0 -0
README.md
CHANGED
@@ -306,7 +306,7 @@ the Apache License Version 2.0.
|
|
306
306
|
<a href="https://github.com/zenml-io/zenml-projects">Projects Showcase</a>
|
307
307
|
<br />
|
308
308
|
<br />
|
309
|
-
🎉 Version 0.
|
309
|
+
🎉 Version 0.60.0 is out. Check out the release notes
|
310
310
|
<a href="https://github.com/zenml-io/zenml/releases">here</a>.
|
311
311
|
<br />
|
312
312
|
🖥️ Download our VS Code Extension <a href="https://marketplace.visualstudio.com/items?itemName=ZenML.zenml-vscode">here</a>.
|
RELEASE_NOTES.md
CHANGED
@@ -1,5 +1,211 @@
|
|
1
1
|
<!-- markdown-link-check-disable -->
|
2
2
|
|
3
|
+
# 0.60.0
|
4
|
+
|
5
|
+
ZenML now uses Pydantic v2. 🥳
|
6
|
+
|
7
|
+
This upgrade comes with a set of critical updates. While your user experience
|
8
|
+
mostly remains unaffected, you might see unexpected behavior due to the
|
9
|
+
changes in our dependencies. Moreover, since Pydantic v2 provides a slightly
|
10
|
+
stricter validation process, you might end up bumping into some validation
|
11
|
+
errors which was not caught before, but it is all for the better 🙂 If
|
12
|
+
you run into any other errors, please let us know either on
|
13
|
+
[GitHub](https://github.com/zenml-io/zenml) or on
|
14
|
+
our [Slack](https://zenml.io/slack-invite).
|
15
|
+
|
16
|
+
## Changes in some of the critical dependencies
|
17
|
+
|
18
|
+
- SQLModel is one of the core dependencies of ZenML and prior to this upgrade,
|
19
|
+
we were utilizing version `0.0.8`. However, this version is relatively
|
20
|
+
outdated and incompatible with Pydantic v2. Within the scope of this upgrade,
|
21
|
+
we upgraded it to `0.0.18`.
|
22
|
+
- Due to the change in the SQLModel version, we also had to upgrade our
|
23
|
+
SQLAlchemy dependency from V1 to v2. While this does not affect the way
|
24
|
+
that you are using ZenML, if you are using SQLAlchemy in your environment,
|
25
|
+
you might have to migrate your code as well. For a detailed list of changes,
|
26
|
+
feel free to
|
27
|
+
check [their migration guide](https://docs.sqlalchemy.org/en/20/changelog/migration_20.html).
|
28
|
+
|
29
|
+
## Changes in `pydantic`
|
30
|
+
|
31
|
+
Pydantic v2 brings a lot of new and exciting changes to the table. The core
|
32
|
+
logic now uses Rust, and it is much faster and more efficient in terms of
|
33
|
+
performance. On top of it, the main concepts like model design, configuration,
|
34
|
+
validation, or serialization now include a lot of new cool features. If you are
|
35
|
+
using `pydantic` in your workflow and are interested in the new changes, you can
|
36
|
+
check [the brilliant migration guide](https://docs.pydantic.dev/2.7/migration/)
|
37
|
+
provided by the `pydantic` team to see the full list of changes.
|
38
|
+
|
39
|
+
## Changes in our integrations changes
|
40
|
+
|
41
|
+
Much like ZenML, `pydantic` is an important dependency in many other Python
|
42
|
+
packages. That’s why conducting this upgrade helped us unlock a new version for
|
43
|
+
several ZenML integration dependencies. Additionally, in some instances, we had
|
44
|
+
to adapt the functionality of the integration to keep it compatible
|
45
|
+
with `pydantic`. So, if you are using any of these integrations, please go
|
46
|
+
through the changes.
|
47
|
+
|
48
|
+
### Airflow
|
49
|
+
|
50
|
+
As mentioned above upgrading our `pydantic` dependency meant we had to upgrade
|
51
|
+
our `sqlmodel` dependency. Upgrading our `sqlmodel` dependency meant we had to
|
52
|
+
upgrade our `sqlalchemy` dependency as well. Unfortunately, `apache-airflow`
|
53
|
+
is still using `sqlalchemy` v1 and is incompatible with pydantic v2. As a
|
54
|
+
solution, we have removed the dependencies of the `airflow` integration. Now,
|
55
|
+
you can use ZenML to create your Airflow pipelines and use a separate
|
56
|
+
environment to run them with Airflow. You can check the updated docs
|
57
|
+
[right here](https://docs.zenml.io/stack-components/orchestrators/airflow).
|
58
|
+
|
59
|
+
### AWS
|
60
|
+
|
61
|
+
Some of our integrations now require `protobuf` 4. Since our
|
62
|
+
previous `sagemaker` version (`2.117.0`) did not support `protobof` 4, we could
|
63
|
+
not pair it with these new integrations. Thankfully `sagemaker` started
|
64
|
+
supporting `protobuf` 4 with version `2.172.0` and relaxing its dependency
|
65
|
+
solved the compatibility issue.
|
66
|
+
|
67
|
+
### Evidently
|
68
|
+
|
69
|
+
The old version of our `evidently` integration was not compatible with Pydantic
|
70
|
+
v2. They started supporting it starting from version `0.4.16`. As their latest
|
71
|
+
version is `0.4.22`, the new dependency of the integration is limited between
|
72
|
+
these two versions.
|
73
|
+
|
74
|
+
### Feast
|
75
|
+
|
76
|
+
Our previous implementation of the `feast` integration was not compatible with
|
77
|
+
Pydantic v2 due to the extra `redis` dependency we were using. This extra
|
78
|
+
dependency is now removed and the `feast` integration is working as intended.
|
79
|
+
|
80
|
+
### GCP
|
81
|
+
|
82
|
+
The previous version of the Kubeflow dependency (`kfp==1.8.22`) in our GCP
|
83
|
+
integration required Pydantic V1 to be installed. While we were upgrading our
|
84
|
+
Pydantic dependency, we saw this as an opportunity and wanted to use this chance
|
85
|
+
to upgrade the `kfp` dependency to v2 (which has no dependencies on the Pydantic
|
86
|
+
library). This is why you may see some functional changes in the vertex step
|
87
|
+
operator and orchestrator. If you would like to go through the changes in
|
88
|
+
the `kfp` library, you can
|
89
|
+
find [the migration guide here](https://www.kubeflow.org/docs/components/pipelines/v2/migration/).
|
90
|
+
|
91
|
+
### Great Expectations
|
92
|
+
|
93
|
+
Great Expectations started supporting Pydantic v2 starting from
|
94
|
+
version `0.17.15` and they are closing in on their `1.0` release. Since this
|
95
|
+
release might include a lot of big changes, we adjusted the dependency in our
|
96
|
+
integration to `great-expectations>=0.17.15,<1.0`. We will try to keep it
|
97
|
+
updated in the future once they release the `1.0` version
|
98
|
+
|
99
|
+
### Kubeflow
|
100
|
+
|
101
|
+
Similar to the GCP integration, the previous version of the kubeflow
|
102
|
+
dependency (`kfp==1.8.22`) in our `kubeflow` integration required Pydantic V1 to
|
103
|
+
be installed. While we were upgrading our Pydantic dependency, we saw this as an
|
104
|
+
opportunity and wanted to use this chance to upgrade the `kfp` dependency to
|
105
|
+
v2 (which has no dependencies on the Pydantic library). If you would like to go
|
106
|
+
through the changes in the `kfp` library, you can
|
107
|
+
find [the migration guide here](https://www.kubeflow.org/docs/components/pipelines/v2/migration/). (
|
108
|
+
We also are considering adding an alternative version of this integration so our
|
109
|
+
users can keep using `kfp` V1 in their environment. Stay tuned for any updates.)
|
110
|
+
|
111
|
+
### MLflow
|
112
|
+
|
113
|
+
`mlflow` is compatible with both Pydantic V1 and v2. However, due to a known
|
114
|
+
issue, if you install `zenml` first and then
|
115
|
+
do `zenml integration install mlflow -y`, it downgrades `pydantic` to V1. This
|
116
|
+
is why we manually added the same duplicated `pydantic` requirement in the
|
117
|
+
integration definition as well. Keep in mind that the `mlflow` library is still
|
118
|
+
using some features of `pydantic` V1 which are deprecated. So, if the
|
119
|
+
integration is installed in your environment, you might run into some
|
120
|
+
deprecation warnings.
|
121
|
+
|
122
|
+
### Label Studio
|
123
|
+
|
124
|
+
While we were working on updating our `pydantic` dependency,
|
125
|
+
the `label-studio-sdk` has released its 1.0 version. In this new
|
126
|
+
version, `pydantic` v2 is also supported. The implementation and documentation
|
127
|
+
of our Label Studio integration have been updated accordingly.
|
128
|
+
|
129
|
+
### Skypilot
|
130
|
+
|
131
|
+
With the switch to `pydantic` v2, the implementation of our `skypilot`
|
132
|
+
integration mostly remained untouched. However, due to an incompatibility
|
133
|
+
between the new version `pydantic` and the `azurecli`, the `skypilot[azure]`
|
134
|
+
flavor can not be installed at the same time, thus our `skypilot_azure`
|
135
|
+
integration is currently deactivated. We are working on fixing this issue and if
|
136
|
+
you are using this integration in your workflows, we recommend staying on the
|
137
|
+
previous version of ZenML until we can solve this issue.
|
138
|
+
|
139
|
+
### Tensorflow
|
140
|
+
|
141
|
+
The new version of `pydantic` creates a drift between `tensorflow`
|
142
|
+
and `typing_extensions` packages and relaxing the dependencies here resolves
|
143
|
+
the issue. At the same time, the upgrade to `kfp` v2 (in integrations
|
144
|
+
like `kubeflow`, `tekton`, or `gcp`) bumps our `protobuf` dependency from `3.X`
|
145
|
+
to `4.X`. To stay compatible with this requirement, the installed version
|
146
|
+
of `tensorflow` needs to be `>=2.12.0`. While this change solves the dependency
|
147
|
+
issues in most settings, we have bumped into some errors while
|
148
|
+
using `tensorflow` 2.12.0 on Python 3.8 on Ubuntu. If you would like to use this
|
149
|
+
integration, please consider using a higher Python version.
|
150
|
+
|
151
|
+
### Tekton
|
152
|
+
|
153
|
+
Similar to the `gcp` and `kubeflow` integrations, the old version of
|
154
|
+
our `tekton` integration was not compatible with `pydantic` V1 due to its `kfp`
|
155
|
+
dependency. With the switch from `kfp` V1 to v2, we have adapted our
|
156
|
+
implementation to use the new version of `kfp` library and updated our
|
157
|
+
documentation accordingly.
|
158
|
+
|
159
|
+
## Additional Changes
|
160
|
+
|
161
|
+
* We have also released a new version of `mlstacks` with Pydantic v2 support.
|
162
|
+
If you are using it in your development environment, you have to upgrade your
|
163
|
+
`mlstacks` package as well.
|
164
|
+
* Added `zenml.integrations.huggingface.steps.run_with_accelerate` to enable running any step using [`accelerate`](https://huggingface.co/docs/accelerate/en/index). This function is supported by a utility that wraps any step function into a CLI script (which is required by most distributed training tools).
|
165
|
+
* Fixed a memory leak that was observed while using the ZenML dashboard to view pipeline logs or artifact visualizations logged through an S3 Artifact Store linked to an AWS Service Connector.
|
166
|
+
* Previously, we had an option called `build_options` that allowed users to pass arguments to the docker build command. However, these options were only applied when building the parent image. On macOS with ARM architecture, one needs to specify `platform=linux/amd64` to the build command to leverage local caching of Docker image layers. We have added a way to specify these build options for the "main" ZenML build as well, not just the parent image build. Additionally, users can now specify a `.dockerignore` file for the parent image build, which was previously not possible.
|
167
|
+
|
168
|
+
## What's Changed
|
169
|
+
|
170
|
+
* Extend migration testing by @avishniakov in https://github.com/zenml-io/zenml/pull/2768
|
171
|
+
* Add retry docs by @htahir1 in https://github.com/zenml-io/zenml/pull/2770
|
172
|
+
* Fix nightly Docker build by @strickvl in https://github.com/zenml-io/zenml/pull/2769
|
173
|
+
* Start CTA and Cloud -> Pro renaming by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2773
|
174
|
+
* Add star CTA to `README` by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2777
|
175
|
+
* Use build python version if available by @schustmi in https://github.com/zenml-io/zenml/pull/2775
|
176
|
+
* Introduced Legacy env var in docs by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2783
|
177
|
+
* Fixing the nlp template for the upcoming pydantic upgrade by @bcdurak in https://github.com/zenml-io/zenml/pull/2778
|
178
|
+
* Full renaming away from cloud to pro by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2782
|
179
|
+
* Adjust docs url for flavors by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2772
|
180
|
+
* Fixed broken unit test on develop and fixed duplicate / by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2785
|
181
|
+
* Added timeout by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2786
|
182
|
+
* Bump NLP template by @avishniakov in https://github.com/zenml-io/zenml/pull/2787
|
183
|
+
* Raise error if Dockerfile does not exist by @schustmi in https://github.com/zenml-io/zenml/pull/2776
|
184
|
+
* Pin `numpy<2.0.0` by @avishniakov in https://github.com/zenml-io/zenml/pull/2789
|
185
|
+
* Fix partial logs loss in step operators with immutable FS in the backend by @avishniakov in https://github.com/zenml-io/zenml/pull/2788
|
186
|
+
* Upgrading to `pydantic` v2 by @bcdurak in https://github.com/zenml-io/zenml/pull/2543
|
187
|
+
* New CI/CD docs by @AlexejPenner in https://github.com/zenml-io/zenml/pull/2784
|
188
|
+
* Improvements for running pipelines from the dashboard by @schustmi in https://github.com/zenml-io/zenml/pull/2781
|
189
|
+
* Accelerate runner helper method by @avishniakov in https://github.com/zenml-io/zenml/pull/2746
|
190
|
+
* Add `--ignore-errors` flag for `zenml artifact prune` by @strickvl in https://github.com/zenml-io/zenml/pull/2780
|
191
|
+
* Enable running a pipeline through the client by @schustmi in https://github.com/zenml-io/zenml/pull/2736
|
192
|
+
* Accelerated template LLMs by @avishniakov in https://github.com/zenml-io/zenml/pull/2797
|
193
|
+
* Separate actions from triggers by @schustmi in https://github.com/zenml-io/zenml/pull/2700
|
194
|
+
* Fix hook type definition and improve code completion for pipeline decorator by @schustmi in https://github.com/zenml-io/zenml/pull/2793
|
195
|
+
* Allow specifying build options for main image build by @schustmi in https://github.com/zenml-io/zenml/pull/2749
|
196
|
+
* Small improvements for yaml config files by @schustmi in https://github.com/zenml-io/zenml/pull/2796
|
197
|
+
* Docs for the `pydantic` migration guide by @bcdurak in https://github.com/zenml-io/zenml/pull/2801
|
198
|
+
* Bump mlflow to v2.14.1 by @christianversloot in https://github.com/zenml-io/zenml/pull/2779
|
199
|
+
* Bugfix fixing the installation script to use the right mlstacks branch by @bcdurak in https://github.com/zenml-io/zenml/pull/2803
|
200
|
+
* Fix S3 artifact store memory leak and other improvements by @stefannica in https://github.com/zenml-io/zenml/pull/2802
|
201
|
+
|
202
|
+
## 🥳 Community Contributions 🥳
|
203
|
+
|
204
|
+
We'd like to give a special thanks to @christianversloot who contributed to
|
205
|
+
this release by bumping the `mlflow` version to 2.14.1
|
206
|
+
|
207
|
+
**Full Changelog**: https://github.com/zenml-io/zenml/compare/0.58.2...0.60.0
|
208
|
+
|
3
209
|
# 0.58.2
|
4
210
|
|
5
211
|
The 0.58.2 minor release is packed with a set of improvements to the ZenML logging and ZenML Server.
|
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.60.0.dev20240627
|
@@ -14,6 +14,7 @@
|
|
14
14
|
"""The base interface to extend the ZenML artifact store."""
|
15
15
|
|
16
16
|
import inspect
|
17
|
+
import os
|
17
18
|
import textwrap
|
18
19
|
from abc import abstractmethod
|
19
20
|
from pathlib import Path
|
@@ -34,6 +35,7 @@ from typing import (
|
|
34
35
|
|
35
36
|
from pydantic import model_validator
|
36
37
|
|
38
|
+
from zenml.constants import ENV_ZENML_SERVER
|
37
39
|
from zenml.enums import StackComponentType
|
38
40
|
from zenml.exceptions import ArtifactStoreInterfaceError
|
39
41
|
from zenml.io import fileio
|
@@ -433,7 +435,11 @@ class BaseArtifactStore(StackComponent):
|
|
433
435
|
**kwargs: The keyword arguments to pass to the Pydantic object.
|
434
436
|
"""
|
435
437
|
super(BaseArtifactStore, self).__init__(*args, **kwargs)
|
436
|
-
|
438
|
+
|
439
|
+
# If running in a ZenML server environment, we don't register
|
440
|
+
# the filesystems. We always use the artifact stores directly.
|
441
|
+
if ENV_ZENML_SERVER not in os.environ:
|
442
|
+
self._register()
|
437
443
|
|
438
444
|
def _register(self) -> None:
|
439
445
|
"""Create and register a filesystem within the filesystem registry."""
|
zenml/artifacts/utils.py
CHANGED
@@ -436,18 +436,21 @@ def load_artifact_visualization(
|
|
436
436
|
artifact_store = _load_artifact_store(
|
437
437
|
artifact_store_id=artifact.artifact_store_id, zen_store=zen_store
|
438
438
|
)
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
439
|
+
try:
|
440
|
+
mode = "rb" if visualization.type == VisualizationType.IMAGE else "r"
|
441
|
+
value = _load_file_from_artifact_store(
|
442
|
+
uri=visualization.uri,
|
443
|
+
artifact_store=artifact_store,
|
444
|
+
mode=mode,
|
445
|
+
)
|
445
446
|
|
446
|
-
|
447
|
-
|
448
|
-
|
447
|
+
# Encode image visualizations if requested
|
448
|
+
if visualization.type == VisualizationType.IMAGE and encode_image:
|
449
|
+
value = base64.b64encode(bytes(value))
|
449
450
|
|
450
|
-
|
451
|
+
return LoadedVisualization(type=visualization.type, value=value)
|
452
|
+
finally:
|
453
|
+
artifact_store.cleanup()
|
451
454
|
|
452
455
|
|
453
456
|
def load_artifact_from_response(artifact: "ArtifactVersionResponse") -> Any:
|
zenml/cli/service_connectors.py
CHANGED
zenml/client.py
CHANGED
@@ -5666,6 +5666,7 @@ class Client(metaclass=ClientMetaClass):
|
|
5666
5666
|
name_id_or_prefix=name_id_or_prefix,
|
5667
5667
|
resource_type=resource_type,
|
5668
5668
|
resource_id=resource_id,
|
5669
|
+
verify=False,
|
5669
5670
|
)
|
5670
5671
|
|
5671
5672
|
connector_client.configure_local_client(
|
@@ -5679,6 +5680,7 @@ class Client(metaclass=ClientMetaClass):
|
|
5679
5680
|
name_id_or_prefix: Union[UUID, str],
|
5680
5681
|
resource_type: Optional[str] = None,
|
5681
5682
|
resource_id: Optional[str] = None,
|
5683
|
+
verify: bool = False,
|
5682
5684
|
) -> "ServiceConnector":
|
5683
5685
|
"""Get the client side of a service connector instance to use with a local client.
|
5684
5686
|
|
@@ -5694,6 +5696,8 @@ class Client(metaclass=ClientMetaClass):
|
|
5694
5696
|
equivalent to the one requested, a `ValueError` exception is
|
5695
5697
|
raised. May be omitted for connectors and resource types that do
|
5696
5698
|
not support multiple resource instances.
|
5699
|
+
verify: Whether to verify that the service connector configuration
|
5700
|
+
and credentials can be used to gain access to the resource.
|
5697
5701
|
|
5698
5702
|
Returns:
|
5699
5703
|
The client side of the indicated service connector instance that can
|
@@ -5731,9 +5735,10 @@ class Client(metaclass=ClientMetaClass):
|
|
5731
5735
|
)
|
5732
5736
|
)
|
5733
5737
|
|
5734
|
-
|
5735
|
-
|
5736
|
-
|
5738
|
+
if verify:
|
5739
|
+
# Verify the connector client on the local machine, because the
|
5740
|
+
# server-side implementation may not be able to do so
|
5741
|
+
connector_client.verify()
|
5737
5742
|
else:
|
5738
5743
|
connector_instance = (
|
5739
5744
|
service_connector_registry.instantiate_connector(
|
@@ -85,7 +85,7 @@ class GoogleCredentialsMixin(StackComponent):
|
|
85
85
|
"trying to use the linked connector, but got "
|
86
86
|
f"{type(credentials)}."
|
87
87
|
)
|
88
|
-
return credentials, connector.config.
|
88
|
+
return credentials, connector.config.gcp_project_id
|
89
89
|
|
90
90
|
if self.config.service_account_path:
|
91
91
|
credentials, project_id = load_credentials_from_file(
|
@@ -351,24 +351,72 @@ class GCPOAuth2Token(AuthenticationConfig):
|
|
351
351
|
class GCPBaseConfig(AuthenticationConfig):
|
352
352
|
"""GCP base configuration."""
|
353
353
|
|
354
|
+
@property
|
355
|
+
def gcp_project_id(self) -> str:
|
356
|
+
"""Get the GCP project ID.
|
357
|
+
|
358
|
+
This method must be implemented by subclasses to ensure that the GCP
|
359
|
+
project ID is always available.
|
360
|
+
|
361
|
+
Raises:
|
362
|
+
NotImplementedError: If the method is not implemented.
|
363
|
+
"""
|
364
|
+
raise NotImplementedError
|
365
|
+
|
366
|
+
|
367
|
+
class GCPBaseProjectIDConfig(GCPBaseConfig):
|
368
|
+
"""GCP base configuration with included project ID."""
|
369
|
+
|
354
370
|
project_id: str = Field(
|
355
371
|
title="GCP Project ID where the target resource is located.",
|
356
372
|
)
|
357
373
|
|
374
|
+
@property
|
375
|
+
def gcp_project_id(self) -> str:
|
376
|
+
"""Get the GCP project ID.
|
377
|
+
|
378
|
+
Returns:
|
379
|
+
The GCP project ID.
|
380
|
+
"""
|
381
|
+
return self.project_id
|
358
382
|
|
359
|
-
|
383
|
+
|
384
|
+
class GCPUserAccountConfig(GCPBaseProjectIDConfig, GCPUserAccountCredentials):
|
360
385
|
"""GCP user account configuration."""
|
361
386
|
|
362
387
|
|
363
388
|
class GCPServiceAccountConfig(GCPBaseConfig, GCPServiceAccountCredentials):
|
364
389
|
"""GCP service account configuration."""
|
365
390
|
|
391
|
+
_project_id: Optional[str] = None
|
392
|
+
|
393
|
+
@property
|
394
|
+
def gcp_project_id(self) -> str:
|
395
|
+
"""Get the GCP project ID.
|
396
|
+
|
397
|
+
When a service account JSON is provided, the project ID can be extracted
|
398
|
+
from it instead of being provided explicitly.
|
366
399
|
|
367
|
-
|
400
|
+
Returns:
|
401
|
+
The GCP project ID.
|
402
|
+
"""
|
403
|
+
if self._project_id is None:
|
404
|
+
self._project_id = json.loads(
|
405
|
+
self.service_account_json.get_secret_value()
|
406
|
+
)["project_id"]
|
407
|
+
# Guaranteed by the field validator
|
408
|
+
assert self._project_id is not None
|
409
|
+
|
410
|
+
return self._project_id
|
411
|
+
|
412
|
+
|
413
|
+
class GCPExternalAccountConfig(
|
414
|
+
GCPBaseProjectIDConfig, GCPExternalAccountCredentials
|
415
|
+
):
|
368
416
|
"""GCP external account configuration."""
|
369
417
|
|
370
418
|
|
371
|
-
class GCPOAuth2TokenConfig(
|
419
|
+
class GCPOAuth2TokenConfig(GCPBaseProjectIDConfig, GCPOAuth2Token):
|
372
420
|
"""GCP OAuth 2.0 configuration."""
|
373
421
|
|
374
422
|
service_account_email: Optional[str] = Field(
|
@@ -540,7 +588,7 @@ resources in the specified project. When used remotely in a GCP workload, the
|
|
540
588
|
configured project has to be the same as the project of the attached service
|
541
589
|
account.
|
542
590
|
""",
|
543
|
-
config_class=
|
591
|
+
config_class=GCPBaseProjectIDConfig,
|
544
592
|
),
|
545
593
|
AuthenticationMethodModel(
|
546
594
|
name="GCP User Account",
|
@@ -1006,6 +1054,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1006
1054
|
# service account authentication)
|
1007
1055
|
|
1008
1056
|
assert isinstance(cfg, GCPServiceAccountConfig)
|
1057
|
+
|
1009
1058
|
credentials = (
|
1010
1059
|
gcp_service_account.Credentials.from_service_account_info(
|
1011
1060
|
json.loads(
|
@@ -1115,7 +1164,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1115
1164
|
#
|
1116
1165
|
# We need to extract the project ID and registry ID from
|
1117
1166
|
# the provided resource ID
|
1118
|
-
config_project_id = self.config.
|
1167
|
+
config_project_id = self.config.gcp_project_id
|
1119
1168
|
project_id: Optional[str] = None
|
1120
1169
|
# A GCR repository URI uses one of several hostnames (gcr.io, us.gcr.io,
|
1121
1170
|
# eu.gcr.io, asia.gcr.io etc.) and the project ID is the first part of
|
@@ -1219,9 +1268,9 @@ class GCPServiceConnector(ServiceConnector):
|
|
1219
1268
|
authorized.
|
1220
1269
|
"""
|
1221
1270
|
if resource_type == GCP_RESOURCE_TYPE:
|
1222
|
-
return self.config.
|
1271
|
+
return self.config.gcp_project_id
|
1223
1272
|
elif resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1224
|
-
return f"gcr.io/{self.config.
|
1273
|
+
return f"gcr.io/{self.config.gcp_project_id}"
|
1225
1274
|
|
1226
1275
|
raise RuntimeError(
|
1227
1276
|
f"Default resource ID not supported for '{resource_type}' resource "
|
@@ -1278,7 +1327,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1278
1327
|
|
1279
1328
|
# Create an GCS client for the bucket
|
1280
1329
|
client = storage.Client(
|
1281
|
-
project=self.config.
|
1330
|
+
project=self.config.gcp_project_id, credentials=credentials
|
1282
1331
|
)
|
1283
1332
|
return client
|
1284
1333
|
|
@@ -1384,7 +1433,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1384
1433
|
"config",
|
1385
1434
|
"set",
|
1386
1435
|
"project",
|
1387
|
-
self.config.
|
1436
|
+
self.config.gcp_project_id,
|
1388
1437
|
],
|
1389
1438
|
check=True,
|
1390
1439
|
stderr=subprocess.STDOUT,
|
@@ -1488,7 +1537,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1488
1537
|
)
|
1489
1538
|
|
1490
1539
|
if auth_method == GCPAuthenticationMethods.IMPLICIT:
|
1491
|
-
auth_config =
|
1540
|
+
auth_config = GCPBaseProjectIDConfig(
|
1492
1541
|
project_id=project_id,
|
1493
1542
|
)
|
1494
1543
|
elif auth_method == GCPAuthenticationMethods.OAUTH2_TOKEN:
|
@@ -1697,7 +1746,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1697
1746
|
|
1698
1747
|
if resource_type == GCS_RESOURCE_TYPE:
|
1699
1748
|
gcs_client = storage.Client(
|
1700
|
-
project=self.config.
|
1749
|
+
project=self.config.gcp_project_id, credentials=credentials
|
1701
1750
|
)
|
1702
1751
|
if not resource_id:
|
1703
1752
|
# List all GCS buckets
|
@@ -1736,7 +1785,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1736
1785
|
# List all GKE clusters
|
1737
1786
|
try:
|
1738
1787
|
clusters = gke_client.list_clusters(
|
1739
|
-
parent=f"projects/{self.config.
|
1788
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/-"
|
1740
1789
|
)
|
1741
1790
|
cluster_names = [cluster.name for cluster in clusters.clusters]
|
1742
1791
|
except google.api_core.exceptions.GoogleAPIError as e:
|
@@ -1810,7 +1859,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1810
1859
|
# object
|
1811
1860
|
auth_method: str = GCPAuthenticationMethods.OAUTH2_TOKEN
|
1812
1861
|
config: GCPBaseConfig = GCPOAuth2TokenConfig(
|
1813
|
-
project_id=self.config.
|
1862
|
+
project_id=self.config.gcp_project_id,
|
1814
1863
|
token=credentials.token,
|
1815
1864
|
service_account_email=credentials.signer_email
|
1816
1865
|
if hasattr(credentials, "signer_email")
|
@@ -1884,7 +1933,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1884
1933
|
# List all GKE clusters
|
1885
1934
|
try:
|
1886
1935
|
clusters = gke_client.list_clusters(
|
1887
|
-
parent=f"projects/{self.config.
|
1936
|
+
parent=f"projects/{self.config.gcp_project_id}/locations/-"
|
1888
1937
|
)
|
1889
1938
|
cluster_map = {
|
1890
1939
|
cluster.name: cluster for cluster in clusters.clusters
|
@@ -1928,7 +1977,7 @@ class GCPServiceConnector(ServiceConnector):
|
|
1928
1977
|
auth_method=KubernetesAuthenticationMethods.TOKEN,
|
1929
1978
|
resource_type=resource_type,
|
1930
1979
|
config=KubernetesTokenConfig(
|
1931
|
-
cluster_name=f"gke_{self.config.
|
1980
|
+
cluster_name=f"gke_{self.config.gcp_project_id}_{cluster_name}",
|
1932
1981
|
certificate_authority=cluster_ca_cert,
|
1933
1982
|
server=f"https://{cluster_server}",
|
1934
1983
|
token=bearer_token,
|
@@ -26,6 +26,7 @@ from typing import (
|
|
26
26
|
)
|
27
27
|
|
28
28
|
import s3fs
|
29
|
+
from fsspec.asyn import FSTimeoutError, sync, sync_wrapper
|
29
30
|
|
30
31
|
from zenml.artifact_stores import BaseArtifactStore
|
31
32
|
from zenml.integrations.s3.flavors.s3_artifact_store_flavor import (
|
@@ -38,10 +39,77 @@ from zenml.stack.authentication_mixin import AuthenticationMixin
|
|
38
39
|
PathType = Union[bytes, str]
|
39
40
|
|
40
41
|
|
42
|
+
class ZenMLS3Filesystem(s3fs.S3FileSystem): # type: ignore[misc]
|
43
|
+
"""Modified s3fs.S3FileSystem to disable caching.
|
44
|
+
|
45
|
+
The original s3fs.S3FileSystem caches all class instances based on the
|
46
|
+
constructor input arguments and it never releases them. This is problematic
|
47
|
+
in the context of the ZenML server, because the server is a long-running
|
48
|
+
process that instantiates many S3 filesystems with different credentials,
|
49
|
+
especially when the credentials are generated by service connectors.
|
50
|
+
|
51
|
+
The caching behavior of s3fs causes the server to slowly consume more and
|
52
|
+
more memory over time until it crashes. This class disables the caching
|
53
|
+
behavior of s3fs by setting the `cachable` attribute to `False`.
|
54
|
+
|
55
|
+
In addition to disabling instance caching, this class also provides a
|
56
|
+
correct cleanup implementation by overriding the `close_session` method
|
57
|
+
the S3 aiobotocore client. The original one provided by s3fs was causing
|
58
|
+
memory leaks by creating a new event loop in the destructor instead of
|
59
|
+
using the existing one.
|
60
|
+
|
61
|
+
A `close` method is also provided to allow for synchronous on-demand cleanup
|
62
|
+
of the S3 client.
|
63
|
+
"""
|
64
|
+
|
65
|
+
cachable = False
|
66
|
+
|
67
|
+
async def _close(self) -> None:
|
68
|
+
"""Close the S3 client."""
|
69
|
+
if self._s3creator is not None: # type: ignore[has-type]
|
70
|
+
await self._s3creator.__aexit__(None, None, None) # type: ignore[has-type]
|
71
|
+
self._s3creator = None
|
72
|
+
self._s3 = None
|
73
|
+
|
74
|
+
close = sync_wrapper(_close)
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def close_session(loop: Any, s3: Any) -> None:
|
78
|
+
"""Close the S3 client session.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
loop: The event loop to use for closing the session.
|
82
|
+
s3: The S3 client to close.
|
83
|
+
"""
|
84
|
+
# IMPORTANT: This method is a copy of the original close_session method
|
85
|
+
# from s3fs.S3FileSystem. The only difference is that it uses the
|
86
|
+
# provided event loop instead of creating a new one.
|
87
|
+
if loop is not None and loop.is_running():
|
88
|
+
try:
|
89
|
+
# NOTE: this is the line in the original method that causes
|
90
|
+
# the memory leak
|
91
|
+
# loop = asyncio.get_event_loop()
|
92
|
+
loop.create_task(s3.__aexit__(None, None, None))
|
93
|
+
return
|
94
|
+
except RuntimeError:
|
95
|
+
pass
|
96
|
+
try:
|
97
|
+
sync(loop, s3.__aexit__, None, None, None, timeout=0.1)
|
98
|
+
return
|
99
|
+
except FSTimeoutError:
|
100
|
+
pass
|
101
|
+
try:
|
102
|
+
# close the actual socket
|
103
|
+
s3._client._endpoint.http_session._connector._close()
|
104
|
+
except AttributeError:
|
105
|
+
# but during shutdown, it may have gone
|
106
|
+
pass
|
107
|
+
|
108
|
+
|
41
109
|
class S3ArtifactStore(BaseArtifactStore, AuthenticationMixin):
|
42
110
|
"""Artifact Store for S3 based artifacts."""
|
43
111
|
|
44
|
-
_filesystem: Optional[
|
112
|
+
_filesystem: Optional[ZenMLS3Filesystem] = None
|
45
113
|
|
46
114
|
@property
|
47
115
|
def config(self) -> S3ArtifactStoreConfig:
|
@@ -98,7 +166,7 @@ class S3ArtifactStore(BaseArtifactStore, AuthenticationMixin):
|
|
98
166
|
return self.config.key, self.config.secret, self.config.token
|
99
167
|
|
100
168
|
@property
|
101
|
-
def filesystem(self) ->
|
169
|
+
def filesystem(self) -> ZenMLS3Filesystem:
|
102
170
|
"""The s3 filesystem to access this artifact store.
|
103
171
|
|
104
172
|
Returns:
|
@@ -110,7 +178,7 @@ class S3ArtifactStore(BaseArtifactStore, AuthenticationMixin):
|
|
110
178
|
|
111
179
|
key, secret, token = self.get_credentials()
|
112
180
|
|
113
|
-
self._filesystem =
|
181
|
+
self._filesystem = ZenMLS3Filesystem(
|
114
182
|
key=key,
|
115
183
|
secret=secret,
|
116
184
|
token=token,
|
@@ -120,6 +188,11 @@ class S3ArtifactStore(BaseArtifactStore, AuthenticationMixin):
|
|
120
188
|
)
|
121
189
|
return self._filesystem
|
122
190
|
|
191
|
+
def cleanup(self) -> None:
|
192
|
+
"""Close the filesystem."""
|
193
|
+
if self._filesystem:
|
194
|
+
self._filesystem.close()
|
195
|
+
|
123
196
|
def open(self, path: PathType, mode: str = "r") -> Any:
|
124
197
|
"""Open a file at the given path.
|
125
198
|
|