zenml-nightly 0.84.0.dev20250729__py3-none-any.whl → 0.84.0.dev20250730__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.
- zenml/VERSION +1 -1
- zenml/client.py +6 -1
- zenml/constants.py +25 -0
- zenml/integrations/gcp/__init__.py +4 -1
- zenml/logging/__init__.py +0 -11
- zenml/logging/step_logging.py +255 -149
- zenml/models/__init__.py +4 -0
- zenml/models/v2/core/service_account.py +87 -1
- zenml/models/v2/core/user.py +20 -6
- zenml/models/v2/misc/external_user.py +4 -1
- zenml/orchestrators/step_launcher.py +2 -4
- zenml/zen_server/auth.py +138 -27
- zenml/zen_server/dashboard/assets/{404-B5cfnwZ1.js → 404-CMbwR10f.js} +1 -1
- zenml/zen_server/dashboard/assets/{@radix-C_LirfyT.js → @radix-B1sy0Lhr.js} +1 -1
- zenml/zen_server/dashboard/assets/{@react-router-BSsrkPOd.js → @react-router-CHjLNlgw.js} +1 -1
- zenml/zen_server/dashboard/assets/{@reactflow-D9hglKLF.js → @reactflow-gbyyU3kq.js} +2 -2
- zenml/zen_server/dashboard/assets/{@tanstack-C0SeHZng.js → @tanstack-Dwlisomv.js} +1 -1
- zenml/zen_server/dashboard/assets/AlertDialogDropdownItem-BpLj419i.js +1 -0
- zenml/zen_server/dashboard/assets/ButtonGroup-DVhzbQxV.js +1 -0
- zenml/zen_server/dashboard/assets/{CodeSnippet-D8iBqOVv.js → CodeSnippet-B7Wd4R8u.js} +1 -1
- zenml/zen_server/dashboard/assets/{CollapsibleCard-D0-pQi1n.js → CollapsibleCard-DSZzbjx5.js} +1 -1
- zenml/zen_server/dashboard/assets/{ComponentBadge-mw2Ja_ON.js → ComponentBadge-MVT08kBV.js} +1 -1
- zenml/zen_server/dashboard/assets/{ComponentIcon-BXgpt-jw.js → ComponentIcon-C9AY8usJ.js} +1 -1
- zenml/zen_server/dashboard/assets/DeleteAlertDialog-CzhTVQaC.js +1 -0
- zenml/zen_server/dashboard/assets/{DialogItem-DeME0oSt.js → DialogItem-DD_7v3UY.js} +1 -1
- zenml/zen_server/dashboard/assets/{DisplayDate-v3KW7oez.js → DisplayDate-57lUNS79.js} +1 -1
- zenml/zen_server/dashboard/assets/{EmptyState-DG0m-CGv.js → EmptyState-BMA34iVR.js} +1 -1
- zenml/zen_server/dashboard/assets/{Error-DcVLcrLs.js → Error-Cakgjexo.js} +1 -1
- zenml/zen_server/dashboard/assets/{ExecutionStatus-C4tlFnlh.js → ExecutionStatus-CQbWovhV.js} +1 -1
- zenml/zen_server/dashboard/assets/{Helpbox-C-RGHz3S.js → Helpbox-7FZpmsHB.js} +1 -1
- zenml/zen_server/dashboard/assets/{Infobox-DFCWPbMb.js → Infobox-DnduZjNU.js} +1 -1
- zenml/zen_server/dashboard/assets/{LeftSideMenu-Czev0KCA.js → LeftSideMenu-D5UEs4vB.js} +1 -1
- zenml/zen_server/dashboard/assets/{Lock-CRP5J_su.js → Lock-u8WOoTTd.js} +1 -1
- zenml/zen_server/dashboard/assets/NestedCollapsible-0yviIfaA.js +1 -0
- zenml/zen_server/dashboard/assets/{NumberBox-CoQjQYDJ.js → NumberBox-C2Pju4PR.js} +1 -1
- zenml/zen_server/dashboard/assets/{Pagination-CcDD5yHh.js → Pagination-C1sE5Nzn.js} +1 -1
- zenml/zen_server/dashboard/assets/{Partials-DlMzfKgs.js → Partials-DosysQMU.js} +1 -1
- zenml/zen_server/dashboard/assets/{PasswordChecker-BZwoeQIm.js → PasswordChecker-DOPLfvg7.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProCta-CU2ycJDo.js → ProCta-CUkpV3PJ.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderIcon-BMAn9Jld.js → ProviderIcon-aQjTuTRX.js} +1 -1
- zenml/zen_server/dashboard/assets/{ProviderRadio-D_q9tE3G.js → ProviderRadio-C6pLVNxC.js} +1 -1
- zenml/zen_server/dashboard/assets/RunsBody-CZAiSxYK.js +1 -0
- zenml/zen_server/dashboard/assets/{SearchField-D_0-uAPj.js → SearchField-Byv1PtST.js} +1 -1
- zenml/zen_server/dashboard/assets/{SecretTooltip-BcWMKb9f.js → SecretTooltip-CErfhVgF.js} +1 -1
- zenml/zen_server/dashboard/assets/{SetPassword-CaKVSqAL.js → SetPassword-C4OH-cn1.js} +1 -1
- zenml/zen_server/dashboard/assets/{SheetHeader-7vwlsY_i.js → SheetHeader-Bb3v9rha.js} +1 -1
- zenml/zen_server/dashboard/assets/StackComponentList-D85oab98.js +1 -0
- zenml/zen_server/dashboard/assets/StackList-DSAjbVb5.js +1 -0
- zenml/zen_server/dashboard/assets/Tabs-2uwqXsdL.js +1 -0
- zenml/zen_server/dashboard/assets/Tick-BQ7_xDBl.js +1 -0
- zenml/zen_server/dashboard/assets/{UpdatePasswordSchemas-Da5RndbV.js → UpdatePasswordSchemas-DNkYgzN6.js} +1 -1
- zenml/zen_server/dashboard/assets/{Wizard-8aJzxUjb.js → Wizard-2FIi8JNY.js} +1 -1
- zenml/zen_server/dashboard/assets/WizardFooter-DQEnlDmZ.js +1 -0
- zenml/zen_server/dashboard/assets/{all-pipeline-runs-query-gorNNEaT.js → all-pipeline-runs-query-CdvWtiAY.js} +1 -1
- zenml/zen_server/dashboard/assets/{arrow-left-hcj2H8HY.js → arrow-left-BFB2mgJo.js} +1 -1
- zenml/zen_server/dashboard/assets/{bar-chart-square-check-9siI9icm.js → bar-chart-square-check-_m-oYysN.js} +1 -1
- zenml/zen_server/dashboard/assets/{bulk-delete-B5RTlnD_.js → bulk-delete-wdObjfps.js} +2 -2
- zenml/zen_server/dashboard/assets/{check-D1bHMJkL.js → check-C9Nye5lR.js} +1 -1
- zenml/zen_server/dashboard/assets/{check-circle-mnEgPhPF.js → check-circle-CG6XAdk-.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-down-Z3nUe-0U.js → chevron-down-Bl7WwQAL.js} +1 -1
- zenml/zen_server/dashboard/assets/{chevron-right-double-CbRQKN4Q.js → chevron-right-double-CbMG1dOM.js} +1 -1
- zenml/zen_server/dashboard/assets/{clock-BMjHXT3f.js → clock-BnPvxp3D.js} +1 -1
- zenml/zen_server/dashboard/assets/{code-browser-DftoiCIg.js → code-browser-CzWvJ4S6.js} +1 -1
- zenml/zen_server/dashboard/assets/configuration-form-BbcNBQTO.js +1 -0
- zenml/zen_server/dashboard/assets/constants-DfvsDtcH.js +1 -0
- zenml/zen_server/dashboard/assets/{create-stack-BruqH_6X.js → create-stack-D4Sf7QpF.js} +1 -1
- zenml/zen_server/dashboard/assets/{credit-card-CH1BHrXY.js → credit-card-DfBA6dBb.js} +1 -1
- zenml/zen_server/dashboard/assets/{dataflow-2-qHjWt7zp.js → dataflow-2-BLkaTH0Q.js} +1 -1
- zenml/zen_server/dashboard/assets/{delete-run-ibBtciMR.js → delete-run-XSre8ru-.js} +1 -1
- zenml/zen_server/dashboard/assets/{expand-full-CD4fFvM-.js → expand-full-C8p_0jQv.js} +1 -1
- zenml/zen_server/dashboard/assets/{eye-CLNgIh_K.js → eye-BXcQevds.js} +1 -1
- zenml/zen_server/dashboard/assets/{file-text-CltVhgwZ.js → file-text-TdPXNd7s.js} +1 -1
- zenml/zen_server/dashboard/assets/form-C1jJgRy1.js +1 -0
- zenml/zen_server/dashboard/assets/{form-schemas-B9XgTS1V.js → form-schemas-BjWDRvGd.js} +1 -1
- zenml/zen_server/dashboard/assets/{help-B0CvBhCm.js → help-CRPfbPTO.js} +1 -1
- zenml/zen_server/dashboard/assets/{icon-hDriJUXY.js → icon--Tp6obFZ.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-mA8kL088.js → index-B4ZeIaPd.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-dCcVgFNl.js → index-BYHxFvoG.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-BQWlHo1Y.js → index-BYzR7YrM.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-BacoJBEQ.js → index-CO6UN3UX.js} +11 -11
- zenml/zen_server/dashboard/assets/{index-B7CRNU8l.js → index-d_QrPL-8.js} +1 -1
- zenml/zen_server/dashboard/assets/{index-BRhKF2z-.js → index-g7wOyc86.js} +1 -1
- zenml/zen_server/dashboard/assets/index-n_sn2ITN.css +1 -0
- zenml/zen_server/dashboard/assets/{index.es-DcVFDpJU.js → index.es-CZMSrR6z.js} +1 -1
- zenml/zen_server/dashboard/assets/{index.esm-COnaHLSh.js → index.esm-SBF9nn_P.js} +1 -1
- zenml/zen_server/dashboard/assets/{info-CyMih3vQ.js → info-CI2szPUG.js} +1 -1
- zenml/zen_server/dashboard/assets/{key-icon-HOx2gazv.js → key-icon-BiV171aI.js} +1 -1
- zenml/zen_server/dashboard/assets/layout-Cyc-V16c.js +1 -0
- zenml/zen_server/dashboard/assets/{layout-C5dgIReC.js → layout-GIiNvS10.js} +1 -1
- zenml/zen_server/dashboard/assets/{login-mutation-CidpsqyH.js → login-mutation-CDgTlQBD.js} +1 -1
- zenml/zen_server/dashboard/assets/{logs-DoLoTEfj.js → logs-lNLbZqHL.js} +1 -1
- zenml/zen_server/dashboard/assets/{mail-C160gvB0.js → mail-DBJRSzyu.js} +1 -1
- zenml/zen_server/dashboard/assets/{message-chat-square-DLz6XmPS.js → message-chat-square-C3CRCQvD.js} +1 -1
- zenml/zen_server/dashboard/assets/{package-BhYXGPxF.js → package-pR4bbXJp.js} +1 -1
- zenml/zen_server/dashboard/assets/page-1DXG7hp8.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BCRXJXC9.js → page-43SWdz4k.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-YdWnx9MX.js → page-AOVMuHDc.js} +1 -1
- zenml/zen_server/dashboard/assets/page-B4QBxV0B.js +22 -0
- zenml/zen_server/dashboard/assets/{page-CvllZMBF.js → page-B8ict_cR.js} +1 -1
- zenml/zen_server/dashboard/assets/page-BDre_qCO.js +1 -0
- zenml/zen_server/dashboard/assets/page-BbsvGfyi.js +18 -0
- zenml/zen_server/dashboard/assets/page-Bi4I23Hs.js +1 -0
- zenml/zen_server/dashboard/assets/page-BnAMyQZb.js +1 -0
- zenml/zen_server/dashboard/assets/{page-D6cvOG8w.js → page-BnZEAhNn.js} +1 -1
- zenml/zen_server/dashboard/assets/page-Bncp08FW.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BTDi81N3.js → page-C4Jnp1qP.js} +1 -1
- zenml/zen_server/dashboard/assets/page-C7Z2sDHE.js +1 -0
- zenml/zen_server/dashboard/assets/page-C8t9qLdV.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BByayrO-.js → page-C9IsnFid.js} +2 -2
- zenml/zen_server/dashboard/assets/page-CEHZzQ1Q.js +1 -0
- zenml/zen_server/dashboard/assets/page-CGtll3Jk.js +1 -0
- zenml/zen_server/dashboard/assets/{page-q41JNDWO.js → page-CKPkbOoE.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DF4FVxxW.js → page-CMvcoaSZ.js} +2 -2
- zenml/zen_server/dashboard/assets/page-CVwTAlzd.js +1 -0
- zenml/zen_server/dashboard/assets/{page-DSZfclXt.js → page-ClqRvz_V.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-7CJ4Wq3O.js → page-CzDjN1wE.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-BK59rZvf.js → page-D654xHWd.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DcXrWWWh.js → page-DLrYW-Dn.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DOnAInjC.js +1 -0
- zenml/zen_server/dashboard/assets/{page-Cc8owYXQ.js → page-DQncGJeH.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DQzNmXk3.js +1 -0
- zenml/zen_server/dashboard/assets/{page-FQxi1Otg.js → page-DSn1Jcvs.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DZ5hcS1o.js +1 -0
- zenml/zen_server/dashboard/assets/page-DerIbaqa.js +1 -0
- zenml/zen_server/dashboard/assets/{page-8U20Tu_8.js → page-DgOEl3Bc.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DgldL5UB.js → page-DgSu4pTE.js} +2 -2
- zenml/zen_server/dashboard/assets/page-DgblJuXC.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CY0LPcAJ.js → page-Dpw-xP4q.js} +1 -1
- zenml/zen_server/dashboard/assets/page-DsFXol8x.js +1 -0
- zenml/zen_server/dashboard/assets/page-Du6bFZTS.js +1 -0
- zenml/zen_server/dashboard/assets/{page-CeGBDh1Q.js → page-DznxxpKA.js} +1 -1
- zenml/zen_server/dashboard/assets/page-E9Mx9_rn.js +1 -0
- zenml/zen_server/dashboard/assets/page-HrebZOAM.js +1 -0
- zenml/zen_server/dashboard/assets/{page-BX67x4iL.js → page-O5xnz_nW.js} +1 -1
- zenml/zen_server/dashboard/assets/{page-DDWW21kl.js → page-hX7NkNdA.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-BKKcL1Kp.js → persist-B3Hb8HDW.js} +1 -1
- zenml/zen_server/dashboard/assets/{persist-DxiyfAax.js → persist-Dkbp9MFP.js} +1 -1
- zenml/zen_server/dashboard/assets/{pipeline-BJ8liDnl.js → pipeline-CDJSAXrb.js} +1 -1
- zenml/zen_server/dashboard/assets/{plus-cI8zD2xh.js → plus-DOrkJCmy.js} +1 -1
- zenml/zen_server/dashboard/assets/primary-role-CjkpP9fL.js +1 -0
- zenml/zen_server/dashboard/assets/{react-error-boundary.esm-DoXxY4pT.js → react-error-boundary.esm-DgOYA0wh.js} +1 -1
- zenml/zen_server/dashboard/assets/{refresh-3EF2R7ja.js → refresh-C8BXS3Fb.js} +1 -1
- zenml/zen_server/dashboard/assets/{resource-tyes-list-B5rkZcbc.js → resource-tyes-list-CugyWUAE.js} +1 -1
- zenml/zen_server/dashboard/assets/{resource-type-tooltip-E97WGqfk.js → resource-type-tooltip-V-zdiLKz.js} +1 -1
- zenml/zen_server/dashboard/assets/{service-B9aVzfAF.js → service-FjqYTmBw.js} +2 -2
- zenml/zen_server/dashboard/assets/{service-connectors-DL2-k_E2.js → service-connectors-nF4-OXB9.js} +1 -1
- zenml/zen_server/dashboard/assets/{sharedSchema-DyUO09BR.js → sharedSchema-Br1JPr8d.js} +1 -1
- zenml/zen_server/dashboard/assets/{slash-circle-D2Lb2FyR.js → slash-circle-B0xMh8q6.js} +1 -1
- zenml/zen_server/dashboard/assets/{stack-detail-query-Bc4QKlWg.js → stack-detail-query-DnyMCdVE.js} +1 -1
- zenml/zen_server/dashboard/assets/{terminal-BObrvDlO.js → terminal-C2tAF2XG.js} +1 -1
- zenml/zen_server/dashboard/assets/{terminal-square-BObrvDlO.js → terminal-square-C2tAF2XG.js} +1 -1
- zenml/zen_server/dashboard/assets/{transform-DFpKTKgF.js → transform-S7omcvTm.js} +1 -1
- zenml/zen_server/dashboard/assets/{trash-HKxXWbSG.js → trash-D9LA4VFD.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-current-user-mutation-DSyUyHVj.js → update-current-user-mutation-B3Dpv3u6.js} +1 -1
- zenml/zen_server/dashboard/assets/{update-server-settings-mutation-CdM-Sdds.js → update-server-settings-mutation-s7tlHN8s.js} +1 -1
- zenml/zen_server/dashboard/assets/{zod-DgEcN9jD.js → zod-D48zuELD.js} +1 -1
- zenml/zen_server/dashboard/index.html +7 -7
- zenml/zen_server/rbac/zenml_cloud_rbac.py +8 -4
- zenml/zen_server/routers/service_accounts_endpoints.py +71 -12
- zenml/zen_stores/base_zen_store.py +0 -19
- zenml/zen_stores/migrations/versions/8d4b9ba22c1f_add_user_avatar.py +39 -0
- zenml/zen_stores/schemas/user_schemas.py +16 -5
- zenml/zen_stores/sql_zen_store.py +5 -1
- {zenml_nightly-0.84.0.dev20250729.dist-info → zenml_nightly-0.84.0.dev20250730.dist-info}/METADATA +1 -1
- {zenml_nightly-0.84.0.dev20250729.dist-info → zenml_nightly-0.84.0.dev20250730.dist-info}/RECORD +168 -167
- zenml/zen_server/dashboard/assets/AlertDialogDropdownItem-druRNuO2.js +0 -1
- zenml/zen_server/dashboard/assets/ButtonGroup-SF2DlzHV.js +0 -1
- zenml/zen_server/dashboard/assets/DeleteAlertDialog-BbyFVnVI.js +0 -1
- zenml/zen_server/dashboard/assets/NestedCollapsible-CN9scBUn.js +0 -1
- zenml/zen_server/dashboard/assets/RunsBody-BToytB8e.js +0 -1
- zenml/zen_server/dashboard/assets/StackComponentList-s7eSfm8o.js +0 -1
- zenml/zen_server/dashboard/assets/StackList-Dt0FrIkM.js +0 -1
- zenml/zen_server/dashboard/assets/Tabs-B27AHUfo.js +0 -1
- zenml/zen_server/dashboard/assets/Tick-DDeDgTuT.js +0 -1
- zenml/zen_server/dashboard/assets/WizardFooter-Bt7_UE14.js +0 -1
- zenml/zen_server/dashboard/assets/configuration-form-Yz8m0QIG.js +0 -1
- zenml/zen_server/dashboard/assets/constants-DeV48DuZ.js +0 -1
- zenml/zen_server/dashboard/assets/form-6aSt3tIl.js +0 -1
- zenml/zen_server/dashboard/assets/index-eggipFZS.css +0 -1
- zenml/zen_server/dashboard/assets/layout-CFLL6-CM.js +0 -1
- zenml/zen_server/dashboard/assets/page-6huxSHEu.js +0 -1
- zenml/zen_server/dashboard/assets/page-BMpXak4U.js +0 -1
- zenml/zen_server/dashboard/assets/page-Bjmcdg64.js +0 -1
- zenml/zen_server/dashboard/assets/page-BsAn8p4m.js +0 -1
- zenml/zen_server/dashboard/assets/page-BwjPRuaY.js +0 -1
- zenml/zen_server/dashboard/assets/page-CDtSVkNc.js +0 -1
- zenml/zen_server/dashboard/assets/page-CEDU0L2T.js +0 -1
- zenml/zen_server/dashboard/assets/page-COJK90rG.js +0 -1
- zenml/zen_server/dashboard/assets/page-C_XMn4GU.js +0 -1
- zenml/zen_server/dashboard/assets/page-Cb3KGsPK.js +0 -22
- zenml/zen_server/dashboard/assets/page-CiGOVsj3.js +0 -1
- zenml/zen_server/dashboard/assets/page-CmLSFMkW.js +0 -1
- zenml/zen_server/dashboard/assets/page-CnfCptXq.js +0 -1
- zenml/zen_server/dashboard/assets/page-CxzglV3-.js +0 -1
- zenml/zen_server/dashboard/assets/page-DVLez4R1.js +0 -1
- zenml/zen_server/dashboard/assets/page-Dg7-H_9i.js +0 -1
- zenml/zen_server/dashboard/assets/page-Dw7XuiSo.js +0 -18
- zenml/zen_server/dashboard/assets/page-XrmOHHg7.js +0 -1
- zenml/zen_server/dashboard/assets/page-oRm7D4TC.js +0 -1
- zenml/zen_server/dashboard/assets/page-x2GXC8sI.js +0 -1
- zenml/zen_server/dashboard/assets/page-z2FXP4GY.js +0 -1
- zenml/zen_server/dashboard/assets/primary-role-CPGHymjN.js +0 -1
- {zenml_nightly-0.84.0.dev20250729.dist-info → zenml_nightly-0.84.0.dev20250730.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.84.0.dev20250729.dist-info → zenml_nightly-0.84.0.dev20250730.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.84.0.dev20250729.dist-info → zenml_nightly-0.84.0.dev20250730.dist-info}/entry_points.txt +0 -0
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.84.0.
|
1
|
+
0.84.0.dev20250730
|
zenml/client.py
CHANGED
@@ -7339,19 +7339,24 @@ class Client(metaclass=ClientMetaClass):
|
|
7339
7339
|
def create_service_account(
|
7340
7340
|
self,
|
7341
7341
|
name: str,
|
7342
|
+
full_name: Optional[str] = None,
|
7342
7343
|
description: str = "",
|
7343
7344
|
) -> ServiceAccountResponse:
|
7344
7345
|
"""Create a new service account.
|
7345
7346
|
|
7346
7347
|
Args:
|
7347
7348
|
name: The name of the service account.
|
7349
|
+
full_name: The display name of the service account.
|
7348
7350
|
description: The description of the service account.
|
7349
7351
|
|
7350
7352
|
Returns:
|
7351
7353
|
The created service account.
|
7352
7354
|
"""
|
7353
7355
|
service_account = ServiceAccountRequest(
|
7354
|
-
name=name,
|
7356
|
+
name=name,
|
7357
|
+
full_name=full_name or "",
|
7358
|
+
description=description,
|
7359
|
+
active=True,
|
7355
7360
|
)
|
7356
7361
|
created_service_account = self.zen_store.create_service_account(
|
7357
7362
|
service_account=service_account
|
zenml/constants.py
CHANGED
@@ -174,6 +174,12 @@ ENV_ZENML_ENABLE_IMPLICIT_AUTH_METHODS = "ZENML_ENABLE_IMPLICIT_AUTH_METHODS"
|
|
174
174
|
ENV_ZENML_DISABLE_PIPELINE_LOGS_STORAGE = "ZENML_DISABLE_PIPELINE_LOGS_STORAGE"
|
175
175
|
ENV_ZENML_DISABLE_STEP_LOGS_STORAGE = "ZENML_DISABLE_STEP_LOGS_STORAGE"
|
176
176
|
ENV_ZENML_DISABLE_STEP_NAMES_IN_LOGS = "ZENML_DISABLE_STEP_NAMES_IN_LOGS"
|
177
|
+
ENV_ZENML_LOGS_STORAGE_MAX_QUEUE_SIZE = "ZENML_LOGS_STORAGE_MAX_QUEUE_SIZE"
|
178
|
+
ENV_ZENML_LOGS_STORAGE_QUEUE_TIMEOUT = "ZENML_LOGS_STORAGE_QUEUE_TIMEOUT"
|
179
|
+
|
180
|
+
ENV_ZENML_LOGS_WRITE_INTERVAL_SECONDS = "ZENML_LOGS_WRITE_INTERVAL_SECONDS"
|
181
|
+
ENV_ZENML_LOGS_MERGE_INTERVAL_SECONDS = "ZENML_LOGS_MERGE_INTERVAL_SECONDS"
|
182
|
+
|
177
183
|
ENV_ZENML_CUSTOM_SOURCE_ROOT = "ZENML_CUSTOM_SOURCE_ROOT"
|
178
184
|
ENV_ZENML_PIPELINE_RUN_API_TOKEN_EXPIRATION = (
|
179
185
|
"ZENML_PIPELINE_API_TOKEN_EXPIRATION"
|
@@ -504,3 +510,22 @@ STACK_DEPLOYMENT_API_TOKEN_EXPIRATION = 60 * 6 # 6 hours
|
|
504
510
|
ZENML_PIPELINE_RUN_API_TOKEN_EXPIRATION = handle_int_env_var(
|
505
511
|
ENV_ZENML_PIPELINE_RUN_API_TOKEN_EXPIRATION, default=0
|
506
512
|
)
|
513
|
+
|
514
|
+
# Logs storage constants
|
515
|
+
# Maximum number of log batches to queue.
|
516
|
+
LOGS_STORAGE_MAX_QUEUE_SIZE = handle_int_env_var(
|
517
|
+
ENV_ZENML_LOGS_STORAGE_MAX_QUEUE_SIZE, default=100000
|
518
|
+
)
|
519
|
+
# Queue timeout controls log dropping vs application blocking:
|
520
|
+
# - Positive value (e.g., 30): Wait N seconds, then drop logs if queue full
|
521
|
+
# - Negative value (e.g., -1): Block indefinitely until queue has space (never drop logs)
|
522
|
+
LOGS_STORAGE_QUEUE_TIMEOUT = handle_int_env_var(
|
523
|
+
ENV_ZENML_LOGS_STORAGE_QUEUE_TIMEOUT, default=-1
|
524
|
+
)
|
525
|
+
# Log batch-write and merge windows in seconds.
|
526
|
+
LOGS_WRITE_INTERVAL_SECONDS = handle_int_env_var(
|
527
|
+
ENV_ZENML_LOGS_WRITE_INTERVAL_SECONDS, default=2
|
528
|
+
)
|
529
|
+
LOGS_MERGE_INTERVAL_SECONDS = handle_int_env_var(
|
530
|
+
ENV_ZENML_LOGS_MERGE_INTERVAL_SECONDS, default=10 * 60
|
531
|
+
)
|
@@ -43,9 +43,12 @@ class GcpIntegration(Integration):
|
|
43
43
|
"""Definition of Google Cloud Platform integration for ZenML."""
|
44
44
|
|
45
45
|
NAME = GCP
|
46
|
+
# Adding the gcsfs<=2024.12.0 for now to solve the issue with the
|
47
|
+
# increased number of list calls. This is a temporary fix and should be
|
48
|
+
# removed once the issue is resolved.
|
46
49
|
REQUIREMENTS = [
|
47
50
|
"kfp>=2.6.0",
|
48
|
-
"gcsfs!=2025.5.0,!=2025.5.0.post1",
|
51
|
+
"gcsfs!=2025.5.0,!=2025.5.0.post1,<=2024.12.0",
|
49
52
|
"google-cloud-secret-manager",
|
50
53
|
"google-cloud-container>=2.21.0",
|
51
54
|
"google-cloud-artifact-registry>=1.11.3",
|
zenml/logging/__init__.py
CHANGED
@@ -11,14 +11,3 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
12
12
|
# or implied. See the License for the specific language governing
|
13
13
|
# permissions and limitations under the License.
|
14
|
-
|
15
|
-
"""Logging utilities."""
|
16
|
-
|
17
|
-
# How many seconds to wait before uploading logs to the artifact store
|
18
|
-
STEP_LOGS_STORAGE_INTERVAL_SECONDS: int = 15
|
19
|
-
|
20
|
-
# How many messages to buffer before uploading logs to the artifact store
|
21
|
-
STEP_LOGS_STORAGE_MAX_MESSAGES: int = 100
|
22
|
-
|
23
|
-
# How often to merge logs into a single file
|
24
|
-
STEP_LOGS_STORAGE_MERGE_INTERVAL_SECONDS: int = 10 * 60
|
zenml/logging/step_logging.py
CHANGED
@@ -13,10 +13,13 @@
|
|
13
13
|
# permissions and limitations under the License.
|
14
14
|
"""ZenML logging handler."""
|
15
15
|
|
16
|
+
import asyncio
|
16
17
|
import logging
|
17
18
|
import os
|
19
|
+
import queue
|
18
20
|
import re
|
19
21
|
import sys
|
22
|
+
import threading
|
20
23
|
import time
|
21
24
|
from contextlib import nullcontext
|
22
25
|
from contextvars import ContextVar
|
@@ -35,15 +38,14 @@ from zenml.client import Client
|
|
35
38
|
from zenml.constants import (
|
36
39
|
ENV_ZENML_DISABLE_PIPELINE_LOGS_STORAGE,
|
37
40
|
ENV_ZENML_DISABLE_STEP_NAMES_IN_LOGS,
|
41
|
+
LOGS_MERGE_INTERVAL_SECONDS,
|
42
|
+
LOGS_STORAGE_MAX_QUEUE_SIZE,
|
43
|
+
LOGS_STORAGE_QUEUE_TIMEOUT,
|
44
|
+
LOGS_WRITE_INTERVAL_SECONDS,
|
38
45
|
handle_bool_env_var,
|
39
46
|
)
|
40
47
|
from zenml.exceptions import DoesNotExistException
|
41
48
|
from zenml.logger import get_logger
|
42
|
-
from zenml.logging import (
|
43
|
-
STEP_LOGS_STORAGE_INTERVAL_SECONDS,
|
44
|
-
STEP_LOGS_STORAGE_MAX_MESSAGES,
|
45
|
-
STEP_LOGS_STORAGE_MERGE_INTERVAL_SECONDS,
|
46
|
-
)
|
47
49
|
from zenml.models import (
|
48
50
|
LogsRequest,
|
49
51
|
PipelineDeploymentResponse,
|
@@ -58,6 +60,8 @@ logger = get_logger(__name__)
|
|
58
60
|
|
59
61
|
redirected: ContextVar[bool] = ContextVar("redirected", default=False)
|
60
62
|
|
63
|
+
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
64
|
+
|
61
65
|
LOGS_EXTENSION = ".log"
|
62
66
|
PIPELINE_RUN_LOGS_FOLDER = "pipeline_runs"
|
63
67
|
|
@@ -71,7 +75,6 @@ def remove_ansi_escape_codes(text: str) -> str:
|
|
71
75
|
Returns:
|
72
76
|
the version of the input string where the escape codes are removed.
|
73
77
|
"""
|
74
|
-
ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
75
78
|
return ansi_escape.sub("", text)
|
76
79
|
|
77
80
|
|
@@ -232,93 +235,206 @@ def fetch_logs(
|
|
232
235
|
|
233
236
|
|
234
237
|
class PipelineLogsStorage:
|
235
|
-
"""Helper class which buffers and stores logs to a given URI."""
|
238
|
+
"""Helper class which buffers and stores logs to a given URI using a background thread."""
|
236
239
|
|
237
240
|
def __init__(
|
238
241
|
self,
|
239
242
|
logs_uri: str,
|
240
243
|
artifact_store: "BaseArtifactStore",
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
+
max_queue_size: int = LOGS_STORAGE_MAX_QUEUE_SIZE,
|
245
|
+
queue_timeout: int = LOGS_STORAGE_QUEUE_TIMEOUT,
|
246
|
+
write_interval: int = LOGS_WRITE_INTERVAL_SECONDS,
|
247
|
+
merge_files_interval: int = LOGS_MERGE_INTERVAL_SECONDS,
|
244
248
|
) -> None:
|
245
249
|
"""Initialization.
|
246
250
|
|
247
251
|
Args:
|
248
252
|
logs_uri: the URI of the log file or folder.
|
249
253
|
artifact_store: Artifact Store from the current step context
|
250
|
-
|
251
|
-
|
252
|
-
|
254
|
+
max_queue_size: maximum number of individual messages to queue.
|
255
|
+
queue_timeout: timeout in seconds for putting items in queue when full.
|
256
|
+
- Positive value: Wait N seconds, then drop logs if queue still full
|
257
|
+
- Negative value: Block indefinitely until queue has space (never drop logs)
|
258
|
+
write_interval: the amount of seconds before the created files
|
259
|
+
get written to the artifact store.
|
253
260
|
merge_files_interval: the amount of seconds before the created files
|
254
261
|
get merged into a single file.
|
255
262
|
"""
|
256
263
|
# Parameters
|
257
264
|
self.logs_uri = logs_uri
|
258
|
-
self.
|
259
|
-
self.
|
265
|
+
self.max_queue_size = max_queue_size
|
266
|
+
self.queue_timeout = queue_timeout
|
267
|
+
self.write_interval = write_interval
|
260
268
|
self.merge_files_interval = merge_files_interval
|
261
269
|
|
262
270
|
# State
|
263
|
-
self.buffer: List[str] = []
|
264
|
-
self.disabled_buffer: List[str] = []
|
265
|
-
self.last_save_time = time.time()
|
266
|
-
self.disabled = False
|
267
271
|
self.artifact_store = artifact_store
|
268
272
|
|
269
273
|
# Immutable filesystems state
|
270
274
|
self.last_merge_time = time.time()
|
271
275
|
|
276
|
+
# Queue and log storage thread for async processing
|
277
|
+
self.log_queue: queue.Queue[str] = queue.Queue(maxsize=max_queue_size)
|
278
|
+
self.log_storage_thread: Optional[threading.Thread] = None
|
279
|
+
self.shutdown_event = threading.Event()
|
280
|
+
self.merge_event = threading.Event()
|
281
|
+
|
282
|
+
# Start the log storage thread
|
283
|
+
self._start_log_storage_thread()
|
284
|
+
|
285
|
+
def _start_log_storage_thread(self) -> None:
|
286
|
+
"""Start the log storage thread for processing log queue."""
|
287
|
+
if (
|
288
|
+
self.log_storage_thread is None
|
289
|
+
or not self.log_storage_thread.is_alive()
|
290
|
+
):
|
291
|
+
self.log_storage_thread = threading.Thread(
|
292
|
+
target=self._log_storage_worker,
|
293
|
+
name="LogsStorage-Worker",
|
294
|
+
)
|
295
|
+
self.log_storage_thread.start()
|
296
|
+
|
297
|
+
def _process_log_queue(self, force_merge: bool = False) -> None:
|
298
|
+
"""Write and merge logs to the artifact store using time-based batching.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
force_merge: Whether to force merge the logs.
|
302
|
+
"""
|
303
|
+
try:
|
304
|
+
messages = []
|
305
|
+
|
306
|
+
# Get first message (blocking with timeout)
|
307
|
+
try:
|
308
|
+
first_message = self.log_queue.get(timeout=1)
|
309
|
+
messages.append(first_message)
|
310
|
+
except queue.Empty:
|
311
|
+
return
|
312
|
+
|
313
|
+
# Get any remaining messages without waiting (drain quickly)
|
314
|
+
while True:
|
315
|
+
try:
|
316
|
+
additional_message = self.log_queue.get_nowait()
|
317
|
+
messages.append(additional_message)
|
318
|
+
except queue.Empty:
|
319
|
+
break
|
320
|
+
|
321
|
+
# Write the messages to the artifact store
|
322
|
+
if messages:
|
323
|
+
self.write_buffer(messages)
|
324
|
+
|
325
|
+
# Merge the log files if needed
|
326
|
+
if (
|
327
|
+
self._is_merge_needed
|
328
|
+
or self.merge_event.is_set()
|
329
|
+
or force_merge
|
330
|
+
):
|
331
|
+
self.merge_event.clear()
|
332
|
+
|
333
|
+
self.merge_log_files(merge_all_files=force_merge)
|
334
|
+
|
335
|
+
except Exception as e:
|
336
|
+
logger.error("Error in log storage thread: %s", e)
|
337
|
+
finally:
|
338
|
+
# Always mark all queue tasks as done
|
339
|
+
for _ in messages:
|
340
|
+
self.log_queue.task_done()
|
341
|
+
|
342
|
+
time.sleep(self.write_interval)
|
343
|
+
|
344
|
+
def _log_storage_worker(self) -> None:
|
345
|
+
"""Log storage thread worker that processes the log queue."""
|
346
|
+
# Process the log queue until shutdown is requested
|
347
|
+
while not self.shutdown_event.is_set():
|
348
|
+
self._process_log_queue()
|
349
|
+
|
350
|
+
# Shutdown requested - drain remaining queue items and merge log files
|
351
|
+
self._process_log_queue(force_merge=True)
|
352
|
+
|
353
|
+
def _shutdown_log_storage_thread(self, timeout: int = 5) -> None:
|
354
|
+
"""Shutdown the log storage thread gracefully.
|
355
|
+
|
356
|
+
Args:
|
357
|
+
timeout: Maximum time to wait for thread shutdown.
|
358
|
+
"""
|
359
|
+
if self.log_storage_thread and self.log_storage_thread.is_alive():
|
360
|
+
# Then signal the worker to begin graceful shutdown
|
361
|
+
self.shutdown_event.set()
|
362
|
+
|
363
|
+
# Wait for thread to finish (it will drain the queue automatically)
|
364
|
+
self.log_storage_thread.join(timeout=timeout)
|
365
|
+
|
272
366
|
def write(self, text: str) -> None:
|
273
|
-
"""Main write method.
|
367
|
+
"""Main write method that sends individual messages directly to queue.
|
274
368
|
|
275
369
|
Args:
|
276
370
|
text: the incoming string.
|
277
371
|
"""
|
372
|
+
# Skip empty lines
|
278
373
|
if text == "\n":
|
279
374
|
return
|
280
375
|
|
281
|
-
|
282
|
-
|
376
|
+
# If the current thread is the log storage thread, do nothing
|
377
|
+
# to prevent recursion when the storage thread itself generates logs
|
378
|
+
if (
|
379
|
+
self.log_storage_thread
|
380
|
+
and threading.current_thread() == self.log_storage_thread
|
381
|
+
):
|
382
|
+
return
|
383
|
+
|
384
|
+
# If the current thread is the fsspec IO thread, do nothing
|
385
|
+
if self._is_fsspec_io_thread:
|
386
|
+
return
|
387
|
+
|
388
|
+
try:
|
389
|
+
# Format the message with timestamp
|
283
390
|
timestamp = utc_now().strftime("%Y-%m-%d %H:%M:%S")
|
284
391
|
formatted_message = (
|
285
392
|
f"[{timestamp} UTC] {remove_ansi_escape_codes(text)}"
|
286
393
|
)
|
287
|
-
|
288
|
-
|
394
|
+
formatted_message = formatted_message.rstrip()
|
395
|
+
|
396
|
+
# Send individual message directly to queue
|
397
|
+
if not self.shutdown_event.is_set():
|
398
|
+
try:
|
399
|
+
if self.queue_timeout < 0:
|
400
|
+
# Negative timeout = block indefinitely until queue has space
|
401
|
+
# Guarantees no log loss but may hang application
|
402
|
+
self.log_queue.put(formatted_message)
|
403
|
+
else:
|
404
|
+
# Positive timeout = wait specified time then drop logs
|
405
|
+
# Prevents application hanging but may lose logs
|
406
|
+
self.log_queue.put(
|
407
|
+
formatted_message, timeout=self.queue_timeout
|
408
|
+
)
|
409
|
+
except queue.Full:
|
410
|
+
# This only happens with positive timeout
|
411
|
+
# Queue is full - just skip this message to avoid blocking
|
412
|
+
# Better to drop logs than hang the application
|
413
|
+
pass
|
414
|
+
|
415
|
+
except Exception:
|
416
|
+
# Silently ignore errors to prevent recursion
|
417
|
+
pass
|
289
418
|
|
290
419
|
@property
|
291
|
-
def
|
292
|
-
"""Checks whether the
|
420
|
+
def _is_merge_needed(self) -> bool:
|
421
|
+
"""Checks whether the log files need to be merged.
|
293
422
|
|
294
423
|
Returns:
|
295
|
-
whether the
|
424
|
+
whether the log files need to be merged.
|
296
425
|
"""
|
297
426
|
return (
|
298
|
-
|
299
|
-
|
427
|
+
self.artifact_store.config.IS_IMMUTABLE_FILESYSTEM
|
428
|
+
and time.time() - self.last_merge_time > self.merge_files_interval
|
300
429
|
)
|
301
430
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
Args:
|
306
|
-
suffix: optional suffix for the file name
|
431
|
+
@property
|
432
|
+
def _is_fsspec_io_thread(self) -> bool:
|
433
|
+
"""Checks if the current thread is the fsspec IO thread.
|
307
434
|
|
308
435
|
Returns:
|
309
|
-
|
436
|
+
whether the current thread is the fsspec IO thread.
|
310
437
|
"""
|
311
|
-
return f"{time.time()}{suffix}{LOGS_EXTENSION}"
|
312
|
-
|
313
|
-
def save_to_file(self, force: bool = False) -> None:
|
314
|
-
"""Method to save the buffer to the given URI.
|
315
|
-
|
316
|
-
Args:
|
317
|
-
force: whether to force a save even if the write conditions not met.
|
318
|
-
"""
|
319
|
-
import asyncio
|
320
|
-
import threading
|
321
|
-
|
322
438
|
# Most artifact stores are based on fsspec, which converts between
|
323
439
|
# sync and async operations by using a separate AIO thread.
|
324
440
|
# It may happen that the fsspec call itself will log something,
|
@@ -329,82 +445,79 @@ class PipelineLogsStorage:
|
|
329
445
|
# To avoid this, we simply check if we're running in the fsspec AIO
|
330
446
|
# thread and skip the save if that's the case.
|
331
447
|
try:
|
332
|
-
|
448
|
+
return (
|
333
449
|
asyncio.events.get_running_loop() is not None
|
334
450
|
and threading.current_thread().name == "fsspecIO"
|
335
|
-
)
|
336
|
-
return
|
451
|
+
)
|
337
452
|
except RuntimeError:
|
338
453
|
# No running loop
|
339
|
-
|
454
|
+
return False
|
340
455
|
|
341
|
-
|
342
|
-
|
343
|
-
# code that follows might still emit logging messages, which will
|
344
|
-
# end up triggering this method again, causing an infinite loop.
|
345
|
-
self.disabled = True
|
456
|
+
def _get_timestamped_filename(self, suffix: str = "") -> str:
|
457
|
+
"""Returns a timestamped filename.
|
346
458
|
|
347
|
-
|
348
|
-
|
349
|
-
# logs generated by different threads are not interleaved.
|
350
|
-
# Given that most artifact stores are based on fsspec, which
|
351
|
-
# use a separate thread for async operations, it may happen that
|
352
|
-
# the fsspec library itself will log something, which will end
|
353
|
-
# up in a deadlock.
|
354
|
-
# To avoid this, we temporarily disable the lock in the logging
|
355
|
-
# handler while writing to the file.
|
356
|
-
logging_handler = logging.getLogger().handlers[0]
|
357
|
-
logging_lock = logging_handler.lock
|
358
|
-
logging_handler.lock = None
|
359
|
-
|
360
|
-
if self.buffer:
|
361
|
-
if self.artifact_store.config.IS_IMMUTABLE_FILESYSTEM:
|
362
|
-
_logs_uri = self._get_timestamped_filename()
|
363
|
-
with self.artifact_store.open(
|
364
|
-
os.path.join(
|
365
|
-
self.logs_uri,
|
366
|
-
_logs_uri,
|
367
|
-
),
|
368
|
-
"w",
|
369
|
-
) as file:
|
370
|
-
for message in self.buffer:
|
371
|
-
file.write(f"{message}\n")
|
372
|
-
else:
|
373
|
-
with self.artifact_store.open(
|
374
|
-
self.logs_uri, "a"
|
375
|
-
) as file:
|
376
|
-
for message in self.buffer:
|
377
|
-
file.write(f"{message}\n")
|
378
|
-
self.artifact_store._remove_previous_file_versions(
|
379
|
-
self.logs_uri
|
380
|
-
)
|
459
|
+
Args:
|
460
|
+
suffix: optional suffix for the file name
|
381
461
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
# I/O errors.
|
387
|
-
logger.error(f"Error while trying to write logs: {e}")
|
388
|
-
finally:
|
389
|
-
# Restore the original logging handler lock
|
390
|
-
logging_handler.lock = logging_lock
|
462
|
+
Returns:
|
463
|
+
The timestamped filename.
|
464
|
+
"""
|
465
|
+
return f"{time.time()}{suffix}{LOGS_EXTENSION}"
|
391
466
|
|
392
|
-
|
393
|
-
|
467
|
+
def write_buffer(self, buffer_to_write: List[str]) -> None:
|
468
|
+
"""Write the given buffer to file. This runs in the log storage thread.
|
394
469
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
if
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
470
|
+
Args:
|
471
|
+
buffer_to_write: The buffer contents to write to file.
|
472
|
+
"""
|
473
|
+
if not buffer_to_write:
|
474
|
+
return
|
475
|
+
|
476
|
+
# The configured logging handler uses a lock to ensure that
|
477
|
+
# logs generated by different threads are not interleaved.
|
478
|
+
# Given that most artifact stores are based on fsspec, which
|
479
|
+
# use a separate thread for async operations, it may happen that
|
480
|
+
# the fsspec library itself will log something, which will end
|
481
|
+
# up in a deadlock.
|
482
|
+
# To avoid this, we temporarily disable the lock in the logging
|
483
|
+
# handler while writing to the file.
|
484
|
+
logging_handler = None
|
485
|
+
logging_lock = None
|
486
|
+
try:
|
487
|
+
# Only try to access logging handler if it exists
|
488
|
+
root_logger = logging.getLogger()
|
489
|
+
if root_logger.handlers:
|
490
|
+
logging_handler = root_logger.handlers[0]
|
491
|
+
logging_lock = getattr(logging_handler, "lock", None)
|
492
|
+
if logging_lock:
|
493
|
+
logging_handler.lock = None
|
494
|
+
|
495
|
+
# If the artifact store is immutable, write the buffer to a new file
|
496
|
+
if self.artifact_store.config.IS_IMMUTABLE_FILESYSTEM:
|
497
|
+
_logs_uri = self._get_timestamped_filename()
|
498
|
+
with self.artifact_store.open(
|
499
|
+
os.path.join(
|
500
|
+
self.logs_uri,
|
501
|
+
_logs_uri,
|
502
|
+
),
|
503
|
+
"w",
|
504
|
+
) as file:
|
505
|
+
for message in buffer_to_write:
|
506
|
+
file.write(f"{message}\n")
|
507
|
+
|
508
|
+
# If the artifact store is mutable, append the buffer to the existing file
|
509
|
+
else:
|
510
|
+
with self.artifact_store.open(self.logs_uri, "a") as file:
|
511
|
+
for message in buffer_to_write:
|
512
|
+
file.write(f"{message}\n")
|
513
|
+
self.artifact_store._remove_previous_file_versions(
|
514
|
+
self.logs_uri
|
515
|
+
)
|
516
|
+
|
517
|
+
finally:
|
518
|
+
# Re-enable the logging lock
|
519
|
+
if logging_handler and logging_lock:
|
520
|
+
logging_handler.lock = logging_lock
|
408
521
|
|
409
522
|
def merge_log_files(self, merge_all_files: bool = False) -> None:
|
410
523
|
"""Merges all log files into one in the given URI.
|
@@ -414,12 +527,15 @@ class PipelineLogsStorage:
|
|
414
527
|
Args:
|
415
528
|
merge_all_files: whether to merge all files or only raw files
|
416
529
|
"""
|
530
|
+
# If the artifact store is immutable, merge the log files
|
417
531
|
if self.artifact_store.config.IS_IMMUTABLE_FILESYSTEM:
|
418
532
|
merged_file_suffix = "_merged"
|
419
533
|
files_ = self.artifact_store.listdir(self.logs_uri)
|
420
534
|
if not merge_all_files:
|
421
535
|
# already merged files will not be merged again
|
422
|
-
files_ = [
|
536
|
+
files_ = [
|
537
|
+
f for f in files_ if merged_file_suffix not in str(f)
|
538
|
+
]
|
423
539
|
file_name_ = self._get_timestamped_filename(
|
424
540
|
suffix=merged_file_suffix
|
425
541
|
)
|
@@ -453,6 +569,13 @@ class PipelineLogsStorage:
|
|
453
569
|
os.path.join(self.logs_uri, str(file))
|
454
570
|
)
|
455
571
|
|
572
|
+
# Update the last merge time
|
573
|
+
self.last_merge_time = time.time()
|
574
|
+
|
575
|
+
def send_merge_event(self) -> None:
|
576
|
+
"""Send a merge event to the log storage thread."""
|
577
|
+
self.merge_event.set()
|
578
|
+
|
456
579
|
|
457
580
|
class PipelineLogsStorageContext:
|
458
581
|
"""Context manager which patches stdout and stderr during pipeline run execution."""
|
@@ -474,6 +597,7 @@ class PipelineLogsStorageContext:
|
|
474
597
|
logs_uri=logs_uri, artifact_store=artifact_store
|
475
598
|
)
|
476
599
|
self.prepend_step_name = prepend_step_name
|
600
|
+
self._original_methods_saved = False
|
477
601
|
|
478
602
|
def __enter__(self) -> "PipelineLogsStorageContext":
|
479
603
|
"""Enter condition of the context manager.
|
@@ -484,17 +608,16 @@ class PipelineLogsStorageContext:
|
|
484
608
|
Returns:
|
485
609
|
self
|
486
610
|
"""
|
611
|
+
# Save original write methods (flush methods are not wrapped)
|
487
612
|
self.stdout_write = getattr(sys.stdout, "write")
|
488
|
-
self.stdout_flush = getattr(sys.stdout, "flush")
|
489
|
-
|
490
613
|
self.stderr_write = getattr(sys.stderr, "write")
|
491
|
-
self.
|
614
|
+
self._original_methods_saved = True
|
492
615
|
|
616
|
+
# Wrap stdout/stderr write methods only
|
617
|
+
# Note: We don't wrap flush() as it's unnecessary overhead and the
|
618
|
+
# background thread handles log flushing based on time/buffer size
|
493
619
|
setattr(sys.stdout, "write", self._wrap_write(self.stdout_write))
|
494
|
-
setattr(sys.stdout, "flush", self._wrap_flush(self.stdout_flush))
|
495
|
-
|
496
620
|
setattr(sys.stderr, "write", self._wrap_write(self.stderr_write))
|
497
|
-
setattr(sys.stderr, "flush", self._wrap_flush(self.stderr_flush))
|
498
621
|
|
499
622
|
redirected.set(True)
|
500
623
|
return self
|
@@ -514,20 +637,20 @@ class PipelineLogsStorageContext:
|
|
514
637
|
|
515
638
|
Restores the `write` method of both stderr and stdout.
|
516
639
|
"""
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
redirected.set(False)
|
640
|
+
# Step 1: Restore stdout/stderr FIRST to prevent logging during shutdown
|
641
|
+
try:
|
642
|
+
if self._original_methods_saved:
|
643
|
+
setattr(sys.stdout, "write", self.stdout_write)
|
644
|
+
setattr(sys.stderr, "write", self.stderr_write)
|
645
|
+
redirected.set(False)
|
646
|
+
except Exception:
|
647
|
+
pass
|
526
648
|
|
649
|
+
# Step 2: Shutdown thread (it will automatically drain queue and merge files)
|
527
650
|
try:
|
528
|
-
self.storage.
|
529
|
-
except
|
530
|
-
|
651
|
+
self.storage._shutdown_log_storage_thread()
|
652
|
+
except Exception:
|
653
|
+
pass
|
531
654
|
|
532
655
|
def _wrap_write(self, method: Callable[..., Any]) -> Callable[..., Any]:
|
533
656
|
"""Wrapper function that utilizes the storage object to store logs.
|
@@ -577,23 +700,6 @@ class PipelineLogsStorageContext:
|
|
577
700
|
|
578
701
|
return wrapped_write
|
579
702
|
|
580
|
-
def _wrap_flush(self, method: Callable[..., Any]) -> Callable[..., Any]:
|
581
|
-
"""Wrapper function that flushes the buffer of the storage object.
|
582
|
-
|
583
|
-
Args:
|
584
|
-
method: the original flush method
|
585
|
-
|
586
|
-
Returns:
|
587
|
-
the wrapped flush method.
|
588
|
-
"""
|
589
|
-
|
590
|
-
def wrapped_flush(*args: Any, **kwargs: Any) -> Any:
|
591
|
-
output = method(*args, **kwargs)
|
592
|
-
self.storage.save_to_file()
|
593
|
-
return output
|
594
|
-
|
595
|
-
return wrapped_flush
|
596
|
-
|
597
703
|
|
598
704
|
def setup_orchestrator_logging(
|
599
705
|
run_id: str, deployment: "PipelineDeploymentResponse"
|
zenml/models/__init__.py
CHANGED
@@ -283,6 +283,8 @@ from zenml.models.v2.core.service_account import (
|
|
283
283
|
ServiceAccountResponseMetadata,
|
284
284
|
ServiceAccountUpdate,
|
285
285
|
ServiceAccountRequest,
|
286
|
+
ServiceAccountInternalRequest,
|
287
|
+
ServiceAccountInternalUpdate,
|
286
288
|
ServiceAccountResponse,
|
287
289
|
)
|
288
290
|
from zenml.models.v2.core.service_connector import (
|
@@ -740,6 +742,8 @@ __all__ = [
|
|
740
742
|
"ServiceAccountResponseMetadata",
|
741
743
|
"ServiceAccountUpdate",
|
742
744
|
"ServiceAccountRequest",
|
745
|
+
"ServiceAccountInternalRequest",
|
746
|
+
"ServiceAccountInternalUpdate",
|
743
747
|
"ServiceAccountResponse",
|
744
748
|
"ServiceConnectorConfiguration",
|
745
749
|
"ServiceConnectorRequest",
|