flowyml 1.3.0__tar.gz → 1.5.0__tar.gz
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.
- {flowyml-1.3.0 → flowyml-1.5.0}/PKG-INFO +3 -1
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/execution_status.py +1 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/executor.py +175 -3
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/observability.py +7 -7
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/resources.py +12 -12
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/retry_policy.py +2 -2
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/scheduler.py +9 -9
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/scheduler_config.py +2 -3
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/submission_result.py +4 -4
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/bridge.py +9 -9
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/plugins.py +2 -2
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/registry.py +21 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/base.py +33 -0
- flowyml-1.5.0/flowyml/storage/metadata.py +53 -0
- flowyml-1.5.0/flowyml/storage/remote.py +590 -0
- flowyml-1.5.0/flowyml/storage/sql.py +951 -0
- flowyml-1.5.0/flowyml/ui/backend/dependencies.py +28 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/main.py +4 -79
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/assets.py +170 -9
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/client.py +6 -6
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/execution.py +2 -2
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/experiments.py +53 -6
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/metrics.py +23 -68
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/pipelines.py +19 -10
- flowyml-1.5.0/flowyml/ui/backend/routers/runs.py +486 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/schedules.py +5 -21
- flowyml-1.5.0/flowyml/ui/backend/routers/stats.py +14 -0
- flowyml-1.5.0/flowyml/ui/backend/routers/traces.py +68 -0
- flowyml-1.5.0/flowyml/ui/backend/routers/websocket.py +121 -0
- flowyml-1.5.0/flowyml/ui/frontend/dist/assets/index-CBUXOWze.css +1 -0
- flowyml-1.5.0/flowyml/ui/frontend/dist/assets/index-DF8dJaFL.js +629 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/dist/index.html +2 -2
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/package-lock.json +289 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/package.json +1 -0
- flowyml-1.5.0/flowyml/ui/frontend/src/app/compare/page.jsx +213 -0
- flowyml-1.5.0/flowyml/ui/frontend/src/app/experiments/compare/page.jsx +289 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/experiments/page.jsx +61 -1
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +418 -203
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/runs/page.jsx +64 -3
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/settings/page.jsx +1 -1
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/tokens/page.jsx +8 -6
- flowyml-1.5.0/flowyml/ui/frontend/src/components/ArtifactViewer.jsx +159 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/NavigationTree.jsx +26 -9
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/PipelineGraph.jsx +26 -24
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +42 -14
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/router/index.jsx +4 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/pyproject.toml +3 -1
- flowyml-1.3.0/flowyml/storage/metadata.py +0 -1092
- flowyml-1.3.0/flowyml/ui/backend/routers/runs.py +0 -208
- flowyml-1.3.0/flowyml/ui/backend/routers/traces.py +0 -84
- flowyml-1.3.0/flowyml/ui/frontend/dist/assets/index-DcYwrn2j.css +0 -1
- flowyml-1.3.0/flowyml/ui/frontend/dist/assets/index-Dlz_ygOL.js +0 -592
- {flowyml-1.3.0 → flowyml-1.5.0}/LICENSE +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/README.md +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/artifact.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/base.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/dataset.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/featureset.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/metrics.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/model.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/registry.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/assets/report.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/experiment.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/init.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/main.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/run.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/stack_cli.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/cli/ui.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/advanced_cache.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/approval.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/cache.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/checkpoint.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/conditional.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/context.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/error_handling.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/graph.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/hooks.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/orchestrator.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/parallel.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/pipeline.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/project.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/remote_orchestrator.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/step.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/step_grouping.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/templates.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/core/versioning.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/integrations/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/integrations/keras.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/monitoring/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/monitoring/alerts.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/monitoring/data.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/monitoring/llm.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/monitoring/monitor.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/monitoring/notifications.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/registry/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/registry/model_registry.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/registry/pipeline_registry.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/aws.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/azure.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/base.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/components.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/gcp.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/local.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/migration.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/stacks/plugin_config.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/artifacts.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/cloudpickle.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/keras.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/numpy.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/pandas.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/pytorch.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/sklearn.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/storage/materializers/tensorflow.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/tracking/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/tracking/experiment.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/tracking/leaderboard.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/tracking/runs.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/Dockerfile +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/auth.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/leaderboard.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/notifications.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/plugins.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/backend/routers/projects.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/Dockerfile +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/README.md +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/index.html +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/nginx.conf +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/postcss.config.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/App.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/assets/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/dashboard/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/experiments/[experimentId]/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/leaderboard/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/observability/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/pipelines/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/plugins/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectArtifactsList.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHeader.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectHierarchy.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectMetricsPanel.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectPipelinesList.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRelations.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectRunsList.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectTabs.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/[projectId]/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/projects/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/schedules/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/app/traces/page.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/AssetLineageGraph.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/AssetStatsDashboard.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/AssetTreeHierarchy.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ExperimentDetailsPanel.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/Layout.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/PipelineDetailsPanel.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ProjectSelector.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/header/Header.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/plugins/AddPluginDialog.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/plugins/InstalledPlugins.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/plugins/PluginBrowser.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/plugins/ZenMLIntegration.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/Badge.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/Button.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/Card.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/CodeSnippet.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/CollapsibleCard.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/DataView.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/EmptyState.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/ErrorBoundary.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/ExecutionStatus.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/KeyValue.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/components/ui/ProjectSelector.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/contexts/ProjectContext.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/contexts/ThemeContext.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/contexts/ToastContext.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/index.css +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/layouts/MainLayout.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/main.jsx +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/services/pluginService.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/utils/api.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/utils/cn.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/utils/date.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/src/utils/downloads.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/tailwind.config.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/frontend/vite.config.js +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/ui/utils.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/__init__.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/config.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/debug.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/environment.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/git.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/logging.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/performance.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/stack_config.py +0 -0
- {flowyml-1.3.0 → flowyml-1.5.0}/flowyml/utils/validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowyml
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Next-Generation ML Pipeline Framework
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -36,11 +36,13 @@ Requires-Dist: httpx (>=0.24,<0.28)
|
|
|
36
36
|
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
|
37
37
|
Requires-Dist: numpy (>=1.20.0)
|
|
38
38
|
Requires-Dist: pandas (>=1.3.0)
|
|
39
|
+
Requires-Dist: psycopg2-binary (>=2.9.0)
|
|
39
40
|
Requires-Dist: pydantic (>=2.0.0)
|
|
40
41
|
Requires-Dist: python-multipart (>=0.0.6) ; extra == "ui" or extra == "all"
|
|
41
42
|
Requires-Dist: pytz (>=2024.1,<2025.0)
|
|
42
43
|
Requires-Dist: pyyaml (>=6.0)
|
|
43
44
|
Requires-Dist: scikit-learn (>=1.0.0) ; extra == "sklearn" or extra == "all"
|
|
45
|
+
Requires-Dist: sqlalchemy (>=2.0.0)
|
|
44
46
|
Requires-Dist: tensorflow (>=2.12.0) ; extra == "tensorflow" or extra == "all"
|
|
45
47
|
Requires-Dist: toml (>=0.10.2)
|
|
46
48
|
Requires-Dist: torch (>=2.0.0) ; extra == "pytorch" or extra == "all"
|
|
@@ -7,6 +7,133 @@ from typing import Any
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
|
|
10
|
+
import threading
|
|
11
|
+
import ctypes
|
|
12
|
+
import requests
|
|
13
|
+
import os
|
|
14
|
+
import inspect
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StopExecutionError(Exception):
|
|
18
|
+
"""Exception raised when execution is stopped externally."""
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Alias for backwards compatibility
|
|
24
|
+
StopExecution = StopExecutionError
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _async_raise(tid, exctype):
|
|
28
|
+
"""Raises an exception in the threads with id tid"""
|
|
29
|
+
if not inspect.isclass(exctype):
|
|
30
|
+
raise TypeError("Only types can be raised (not instances)")
|
|
31
|
+
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype))
|
|
32
|
+
if res == 0:
|
|
33
|
+
raise ValueError("invalid thread id")
|
|
34
|
+
if res != 1:
|
|
35
|
+
# """if it returns a number greater than one, you're in trouble,
|
|
36
|
+
# and you should call it again with exc=NULL to revert the effect"""
|
|
37
|
+
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
|
|
38
|
+
raise SystemError("PyThreadState_SetAsyncExc failed")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class LogCapture:
|
|
42
|
+
"""Context manager to capture stdout/stderr for streaming to the server."""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self._buffer = []
|
|
46
|
+
self._lock = threading.Lock()
|
|
47
|
+
|
|
48
|
+
def write(self, text):
|
|
49
|
+
if text.strip():
|
|
50
|
+
with self._lock:
|
|
51
|
+
self._buffer.append(text)
|
|
52
|
+
|
|
53
|
+
def flush(self):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def get_and_clear(self) -> list[str]:
|
|
57
|
+
with self._lock:
|
|
58
|
+
lines = self._buffer[:]
|
|
59
|
+
self._buffer.clear()
|
|
60
|
+
return lines
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class MonitorThread(threading.Thread):
|
|
64
|
+
"""Background thread that sends heartbeats and flushes logs to the server."""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
run_id: str,
|
|
69
|
+
step_name: str,
|
|
70
|
+
target_tid: int,
|
|
71
|
+
log_capture: LogCapture | None = None,
|
|
72
|
+
interval: int = 5,
|
|
73
|
+
):
|
|
74
|
+
super().__init__()
|
|
75
|
+
self.run_id = run_id
|
|
76
|
+
self.step_name = step_name
|
|
77
|
+
self.target_tid = target_tid
|
|
78
|
+
self.log_capture = log_capture
|
|
79
|
+
self.interval = interval
|
|
80
|
+
self._stop_event = threading.Event()
|
|
81
|
+
self.api_url = os.getenv("FLOWYML_SERVER_URL", "http://localhost:8000")
|
|
82
|
+
|
|
83
|
+
def stop(self):
|
|
84
|
+
self._stop_event.set()
|
|
85
|
+
|
|
86
|
+
def _flush_logs(self):
|
|
87
|
+
"""Send captured logs to the server."""
|
|
88
|
+
if not self.log_capture:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
lines = self.log_capture.get_and_clear()
|
|
92
|
+
if not lines:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
content = "".join(lines)
|
|
96
|
+
with contextlib.suppress(Exception):
|
|
97
|
+
requests.post(
|
|
98
|
+
f"{self.api_url}/api/runs/{self.run_id}/steps/{self.step_name}/logs",
|
|
99
|
+
json={
|
|
100
|
+
"content": content,
|
|
101
|
+
"level": "INFO",
|
|
102
|
+
"timestamp": datetime.now().isoformat(),
|
|
103
|
+
},
|
|
104
|
+
timeout=2,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def run(self):
|
|
108
|
+
while not self._stop_event.is_set():
|
|
109
|
+
try:
|
|
110
|
+
# Send heartbeat
|
|
111
|
+
response = requests.post(
|
|
112
|
+
f"{self.api_url}/api/runs/{self.run_id}/steps/{self.step_name}/heartbeat",
|
|
113
|
+
json={"step_name": self.step_name, "status": "running"},
|
|
114
|
+
timeout=2,
|
|
115
|
+
)
|
|
116
|
+
if response.status_code == 200:
|
|
117
|
+
data = response.json()
|
|
118
|
+
if data.get("action") == "stop":
|
|
119
|
+
print(f"Received stop signal for step {self.step_name}")
|
|
120
|
+
_async_raise(self.target_tid, StopExecution)
|
|
121
|
+
break
|
|
122
|
+
except Exception:
|
|
123
|
+
pass # Ignore heartbeat failures
|
|
124
|
+
|
|
125
|
+
# Flush logs
|
|
126
|
+
self._flush_logs()
|
|
127
|
+
|
|
128
|
+
self._stop_event.wait(self.interval)
|
|
129
|
+
|
|
130
|
+
# Final log flush
|
|
131
|
+
self._flush_logs()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Keep HeartbeatThread as an alias for backwards compatibility
|
|
135
|
+
HeartbeatThread = MonitorThread
|
|
136
|
+
|
|
10
137
|
|
|
11
138
|
@dataclass
|
|
12
139
|
class ExecutionResult:
|
|
@@ -103,8 +230,6 @@ class LocalExecutor(Executor):
|
|
|
103
230
|
# or just pass what we can.
|
|
104
231
|
# A simple approach: pass nothing if it takes no args, or kwargs if it does.
|
|
105
232
|
# But inspect is safer.
|
|
106
|
-
import inspect
|
|
107
|
-
|
|
108
233
|
sig = inspect.signature(step.condition)
|
|
109
234
|
kwargs = {**inputs, **context_params}
|
|
110
235
|
|
|
@@ -157,7 +282,54 @@ class LocalExecutor(Executor):
|
|
|
157
282
|
kwargs = {**inputs, **context_params}
|
|
158
283
|
|
|
159
284
|
# Execute step
|
|
160
|
-
|
|
285
|
+
monitor_thread = None
|
|
286
|
+
log_capture = None
|
|
287
|
+
original_stdout = None
|
|
288
|
+
original_stderr = None
|
|
289
|
+
try:
|
|
290
|
+
# Start monitoring thread with log capture if run_id is present
|
|
291
|
+
if run_id:
|
|
292
|
+
import sys
|
|
293
|
+
|
|
294
|
+
log_capture = LogCapture()
|
|
295
|
+
original_stdout = sys.stdout
|
|
296
|
+
original_stderr = sys.stderr
|
|
297
|
+
sys.stdout = log_capture
|
|
298
|
+
sys.stderr = log_capture
|
|
299
|
+
|
|
300
|
+
monitor_thread = MonitorThread(
|
|
301
|
+
run_id=run_id,
|
|
302
|
+
step_name=step.name,
|
|
303
|
+
target_tid=threading.get_ident(),
|
|
304
|
+
log_capture=log_capture,
|
|
305
|
+
)
|
|
306
|
+
monitor_thread.start()
|
|
307
|
+
|
|
308
|
+
result = step.func(**kwargs)
|
|
309
|
+
except StopExecution:
|
|
310
|
+
duration = time.time() - start_time
|
|
311
|
+
return ExecutionResult(
|
|
312
|
+
step_name=step.name,
|
|
313
|
+
success=False,
|
|
314
|
+
error="Execution stopped by user",
|
|
315
|
+
duration_seconds=duration,
|
|
316
|
+
retries=retries,
|
|
317
|
+
)
|
|
318
|
+
finally:
|
|
319
|
+
# Restore stdout/stderr
|
|
320
|
+
if original_stdout:
|
|
321
|
+
import sys
|
|
322
|
+
|
|
323
|
+
sys.stdout = original_stdout
|
|
324
|
+
if original_stderr:
|
|
325
|
+
import sys
|
|
326
|
+
|
|
327
|
+
sys.stderr = original_stderr
|
|
328
|
+
|
|
329
|
+
# Stop monitor thread
|
|
330
|
+
if monitor_thread:
|
|
331
|
+
monitor_thread.stop()
|
|
332
|
+
monitor_thread.join()
|
|
161
333
|
|
|
162
334
|
# Materialize output if artifact store is available
|
|
163
335
|
artifact_uri = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Observability hooks for monitoring and metrics collection."""
|
|
2
2
|
|
|
3
|
-
from typing import Protocol, Any,
|
|
3
|
+
from typing import Protocol, Any, TYPE_CHECKING
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
|
|
@@ -31,8 +31,8 @@ class PipelineMetricEvent(MetricEvent):
|
|
|
31
31
|
|
|
32
32
|
pipeline_name: str = ""
|
|
33
33
|
run_id: str = ""
|
|
34
|
-
duration_seconds:
|
|
35
|
-
success:
|
|
34
|
+
duration_seconds: float | None = None
|
|
35
|
+
success: bool | None = None
|
|
36
36
|
|
|
37
37
|
def to_dict(self) -> dict[str, Any]:
|
|
38
38
|
base = super().to_dict()
|
|
@@ -54,8 +54,8 @@ class StepMetricEvent(MetricEvent):
|
|
|
54
54
|
step_name: str = ""
|
|
55
55
|
pipeline_name: str = ""
|
|
56
56
|
run_id: str = ""
|
|
57
|
-
duration_seconds:
|
|
58
|
-
success:
|
|
57
|
+
duration_seconds: float | None = None
|
|
58
|
+
success: bool | None = None
|
|
59
59
|
cached: bool = False
|
|
60
60
|
|
|
61
61
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -196,7 +196,7 @@ class PrometheusMetricsCollector:
|
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
# Global metrics collector
|
|
199
|
-
_metrics_collector:
|
|
199
|
+
_metrics_collector: MetricsCollector | None = None
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
def set_metrics_collector(collector: MetricsCollector) -> None:
|
|
@@ -205,6 +205,6 @@ def set_metrics_collector(collector: MetricsCollector) -> None:
|
|
|
205
205
|
_metrics_collector = collector
|
|
206
206
|
|
|
207
207
|
|
|
208
|
-
def get_metrics_collector() ->
|
|
208
|
+
def get_metrics_collector() -> MetricsCollector | None:
|
|
209
209
|
"""Get global metrics collector."""
|
|
210
210
|
return _metrics_collector
|
|
@@ -5,7 +5,7 @@ including CPU, memory, GPU, storage, and node affinity requirements.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
import re
|
|
10
10
|
|
|
11
11
|
|
|
@@ -25,7 +25,7 @@ class GPUConfig:
|
|
|
25
25
|
|
|
26
26
|
gpu_type: str
|
|
27
27
|
count: int = 1
|
|
28
|
-
memory:
|
|
28
|
+
memory: str | None = None
|
|
29
29
|
|
|
30
30
|
def __post_init__(self):
|
|
31
31
|
"""Validate GPU configuration."""
|
|
@@ -215,11 +215,11 @@ class ResourceRequirements:
|
|
|
215
215
|
... )
|
|
216
216
|
"""
|
|
217
217
|
|
|
218
|
-
cpu:
|
|
219
|
-
memory:
|
|
220
|
-
storage:
|
|
221
|
-
gpu:
|
|
222
|
-
node_affinity:
|
|
218
|
+
cpu: str | None = None
|
|
219
|
+
memory: str | None = None
|
|
220
|
+
storage: str | None = None
|
|
221
|
+
gpu: GPUConfig | None = None
|
|
222
|
+
node_affinity: NodeAffinity | None = None
|
|
223
223
|
|
|
224
224
|
def __post_init__(self):
|
|
225
225
|
"""Validate resource specifications."""
|
|
@@ -408,11 +408,11 @@ class ResourceRequirements:
|
|
|
408
408
|
|
|
409
409
|
|
|
410
410
|
def resources(
|
|
411
|
-
cpu:
|
|
412
|
-
memory:
|
|
413
|
-
storage:
|
|
414
|
-
gpu:
|
|
415
|
-
node_affinity:
|
|
411
|
+
cpu: str | None = None,
|
|
412
|
+
memory: str | None = None,
|
|
413
|
+
storage: str | None = None,
|
|
414
|
+
gpu: GPUConfig | None = None,
|
|
415
|
+
node_affinity: NodeAffinity | None = None,
|
|
416
416
|
) -> ResourceRequirements:
|
|
417
417
|
"""Create a ResourceRequirements object with validation.
|
|
418
418
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Retry policies for orchestrators."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
5
|
from flowyml.core.error_handling import RetryConfig, ExponentialBackoff, execute_with_retry
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
@@ -61,7 +61,7 @@ def with_retry(orchestrator_method):
|
|
|
61
61
|
...
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
|
-
def wrapper(self, pipeline: "Pipeline", *args, retry_policy:
|
|
64
|
+
def wrapper(self, pipeline: "Pipeline", *args, retry_policy: OrchestratorRetryPolicy | None = None, **kwargs):
|
|
65
65
|
if retry_policy is None:
|
|
66
66
|
# No retry policy, execute normally
|
|
67
67
|
return orchestrator_method(self, pipeline, *args, **kwargs)
|
|
@@ -10,7 +10,7 @@ from collections.abc import Callable
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from datetime import datetime, timedelta
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
13
|
+
from typing import Any
|
|
14
14
|
|
|
15
15
|
from flowyml.core.scheduler_config import SchedulerConfig
|
|
16
16
|
|
|
@@ -77,10 +77,10 @@ class ScheduleExecution:
|
|
|
77
77
|
|
|
78
78
|
schedule_name: str
|
|
79
79
|
started_at: datetime
|
|
80
|
-
completed_at:
|
|
80
|
+
completed_at: datetime | None = None
|
|
81
81
|
success: bool = False
|
|
82
|
-
error:
|
|
83
|
-
duration_seconds:
|
|
82
|
+
error: str | None = None
|
|
83
|
+
duration_seconds: float | None = None
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
class SchedulerMetrics:
|
|
@@ -121,7 +121,7 @@ class SchedulerMetrics:
|
|
|
121
121
|
class SchedulerPersistence:
|
|
122
122
|
"""Persist schedules to SQLite database."""
|
|
123
123
|
|
|
124
|
-
def __init__(self, db_path:
|
|
124
|
+
def __init__(self, db_path: str | None = None):
|
|
125
125
|
self.db_path = db_path or str(Path.cwd() / ".flowyml_scheduler.db")
|
|
126
126
|
self._init_db()
|
|
127
127
|
|
|
@@ -230,7 +230,7 @@ class SchedulerPersistence:
|
|
|
230
230
|
class DistributedLock:
|
|
231
231
|
"""Distributed lock for coordinating multiple scheduler instances."""
|
|
232
232
|
|
|
233
|
-
def __init__(self, backend: str = "file", redis_url:
|
|
233
|
+
def __init__(self, backend: str = "file", redis_url: str | None = None):
|
|
234
234
|
self.backend = backend
|
|
235
235
|
self.redis_url = redis_url
|
|
236
236
|
self._redis = None
|
|
@@ -286,9 +286,9 @@ class PipelineScheduler:
|
|
|
286
286
|
|
|
287
287
|
def __init__(
|
|
288
288
|
self,
|
|
289
|
-
config:
|
|
290
|
-
on_success:
|
|
291
|
-
on_failure:
|
|
289
|
+
config: SchedulerConfig | None = None,
|
|
290
|
+
on_success: Callable | None = None,
|
|
291
|
+
on_failure: Callable | None = None,
|
|
292
292
|
):
|
|
293
293
|
self.config = config or SchedulerConfig.from_env()
|
|
294
294
|
self.schedules: dict[str, Schedule] = {}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Scheduler configuration."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from typing import Optional
|
|
5
4
|
from pydantic import BaseModel
|
|
6
5
|
|
|
7
6
|
|
|
@@ -9,10 +8,10 @@ class SchedulerConfig(BaseModel):
|
|
|
9
8
|
"""Scheduler configuration."""
|
|
10
9
|
|
|
11
10
|
persist_schedules: bool = True
|
|
12
|
-
db_path:
|
|
11
|
+
db_path: str | None = None
|
|
13
12
|
distributed: bool = False
|
|
14
13
|
lock_backend: str = "file" # "file", "redis"
|
|
15
|
-
redis_url:
|
|
14
|
+
redis_url: str | None = None
|
|
16
15
|
check_interval_seconds: int = 10
|
|
17
16
|
max_concurrent_runs: int = 5
|
|
18
17
|
timezone: str = "UTC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Submission result for async pipeline execution."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
|
|
6
6
|
|
|
@@ -15,8 +15,8 @@ class SubmissionResult:
|
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
job_id: str,
|
|
18
|
-
wait_for_completion:
|
|
19
|
-
metadata:
|
|
18
|
+
wait_for_completion: Callable[[], None] | None = None,
|
|
19
|
+
metadata: dict[str, Any] | None = None,
|
|
20
20
|
):
|
|
21
21
|
"""Initialize a submission result.
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ class SubmissionResult:
|
|
|
29
29
|
self.wait_for_completion = wait_for_completion
|
|
30
30
|
self.metadata = metadata or {}
|
|
31
31
|
|
|
32
|
-
def wait(self, timeout:
|
|
32
|
+
def wait(self, timeout: int | None = None) -> None:
|
|
33
33
|
"""Wait for the pipeline run to complete.
|
|
34
34
|
|
|
35
35
|
Args:
|
|
@@ -6,7 +6,7 @@ frameworks (ZenML, Airflow, Prefect, etc.) using rule-based adaptation.
|
|
|
6
6
|
|
|
7
7
|
import inspect
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any
|
|
10
10
|
import logging
|
|
11
11
|
|
|
12
12
|
from flowyml.stacks.components import (
|
|
@@ -25,8 +25,8 @@ class AdaptationRule:
|
|
|
25
25
|
"""Rule for adapting an external component."""
|
|
26
26
|
|
|
27
27
|
# Matching criteria
|
|
28
|
-
source_type:
|
|
29
|
-
name_pattern:
|
|
28
|
+
source_type: str | None = None # e.g., "zenml.orchestrators.base.BaseOrchestrator"
|
|
29
|
+
name_pattern: str | None = None # e.g., ".*Orchestrator"
|
|
30
30
|
has_methods: list[str] = field(default_factory=list) # e.g., ["run", "prepare_pipeline"]
|
|
31
31
|
|
|
32
32
|
# Adaptation logic
|
|
@@ -49,7 +49,7 @@ class GenericBridge:
|
|
|
49
49
|
self,
|
|
50
50
|
external_class: Any,
|
|
51
51
|
name: str,
|
|
52
|
-
config:
|
|
52
|
+
config: dict[str, Any] | None = None,
|
|
53
53
|
) -> type[StackComponent]:
|
|
54
54
|
"""Dynamically create a wrapper class based on rules.
|
|
55
55
|
|
|
@@ -75,7 +75,7 @@ class GenericBridge:
|
|
|
75
75
|
else:
|
|
76
76
|
return self._create_generic_wrapper(external_class, name, component_type, rule)
|
|
77
77
|
|
|
78
|
-
def _find_matching_rule(self, external_class: Any) ->
|
|
78
|
+
def _find_matching_rule(self, external_class: Any) -> AdaptationRule | None:
|
|
79
79
|
"""Find the first rule that matches the external class."""
|
|
80
80
|
for rule in self.rules:
|
|
81
81
|
# Check source type
|
|
@@ -120,7 +120,7 @@ class GenericBridge:
|
|
|
120
120
|
self,
|
|
121
121
|
external_class: Any,
|
|
122
122
|
name: str,
|
|
123
|
-
rule:
|
|
123
|
+
rule: AdaptationRule | None,
|
|
124
124
|
) -> type[Orchestrator]:
|
|
125
125
|
"""Create a wrapper for an Orchestrator."""
|
|
126
126
|
|
|
@@ -174,7 +174,7 @@ class GenericBridge:
|
|
|
174
174
|
self,
|
|
175
175
|
external_class: Any,
|
|
176
176
|
name: str,
|
|
177
|
-
rule:
|
|
177
|
+
rule: AdaptationRule | None,
|
|
178
178
|
) -> type[ArtifactStore]:
|
|
179
179
|
"""Create a wrapper for an Artifact Store."""
|
|
180
180
|
|
|
@@ -216,7 +216,7 @@ class GenericBridge:
|
|
|
216
216
|
self,
|
|
217
217
|
external_class: Any,
|
|
218
218
|
name: str,
|
|
219
|
-
rule:
|
|
219
|
+
rule: AdaptationRule | None,
|
|
220
220
|
) -> type[ContainerRegistry]:
|
|
221
221
|
"""Create a wrapper for a Container Registry."""
|
|
222
222
|
|
|
@@ -259,7 +259,7 @@ class GenericBridge:
|
|
|
259
259
|
external_class: Any,
|
|
260
260
|
name: str,
|
|
261
261
|
comp_type: ComponentType,
|
|
262
|
-
rule:
|
|
262
|
+
rule: AdaptationRule | None,
|
|
263
263
|
) -> type[StackComponent]:
|
|
264
264
|
"""Create a generic wrapper."""
|
|
265
265
|
|
|
@@ -14,7 +14,7 @@ import inspect
|
|
|
14
14
|
import subprocess
|
|
15
15
|
import sys
|
|
16
16
|
from dataclasses import dataclass, field
|
|
17
|
-
from typing import Any,
|
|
17
|
+
from typing import Any, Protocol, runtime_checkable
|
|
18
18
|
|
|
19
19
|
from flowyml.stacks.components import (
|
|
20
20
|
StackComponent,
|
|
@@ -48,7 +48,7 @@ class PluginBridge(Protocol):
|
|
|
48
48
|
self,
|
|
49
49
|
component_class: Any,
|
|
50
50
|
name: str,
|
|
51
|
-
config:
|
|
51
|
+
config: dict[str, Any] | None = None,
|
|
52
52
|
) -> type[StackComponent]:
|
|
53
53
|
"""Wrap an external component class into a flowyml component."""
|
|
54
54
|
...
|
|
@@ -214,6 +214,27 @@ def get_active_stack() -> Stack | None:
|
|
|
214
214
|
Returns:
|
|
215
215
|
Active stack or None
|
|
216
216
|
"""
|
|
217
|
+
# Check for remote execution mode in global config
|
|
218
|
+
from flowyml.utils.config import get_config
|
|
219
|
+
|
|
220
|
+
config = get_config()
|
|
221
|
+
|
|
222
|
+
if config.execution_mode == "remote" and config.remote_server_url:
|
|
223
|
+
# Dynamically create a remote logging stack
|
|
224
|
+
from flowyml.core.executor import LocalExecutor
|
|
225
|
+
from flowyml.storage.remote import RemoteMetadataStore, RemoteArtifactStore
|
|
226
|
+
from flowyml.stacks.base import Stack
|
|
227
|
+
import os
|
|
228
|
+
|
|
229
|
+
api_token = os.getenv("FLOWYML_API_TOKEN")
|
|
230
|
+
|
|
231
|
+
return Stack(
|
|
232
|
+
name="remote_logging",
|
|
233
|
+
executor=LocalExecutor(),
|
|
234
|
+
metadata_store=RemoteMetadataStore(api_url=config.remote_server_url, api_token=api_token),
|
|
235
|
+
artifact_store=RemoteArtifactStore(api_url=config.remote_server_url, api_token=api_token),
|
|
236
|
+
)
|
|
237
|
+
|
|
217
238
|
return get_registry().get_active_stack()
|
|
218
239
|
|
|
219
240
|
|
|
@@ -99,6 +99,27 @@ class MaterializerRegistry:
|
|
|
99
99
|
|
|
100
100
|
return None
|
|
101
101
|
|
|
102
|
+
def get_materializer_by_type_name(self, type_name: str) -> BaseMaterializer | None:
|
|
103
|
+
"""Get materializer by type name string.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
type_name: Full type name (e.g. 'pandas.core.frame.DataFrame')
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Materializer instance or None
|
|
110
|
+
"""
|
|
111
|
+
# Check if we have a direct mapping
|
|
112
|
+
if type_name in self._materializers:
|
|
113
|
+
return self._materializers[type_name]()
|
|
114
|
+
|
|
115
|
+
# Try to find by just class name if full path fails (less reliable but useful fallback)
|
|
116
|
+
simple_name = type_name.split(".")[-1]
|
|
117
|
+
for key, materializer_cls in self._materializers.items():
|
|
118
|
+
if key.split(".")[-1] == simple_name:
|
|
119
|
+
return materializer_cls()
|
|
120
|
+
|
|
121
|
+
return None
|
|
122
|
+
|
|
102
123
|
def list_materializers(self) -> dict[str, type[BaseMaterializer]]:
|
|
103
124
|
"""List all registered materializers.
|
|
104
125
|
|
|
@@ -131,3 +152,15 @@ def get_materializer(obj: Any) -> BaseMaterializer | None:
|
|
|
131
152
|
Materializer instance or None
|
|
132
153
|
"""
|
|
133
154
|
return materializer_registry.get_materializer(obj)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_materializer_by_type_name(type_name: str) -> BaseMaterializer | None:
|
|
158
|
+
"""Get materializer by type name string from global registry.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
type_name: Full type name (e.g. 'pandas.core.frame.DataFrame')
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Materializer instance or None
|
|
165
|
+
"""
|
|
166
|
+
return materializer_registry.get_materializer_by_type_name(type_name)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Metadata storage backends for flowyml."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MetadataStore(ABC):
|
|
7
|
+
"""Base class for metadata storage backends."""
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def save_run(self, run_id: str, metadata: dict) -> None:
|
|
11
|
+
"""Save run metadata."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def load_run(self, run_id: str) -> dict | None:
|
|
16
|
+
"""Load run metadata."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def list_runs(self, limit: int | None = None) -> list[dict]:
|
|
21
|
+
"""List all runs."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def list_pipelines(self) -> list[str]:
|
|
26
|
+
"""List all unique pipeline names."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def save_artifact(self, artifact_id: str, metadata: dict) -> None:
|
|
31
|
+
"""Save artifact metadata."""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def load_artifact(self, artifact_id: str) -> dict | None:
|
|
36
|
+
"""Load artifact metadata."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def list_assets(self, limit: int | None = None, **filters) -> list[dict]:
|
|
41
|
+
"""List assets with optional filters."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def query(self, **filters) -> list[dict]:
|
|
46
|
+
"""Query runs with filters."""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
from flowyml.storage.sql import SQLMetadataStore # noqa: E402
|
|
51
|
+
|
|
52
|
+
# Alias for backward compatibility
|
|
53
|
+
SQLiteMetadataStore = SQLMetadataStore
|