flowyml 1.7.2__py3-none-any.whl → 1.8.0__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.
- flowyml/assets/base.py +15 -0
- flowyml/assets/metrics.py +5 -0
- flowyml/cli/main.py +709 -0
- flowyml/cli/stack_cli.py +138 -25
- flowyml/core/__init__.py +17 -0
- flowyml/core/executor.py +161 -26
- flowyml/core/image_builder.py +129 -0
- flowyml/core/log_streamer.py +227 -0
- flowyml/core/orchestrator.py +22 -2
- flowyml/core/pipeline.py +34 -10
- flowyml/core/routing.py +558 -0
- flowyml/core/step.py +9 -1
- flowyml/core/step_grouping.py +49 -35
- flowyml/core/types.py +407 -0
- flowyml/monitoring/alerts.py +10 -0
- flowyml/monitoring/notifications.py +104 -25
- flowyml/monitoring/slack_blocks.py +323 -0
- flowyml/plugins/__init__.py +251 -0
- flowyml/plugins/alerters/__init__.py +1 -0
- flowyml/plugins/alerters/slack.py +168 -0
- flowyml/plugins/base.py +752 -0
- flowyml/plugins/config.py +478 -0
- flowyml/plugins/deployers/__init__.py +22 -0
- flowyml/plugins/deployers/gcp_cloud_run.py +200 -0
- flowyml/plugins/deployers/sagemaker.py +306 -0
- flowyml/plugins/deployers/vertex.py +290 -0
- flowyml/plugins/integration.py +369 -0
- flowyml/plugins/manager.py +510 -0
- flowyml/plugins/model_registries/__init__.py +22 -0
- flowyml/plugins/model_registries/mlflow.py +159 -0
- flowyml/plugins/model_registries/sagemaker.py +489 -0
- flowyml/plugins/model_registries/vertex.py +386 -0
- flowyml/plugins/orchestrators/__init__.py +13 -0
- flowyml/plugins/orchestrators/sagemaker.py +443 -0
- flowyml/plugins/orchestrators/vertex_ai.py +461 -0
- flowyml/plugins/registries/__init__.py +13 -0
- flowyml/plugins/registries/ecr.py +321 -0
- flowyml/plugins/registries/gcr.py +313 -0
- flowyml/plugins/registry.py +454 -0
- flowyml/plugins/stack.py +494 -0
- flowyml/plugins/stack_config.py +537 -0
- flowyml/plugins/stores/__init__.py +13 -0
- flowyml/plugins/stores/gcs.py +460 -0
- flowyml/plugins/stores/s3.py +453 -0
- flowyml/plugins/trackers/__init__.py +11 -0
- flowyml/plugins/trackers/mlflow.py +316 -0
- flowyml/plugins/validators/__init__.py +3 -0
- flowyml/plugins/validators/deepchecks.py +119 -0
- flowyml/registry/__init__.py +2 -1
- flowyml/registry/model_environment.py +109 -0
- flowyml/registry/model_registry.py +241 -96
- flowyml/serving/__init__.py +17 -0
- flowyml/serving/model_server.py +628 -0
- flowyml/stacks/__init__.py +60 -0
- flowyml/stacks/aws.py +93 -0
- flowyml/stacks/base.py +62 -0
- flowyml/stacks/components.py +12 -0
- flowyml/stacks/gcp.py +44 -9
- flowyml/stacks/plugins.py +115 -0
- flowyml/stacks/registry.py +2 -1
- flowyml/storage/sql.py +401 -12
- flowyml/tracking/experiment.py +8 -5
- flowyml/ui/backend/Dockerfile +87 -16
- flowyml/ui/backend/auth.py +12 -2
- flowyml/ui/backend/main.py +149 -5
- flowyml/ui/backend/routers/ai_context.py +226 -0
- flowyml/ui/backend/routers/assets.py +23 -4
- flowyml/ui/backend/routers/auth.py +96 -0
- flowyml/ui/backend/routers/deployments.py +660 -0
- flowyml/ui/backend/routers/model_explorer.py +597 -0
- flowyml/ui/backend/routers/plugins.py +103 -51
- flowyml/ui/backend/routers/projects.py +91 -8
- flowyml/ui/backend/routers/runs.py +20 -1
- flowyml/ui/backend/routers/schedules.py +22 -17
- flowyml/ui/backend/routers/templates.py +319 -0
- flowyml/ui/backend/routers/websocket.py +2 -2
- flowyml/ui/frontend/Dockerfile +55 -6
- flowyml/ui/frontend/dist/assets/index-B5AsPTSz.css +1 -0
- flowyml/ui/frontend/dist/assets/index-dFbZ8wD8.js +753 -0
- flowyml/ui/frontend/dist/index.html +2 -2
- flowyml/ui/frontend/dist/logo.png +0 -0
- flowyml/ui/frontend/nginx.conf +65 -4
- flowyml/ui/frontend/package-lock.json +1404 -74
- flowyml/ui/frontend/package.json +3 -0
- flowyml/ui/frontend/public/logo.png +0 -0
- flowyml/ui/frontend/src/App.jsx +10 -7
- flowyml/ui/frontend/src/app/auth/Login.jsx +90 -0
- flowyml/ui/frontend/src/app/dashboard/page.jsx +8 -8
- flowyml/ui/frontend/src/app/deployments/page.jsx +786 -0
- flowyml/ui/frontend/src/app/model-explorer/page.jsx +1031 -0
- flowyml/ui/frontend/src/app/pipelines/page.jsx +12 -2
- flowyml/ui/frontend/src/app/projects/[projectId]/_components/ProjectExperimentsList.jsx +19 -6
- flowyml/ui/frontend/src/app/runs/[runId]/page.jsx +36 -24
- flowyml/ui/frontend/src/app/runs/page.jsx +8 -2
- flowyml/ui/frontend/src/app/settings/page.jsx +267 -253
- flowyml/ui/frontend/src/components/AssetDetailsPanel.jsx +29 -7
- flowyml/ui/frontend/src/components/Layout.jsx +6 -0
- flowyml/ui/frontend/src/components/PipelineGraph.jsx +79 -29
- flowyml/ui/frontend/src/components/RunDetailsPanel.jsx +36 -6
- flowyml/ui/frontend/src/components/RunMetaPanel.jsx +113 -0
- flowyml/ui/frontend/src/components/ai/AIAssistantButton.jsx +71 -0
- flowyml/ui/frontend/src/components/ai/AIAssistantPanel.jsx +420 -0
- flowyml/ui/frontend/src/components/header/Header.jsx +22 -0
- flowyml/ui/frontend/src/components/plugins/PluginManager.jsx +4 -4
- flowyml/ui/frontend/src/components/plugins/{ZenMLIntegration.jsx → StackImport.jsx} +38 -12
- flowyml/ui/frontend/src/components/sidebar/Sidebar.jsx +36 -13
- flowyml/ui/frontend/src/contexts/AIAssistantContext.jsx +245 -0
- flowyml/ui/frontend/src/contexts/AuthContext.jsx +108 -0
- flowyml/ui/frontend/src/hooks/useAIContext.js +156 -0
- flowyml/ui/frontend/src/hooks/useWebGPU.js +54 -0
- flowyml/ui/frontend/src/layouts/MainLayout.jsx +6 -0
- flowyml/ui/frontend/src/router/index.jsx +47 -20
- flowyml/ui/frontend/src/services/pluginService.js +3 -1
- flowyml/ui/server_manager.py +5 -5
- flowyml/ui/utils.py +157 -39
- flowyml/utils/config.py +37 -15
- flowyml/utils/model_introspection.py +123 -0
- flowyml/utils/observability.py +30 -0
- flowyml-1.8.0.dist-info/METADATA +174 -0
- {flowyml-1.7.2.dist-info → flowyml-1.8.0.dist-info}/RECORD +123 -65
- {flowyml-1.7.2.dist-info → flowyml-1.8.0.dist-info}/WHEEL +1 -1
- flowyml/ui/frontend/dist/assets/index-B40RsQDq.css +0 -1
- flowyml/ui/frontend/dist/assets/index-CjI0zKCn.js +0 -685
- flowyml-1.7.2.dist-info/METADATA +0 -477
- {flowyml-1.7.2.dist-info → flowyml-1.8.0.dist-info}/entry_points.txt +0 -0
- {flowyml-1.7.2.dist-info → flowyml-1.8.0.dist-info}/licenses/LICENSE +0 -0
flowyml/cli/main.py
CHANGED
|
@@ -376,6 +376,111 @@ def models() -> None:
|
|
|
376
376
|
pass
|
|
377
377
|
|
|
378
378
|
|
|
379
|
+
@cli.group()
|
|
380
|
+
def db() -> None:
|
|
381
|
+
"""Database management commands."""
|
|
382
|
+
pass
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@db.command("migrate")
|
|
386
|
+
@click.option("--revision", default="head", help="Target revision (default: head)")
|
|
387
|
+
@click.option("--sql", is_flag=True, help="Generate SQL instead of running migration")
|
|
388
|
+
def migrate(revision: str, sql: bool) -> None:
|
|
389
|
+
"""Run database migrations.
|
|
390
|
+
|
|
391
|
+
This applies Alembic migrations to your database schema.
|
|
392
|
+
|
|
393
|
+
Examples:
|
|
394
|
+
flowyml db migrate # Upgrade to latest
|
|
395
|
+
flowyml db migrate --revision 001_initial
|
|
396
|
+
flowyml db migrate --sql # Print SQL without executing
|
|
397
|
+
"""
|
|
398
|
+
import os
|
|
399
|
+
|
|
400
|
+
db_url = os.getenv("FLOWYML_DATABASE_URL", "sqlite:///flowyml.db")
|
|
401
|
+
click.echo("🔄 Running database migrations...")
|
|
402
|
+
click.echo(f" Database: {db_url[:50]}...")
|
|
403
|
+
|
|
404
|
+
try:
|
|
405
|
+
from alembic.config import Config
|
|
406
|
+
from alembic import command
|
|
407
|
+
|
|
408
|
+
# Get alembic.ini path (project root)
|
|
409
|
+
alembic_cfg = Config("alembic.ini")
|
|
410
|
+
alembic_cfg.set_main_option("sqlalchemy.url", db_url)
|
|
411
|
+
|
|
412
|
+
if sql:
|
|
413
|
+
command.upgrade(alembic_cfg, revision, sql=True)
|
|
414
|
+
else:
|
|
415
|
+
command.upgrade(alembic_cfg, revision)
|
|
416
|
+
click.echo(f"✅ Migrations applied successfully to revision: {revision}")
|
|
417
|
+
except Exception as e:
|
|
418
|
+
click.echo(f"❌ Migration failed: {e}", err=True)
|
|
419
|
+
raise click.Abort()
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@db.command("downgrade")
|
|
423
|
+
@click.argument("revision", default="-1")
|
|
424
|
+
def downgrade(revision: str) -> None:
|
|
425
|
+
"""Downgrade database schema.
|
|
426
|
+
|
|
427
|
+
Examples:
|
|
428
|
+
flowyml db downgrade -1 # Downgrade one revision
|
|
429
|
+
flowyml db downgrade base # Downgrade to empty database
|
|
430
|
+
"""
|
|
431
|
+
import os
|
|
432
|
+
|
|
433
|
+
db_url = os.getenv("FLOWYML_DATABASE_URL", "sqlite:///flowyml.db")
|
|
434
|
+
click.echo("🔄 Downgrading database...")
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
from alembic.config import Config
|
|
438
|
+
from alembic import command
|
|
439
|
+
|
|
440
|
+
alembic_cfg = Config("alembic.ini")
|
|
441
|
+
alembic_cfg.set_main_option("sqlalchemy.url", db_url)
|
|
442
|
+
|
|
443
|
+
command.downgrade(alembic_cfg, revision)
|
|
444
|
+
click.echo(f"✅ Downgraded to revision: {revision}")
|
|
445
|
+
except Exception as e:
|
|
446
|
+
click.echo(f"❌ Downgrade failed: {e}", err=True)
|
|
447
|
+
raise click.Abort()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@db.command("current")
|
|
451
|
+
def current() -> None:
|
|
452
|
+
"""Show current database revision."""
|
|
453
|
+
import os
|
|
454
|
+
|
|
455
|
+
db_url = os.getenv("FLOWYML_DATABASE_URL", "sqlite:///flowyml.db")
|
|
456
|
+
|
|
457
|
+
try:
|
|
458
|
+
from alembic.config import Config
|
|
459
|
+
from alembic import command
|
|
460
|
+
|
|
461
|
+
alembic_cfg = Config("alembic.ini")
|
|
462
|
+
alembic_cfg.set_main_option("sqlalchemy.url", db_url)
|
|
463
|
+
|
|
464
|
+
click.echo("Current database revision:")
|
|
465
|
+
command.current(alembic_cfg, verbose=True)
|
|
466
|
+
except Exception as e:
|
|
467
|
+
click.echo(f"❌ Failed to get current revision: {e}", err=True)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
@db.command("history")
|
|
471
|
+
def history() -> None:
|
|
472
|
+
"""Show migration history."""
|
|
473
|
+
try:
|
|
474
|
+
from alembic.config import Config
|
|
475
|
+
from alembic import command
|
|
476
|
+
|
|
477
|
+
alembic_cfg = Config("alembic.ini")
|
|
478
|
+
click.echo("Migration history:")
|
|
479
|
+
command.history(alembic_cfg, verbose=True)
|
|
480
|
+
except Exception as e:
|
|
481
|
+
click.echo(f"❌ Failed to get history: {e}", err=True)
|
|
482
|
+
|
|
483
|
+
|
|
379
484
|
# Register model commands
|
|
380
485
|
models.add_command(list_models)
|
|
381
486
|
models.add_command(promote_model)
|
|
@@ -433,6 +538,18 @@ def set_url(server: str, ui: str) -> None:
|
|
|
433
538
|
click.echo(f"✓ Remote UI URL set to '{ui}'")
|
|
434
539
|
cfg.save()
|
|
435
540
|
|
|
541
|
+
cfg.save()
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@config.command("set-token")
|
|
545
|
+
@click.argument("token")
|
|
546
|
+
def set_token(token: str) -> None:
|
|
547
|
+
"""Set the API token for remote authentication."""
|
|
548
|
+
cfg = get_config()
|
|
549
|
+
cfg.api_token = token
|
|
550
|
+
cfg.save()
|
|
551
|
+
click.echo(f"✓ API token set (length: {len(token)})")
|
|
552
|
+
|
|
436
553
|
|
|
437
554
|
@cli.command()
|
|
438
555
|
@click.argument("run_id")
|
|
@@ -826,5 +943,597 @@ def server_status(host: str, port: int) -> None:
|
|
|
826
943
|
click.echo("Start with: flowyml go")
|
|
827
944
|
|
|
828
945
|
|
|
946
|
+
# ============================================================================
|
|
947
|
+
# ZenML Integration Commands
|
|
948
|
+
# ============================================================================
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
@cli.group()
|
|
952
|
+
def zenml() -> None:
|
|
953
|
+
"""ZenML integration commands - Seamlessly use ZenML components in FlowyML.
|
|
954
|
+
|
|
955
|
+
FlowyML can automatically discover and wrap ZenML integrations,
|
|
956
|
+
making them available as first-class FlowyML stack components.
|
|
957
|
+
"""
|
|
958
|
+
pass
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
@zenml.command("list")
|
|
962
|
+
@click.option("--installed", is_flag=True, help="Show only installed integrations")
|
|
963
|
+
def list_zenml_integrations(installed: bool) -> None:
|
|
964
|
+
"""List available ZenML integrations.
|
|
965
|
+
|
|
966
|
+
Shows all ZenML integrations that can be used with FlowyML.
|
|
967
|
+
Use --installed to see only the integrations you have installed.
|
|
968
|
+
"""
|
|
969
|
+
from flowyml.stacks.plugins import get_component_registry
|
|
970
|
+
|
|
971
|
+
registry = get_component_registry()
|
|
972
|
+
|
|
973
|
+
if installed:
|
|
974
|
+
integrations = registry.list_installed_zenml_integrations()
|
|
975
|
+
click.echo("Installed ZenML integrations:\n")
|
|
976
|
+
else:
|
|
977
|
+
integrations = registry.list_zenml_integrations()
|
|
978
|
+
click.echo("Available ZenML integrations:\n")
|
|
979
|
+
|
|
980
|
+
if not integrations:
|
|
981
|
+
click.echo(" No integrations found.")
|
|
982
|
+
click.echo("\n Make sure ZenML is installed: pip install zenml")
|
|
983
|
+
return
|
|
984
|
+
|
|
985
|
+
for name in sorted(integrations):
|
|
986
|
+
click.echo(f" • {name}")
|
|
987
|
+
|
|
988
|
+
click.echo(f"\nTotal: {len(integrations)} integrations")
|
|
989
|
+
|
|
990
|
+
if not installed:
|
|
991
|
+
click.echo("\nTo install an integration:")
|
|
992
|
+
click.echo(" flowyml zenml install <integration_name>")
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
@zenml.command("install")
|
|
996
|
+
@click.argument("integration_name")
|
|
997
|
+
def install_zenml_integration(integration_name: str) -> None:
|
|
998
|
+
"""Install a ZenML integration and its dependencies.
|
|
999
|
+
|
|
1000
|
+
This installs the ZenML integration package and all required
|
|
1001
|
+
dependencies, making it available for use in FlowyML pipelines.
|
|
1002
|
+
|
|
1003
|
+
Examples:
|
|
1004
|
+
flowyml zenml install mlflow
|
|
1005
|
+
flowyml zenml install kubernetes
|
|
1006
|
+
flowyml zenml install aws
|
|
1007
|
+
"""
|
|
1008
|
+
from flowyml.stacks.plugins import get_component_registry
|
|
1009
|
+
|
|
1010
|
+
click.echo(f"Installing ZenML integration '{integration_name}'...")
|
|
1011
|
+
|
|
1012
|
+
registry = get_component_registry()
|
|
1013
|
+
success = registry.install_zenml_integration(integration_name)
|
|
1014
|
+
|
|
1015
|
+
if success:
|
|
1016
|
+
click.echo(f"✓ Successfully installed '{integration_name}'")
|
|
1017
|
+
click.echo("\nTo use this integration in FlowyML:")
|
|
1018
|
+
click.echo(f" flowyml zenml import {integration_name}")
|
|
1019
|
+
else:
|
|
1020
|
+
click.echo(f"✗ Failed to install '{integration_name}'", err=True)
|
|
1021
|
+
click.echo(" Check that ZenML is installed and the integration name is correct.")
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
@zenml.command("import")
|
|
1025
|
+
@click.argument("integration_name")
|
|
1026
|
+
def import_zenml_integration(integration_name: str) -> None:
|
|
1027
|
+
"""Import components from a ZenML integration.
|
|
1028
|
+
|
|
1029
|
+
Discovers all flavors provided by a ZenML integration and registers
|
|
1030
|
+
them as FlowyML stack components, ready to use in your pipelines.
|
|
1031
|
+
|
|
1032
|
+
Examples:
|
|
1033
|
+
flowyml zenml import mlflow
|
|
1034
|
+
flowyml zenml import kubernetes
|
|
1035
|
+
"""
|
|
1036
|
+
from flowyml.stacks.plugins import get_component_registry
|
|
1037
|
+
|
|
1038
|
+
click.echo(f"Importing ZenML integration '{integration_name}'...")
|
|
1039
|
+
|
|
1040
|
+
registry = get_component_registry()
|
|
1041
|
+
components = registry.import_zenml_integration(integration_name)
|
|
1042
|
+
|
|
1043
|
+
if components:
|
|
1044
|
+
click.echo(f"✓ Successfully imported {len(components)} components:\n")
|
|
1045
|
+
for comp in components:
|
|
1046
|
+
click.echo(f" • {comp.__name__}")
|
|
1047
|
+
click.echo("\nThese components are now available in your FlowyML stacks.")
|
|
1048
|
+
else:
|
|
1049
|
+
click.echo(f"✗ No components imported from '{integration_name}'", err=True)
|
|
1050
|
+
click.echo(" Make sure the integration is installed:")
|
|
1051
|
+
click.echo(f" flowyml zenml install {integration_name}")
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
@zenml.command("import-all")
|
|
1055
|
+
def import_all_zenml_integrations() -> None:
|
|
1056
|
+
"""Import all components from all installed ZenML integrations.
|
|
1057
|
+
|
|
1058
|
+
This is the easiest way to make all ZenML components available
|
|
1059
|
+
in FlowyML with a single command.
|
|
1060
|
+
|
|
1061
|
+
Example:
|
|
1062
|
+
flowyml zenml import-all
|
|
1063
|
+
"""
|
|
1064
|
+
from flowyml.stacks.plugins import get_component_registry
|
|
1065
|
+
|
|
1066
|
+
click.echo("Importing all installed ZenML integrations...")
|
|
1067
|
+
|
|
1068
|
+
registry = get_component_registry()
|
|
1069
|
+
result = registry.import_all_zenml()
|
|
1070
|
+
|
|
1071
|
+
if result:
|
|
1072
|
+
total = sum(len(comps) for comps in result.values())
|
|
1073
|
+
click.echo(f"✓ Successfully imported {total} components from {len(result)} integrations:\n")
|
|
1074
|
+
|
|
1075
|
+
for integration_name, components in result.items():
|
|
1076
|
+
click.echo(f" {integration_name}:")
|
|
1077
|
+
for comp in components:
|
|
1078
|
+
click.echo(f" • {comp.__name__}")
|
|
1079
|
+
|
|
1080
|
+
click.echo("\nAll components are now available in your FlowyML stacks.")
|
|
1081
|
+
else:
|
|
1082
|
+
click.echo("✗ No components imported", err=True)
|
|
1083
|
+
click.echo(" Make sure ZenML is installed and you have some integrations installed:")
|
|
1084
|
+
click.echo(" pip install zenml")
|
|
1085
|
+
click.echo(" flowyml zenml install mlflow")
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
@zenml.command("status")
|
|
1089
|
+
def zenml_status() -> None:
|
|
1090
|
+
"""Check ZenML availability and show integration summary.
|
|
1091
|
+
|
|
1092
|
+
Shows whether ZenML is installed and a summary of available
|
|
1093
|
+
and installed integrations.
|
|
1094
|
+
"""
|
|
1095
|
+
try:
|
|
1096
|
+
import zenml
|
|
1097
|
+
|
|
1098
|
+
zenml_version = zenml.__version__
|
|
1099
|
+
zenml_available = True
|
|
1100
|
+
except ImportError:
|
|
1101
|
+
zenml_available = False
|
|
1102
|
+
zenml_version = None
|
|
1103
|
+
|
|
1104
|
+
if zenml_available:
|
|
1105
|
+
click.echo(f"✓ ZenML is installed (version {zenml_version})\n")
|
|
1106
|
+
|
|
1107
|
+
from flowyml.stacks.plugins import get_component_registry
|
|
1108
|
+
|
|
1109
|
+
registry = get_component_registry()
|
|
1110
|
+
|
|
1111
|
+
available = registry.list_zenml_integrations()
|
|
1112
|
+
installed = registry.list_installed_zenml_integrations()
|
|
1113
|
+
|
|
1114
|
+
click.echo(f" Available integrations: {len(available)}")
|
|
1115
|
+
click.echo(f" Installed integrations: {len(installed)}")
|
|
1116
|
+
|
|
1117
|
+
if installed:
|
|
1118
|
+
click.echo(f"\n Installed: {', '.join(installed[:5])}")
|
|
1119
|
+
if len(installed) > 5:
|
|
1120
|
+
click.echo(f" ...and {len(installed) - 5} more")
|
|
1121
|
+
|
|
1122
|
+
click.echo("\n Quick start:")
|
|
1123
|
+
click.echo(" flowyml zenml import-all # Import all installed integrations")
|
|
1124
|
+
else:
|
|
1125
|
+
click.echo("✗ ZenML is not installed\n")
|
|
1126
|
+
click.echo(" To install ZenML:")
|
|
1127
|
+
click.echo(" pip install zenml")
|
|
1128
|
+
click.echo("\n After installing, you can:")
|
|
1129
|
+
click.echo(" flowyml zenml list # List available integrations")
|
|
1130
|
+
click.echo(" flowyml zenml install aws # Install an integration")
|
|
1131
|
+
click.echo(" flowyml zenml import-all # Import all components")
|
|
1132
|
+
|
|
1133
|
+
|
|
1134
|
+
# =============================================================================
|
|
1135
|
+
# NATIVE PLUGIN COMMANDS
|
|
1136
|
+
# =============================================================================
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
@cli.group()
|
|
1140
|
+
def plugin() -> None:
|
|
1141
|
+
"""Native plugin management commands.
|
|
1142
|
+
|
|
1143
|
+
Manage FlowyML plugins without external framework dependencies.
|
|
1144
|
+
Install plugins directly (e.g., 'flowyml plugin install mlflow')
|
|
1145
|
+
and FlowyML will install only the underlying packages you need.
|
|
1146
|
+
"""
|
|
1147
|
+
pass
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
@plugin.command("list")
|
|
1151
|
+
@click.option("--installed", is_flag=True, help="Show only installed plugins")
|
|
1152
|
+
@click.option(
|
|
1153
|
+
"--type",
|
|
1154
|
+
"plugin_type",
|
|
1155
|
+
type=click.Choice(
|
|
1156
|
+
[
|
|
1157
|
+
"experiment_tracker",
|
|
1158
|
+
"artifact_store",
|
|
1159
|
+
"orchestrator",
|
|
1160
|
+
"container_registry",
|
|
1161
|
+
"feature_store",
|
|
1162
|
+
"data_validator",
|
|
1163
|
+
"alerter",
|
|
1164
|
+
],
|
|
1165
|
+
),
|
|
1166
|
+
help="Filter by plugin type",
|
|
1167
|
+
)
|
|
1168
|
+
def plugin_list(installed: bool, plugin_type: str) -> None:
|
|
1169
|
+
"""List available plugins.
|
|
1170
|
+
|
|
1171
|
+
Shows all plugins in the FlowyML catalog. Use --installed to see
|
|
1172
|
+
only plugins whose packages are already installed.
|
|
1173
|
+
|
|
1174
|
+
Examples:
|
|
1175
|
+
flowyml plugin list
|
|
1176
|
+
flowyml plugin list --installed
|
|
1177
|
+
flowyml plugin list --type experiment_tracker
|
|
1178
|
+
"""
|
|
1179
|
+
from flowyml.plugins import get_manager, PluginType
|
|
1180
|
+
|
|
1181
|
+
manager = get_manager()
|
|
1182
|
+
|
|
1183
|
+
# Convert string to PluginType if provided
|
|
1184
|
+
ptype = PluginType(plugin_type) if plugin_type else None
|
|
1185
|
+
|
|
1186
|
+
if installed:
|
|
1187
|
+
plugins = manager.list_installed(ptype)
|
|
1188
|
+
title = "Installed Plugins"
|
|
1189
|
+
else:
|
|
1190
|
+
plugins = manager.list_available(ptype)
|
|
1191
|
+
title = "Available Plugins"
|
|
1192
|
+
|
|
1193
|
+
if not plugins:
|
|
1194
|
+
click.echo("No plugins found.")
|
|
1195
|
+
return
|
|
1196
|
+
|
|
1197
|
+
click.echo(f"\n📦 {title}:\n")
|
|
1198
|
+
|
|
1199
|
+
# Group by type for better display
|
|
1200
|
+
from flowyml.plugins import get_plugin_info
|
|
1201
|
+
|
|
1202
|
+
grouped = {}
|
|
1203
|
+
for name in plugins:
|
|
1204
|
+
info = get_plugin_info(name)
|
|
1205
|
+
if info:
|
|
1206
|
+
type_name = info.plugin_type.value.replace("_", " ").title()
|
|
1207
|
+
if type_name not in grouped:
|
|
1208
|
+
grouped[type_name] = []
|
|
1209
|
+
is_installed = manager.is_installed(name)
|
|
1210
|
+
status = "✓" if is_installed else " "
|
|
1211
|
+
grouped[type_name].append((name, info.description, status))
|
|
1212
|
+
|
|
1213
|
+
for type_name, items in sorted(grouped.items()):
|
|
1214
|
+
click.echo(f" {type_name}:")
|
|
1215
|
+
for name, desc, status in sorted(items):
|
|
1216
|
+
click.echo(f" {status} {name:<20} - {desc[:50]}")
|
|
1217
|
+
click.echo()
|
|
1218
|
+
|
|
1219
|
+
click.echo("Install a plugin with: flowyml plugin install <name>")
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
@plugin.command("install")
|
|
1223
|
+
@click.argument("name")
|
|
1224
|
+
@click.option("--upgrade", is_flag=True, help="Upgrade to latest version")
|
|
1225
|
+
def plugin_install(name: str, upgrade: bool) -> None:
|
|
1226
|
+
"""Install a plugin.
|
|
1227
|
+
|
|
1228
|
+
Installs the underlying packages for a plugin directly.
|
|
1229
|
+
For example, 'flowyml plugin install mlflow' installs the mlflow package.
|
|
1230
|
+
|
|
1231
|
+
Examples:
|
|
1232
|
+
flowyml plugin install mlflow
|
|
1233
|
+
flowyml plugin install kubernetes
|
|
1234
|
+
flowyml plugin install s3 --upgrade
|
|
1235
|
+
"""
|
|
1236
|
+
from flowyml.plugins import get_manager, get_plugin_info
|
|
1237
|
+
|
|
1238
|
+
manager = get_manager()
|
|
1239
|
+
info = get_plugin_info(name)
|
|
1240
|
+
|
|
1241
|
+
if not info:
|
|
1242
|
+
click.echo(f"✗ Plugin '{name}' not found", err=True)
|
|
1243
|
+
click.echo("\nAvailable plugins:")
|
|
1244
|
+
for p in manager.list_available()[:10]:
|
|
1245
|
+
click.echo(f" • {p}")
|
|
1246
|
+
if len(manager.list_available()) > 10:
|
|
1247
|
+
click.echo(f" ... and {len(manager.list_available()) - 10} more")
|
|
1248
|
+
return
|
|
1249
|
+
|
|
1250
|
+
click.echo(f"Installing plugin '{name}'...")
|
|
1251
|
+
click.echo(f" Packages: {', '.join(info.packages)}")
|
|
1252
|
+
|
|
1253
|
+
if manager.install(name, upgrade=upgrade):
|
|
1254
|
+
click.echo(f"\n✓ Plugin '{name}' installed successfully!")
|
|
1255
|
+
click.echo("\nUsage:")
|
|
1256
|
+
click.echo(" from flowyml.plugins import get_plugin")
|
|
1257
|
+
click.echo(f' plugin = get_plugin("{name}")')
|
|
1258
|
+
else:
|
|
1259
|
+
click.echo(f"\n✗ Failed to install '{name}'", err=True)
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
@plugin.command("info")
|
|
1263
|
+
@click.argument("name")
|
|
1264
|
+
def plugin_info(name: str) -> None:
|
|
1265
|
+
"""Show detailed information about a plugin.
|
|
1266
|
+
|
|
1267
|
+
Displays the plugin description, required packages, documentation URL,
|
|
1268
|
+
and current installation status.
|
|
1269
|
+
"""
|
|
1270
|
+
from flowyml.plugins import get_plugin_info, get_manager
|
|
1271
|
+
|
|
1272
|
+
info = get_plugin_info(name)
|
|
1273
|
+
manager = get_manager()
|
|
1274
|
+
|
|
1275
|
+
if not info:
|
|
1276
|
+
click.echo(f"✗ Plugin '{name}' not found", err=True)
|
|
1277
|
+
return
|
|
1278
|
+
|
|
1279
|
+
is_installed = manager.is_installed(name)
|
|
1280
|
+
status = "✓ Installed" if is_installed else "○ Not installed"
|
|
1281
|
+
|
|
1282
|
+
click.echo(f"\n📦 Plugin: {info.name}")
|
|
1283
|
+
click.echo(f" Status: {status}")
|
|
1284
|
+
click.echo(f" Type: {info.plugin_type.value.replace('_', ' ').title()}")
|
|
1285
|
+
click.echo(f" Description: {info.description}")
|
|
1286
|
+
click.echo(f" Version: {info.version}")
|
|
1287
|
+
click.echo(f" Author: {info.author}")
|
|
1288
|
+
click.echo("\n Packages:")
|
|
1289
|
+
for pkg in info.packages:
|
|
1290
|
+
click.echo(f" • {pkg}")
|
|
1291
|
+
if info.tags:
|
|
1292
|
+
click.echo(f"\n Tags: {', '.join(info.tags)}")
|
|
1293
|
+
if info.documentation_url:
|
|
1294
|
+
click.echo(f"\n Docs: {info.documentation_url}")
|
|
1295
|
+
|
|
1296
|
+
if not is_installed:
|
|
1297
|
+
click.echo(f"\n Install with: flowyml plugin install {name}")
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
@plugin.command("uninstall")
|
|
1301
|
+
@click.argument("name")
|
|
1302
|
+
@click.confirmation_option(prompt="Are you sure you want to uninstall?")
|
|
1303
|
+
def plugin_uninstall(name: str) -> None:
|
|
1304
|
+
"""Uninstall a plugin.
|
|
1305
|
+
|
|
1306
|
+
Removes the underlying packages for a plugin.
|
|
1307
|
+
"""
|
|
1308
|
+
from flowyml.plugins import get_manager
|
|
1309
|
+
|
|
1310
|
+
manager = get_manager()
|
|
1311
|
+
|
|
1312
|
+
if not manager.is_installed(name):
|
|
1313
|
+
click.echo(f"Plugin '{name}' is not installed")
|
|
1314
|
+
return
|
|
1315
|
+
|
|
1316
|
+
click.echo(f"Uninstalling plugin '{name}'...")
|
|
1317
|
+
|
|
1318
|
+
if manager.uninstall(name):
|
|
1319
|
+
click.echo(f"✓ Plugin '{name}' uninstalled")
|
|
1320
|
+
else:
|
|
1321
|
+
click.echo(f"✗ Failed to uninstall '{name}'", err=True)
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
@plugin.command("install-git")
|
|
1325
|
+
@click.argument("git_url")
|
|
1326
|
+
def plugin_install_git(git_url: str) -> None:
|
|
1327
|
+
"""Install a community plugin from a git repository.
|
|
1328
|
+
|
|
1329
|
+
Example:
|
|
1330
|
+
flowyml plugin install-git https://github.com/user/flowyml-custom-plugin.git
|
|
1331
|
+
"""
|
|
1332
|
+
from flowyml.plugins import get_manager
|
|
1333
|
+
|
|
1334
|
+
manager = get_manager()
|
|
1335
|
+
|
|
1336
|
+
click.echo(f"Installing plugin from {git_url}...")
|
|
1337
|
+
|
|
1338
|
+
if manager.install_from_git(git_url):
|
|
1339
|
+
click.echo("✓ Plugin installed from git!")
|
|
1340
|
+
click.echo(" Run 'flowyml plugin list --installed' to see available plugins")
|
|
1341
|
+
else:
|
|
1342
|
+
click.echo("✗ Failed to install from git", err=True)
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
# =============================================================================
|
|
1346
|
+
# STACK COMMANDS (Config-based plugin management)
|
|
1347
|
+
# =============================================================================
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
@cli.group()
|
|
1351
|
+
def stack() -> None:
|
|
1352
|
+
"""Stack configuration commands.
|
|
1353
|
+
|
|
1354
|
+
Configure your FlowyML stack (experiment_tracker, artifact_store,
|
|
1355
|
+
orchestrator, etc.) in flowyml.yaml for seamless integration.
|
|
1356
|
+
|
|
1357
|
+
With a configured stack, your code stays clean:
|
|
1358
|
+
from flowyml.plugins import start_run, log_metrics, save_model
|
|
1359
|
+
|
|
1360
|
+
start_run("training")
|
|
1361
|
+
log_metrics({"accuracy": 0.95})
|
|
1362
|
+
save_model(model, "models/classifier")
|
|
1363
|
+
"""
|
|
1364
|
+
pass
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
@stack.command("init")
|
|
1368
|
+
@click.option("--tracker", type=str, help="Experiment tracker plugin (e.g., mlflow)")
|
|
1369
|
+
@click.option("--store", type=str, help="Artifact store plugin (e.g., gcs, s3)")
|
|
1370
|
+
@click.option("--orchestrator", type=str, help="Orchestrator plugin (e.g., vertex_ai)")
|
|
1371
|
+
@click.option("--registry", type=str, help="Container registry plugin (e.g., gcr)")
|
|
1372
|
+
@click.option("--force", is_flag=True, help="Overwrite existing config")
|
|
1373
|
+
def stack_init(
|
|
1374
|
+
tracker: str,
|
|
1375
|
+
store: str,
|
|
1376
|
+
orchestrator: str,
|
|
1377
|
+
registry: str,
|
|
1378
|
+
force: bool,
|
|
1379
|
+
) -> None:
|
|
1380
|
+
"""Initialize a flowyml.yaml configuration file.
|
|
1381
|
+
|
|
1382
|
+
Creates a template configuration file with your selected plugins.
|
|
1383
|
+
You can then customize the configuration with your settings.
|
|
1384
|
+
|
|
1385
|
+
Examples:
|
|
1386
|
+
flowyml stack init --tracker mlflow --store gcs
|
|
1387
|
+
flowyml stack init --tracker mlflow --store s3 --orchestrator kubernetes
|
|
1388
|
+
"""
|
|
1389
|
+
import os
|
|
1390
|
+
from flowyml.plugins import generate_config_template
|
|
1391
|
+
|
|
1392
|
+
config_path = "flowyml.yaml"
|
|
1393
|
+
|
|
1394
|
+
if os.path.exists(config_path) and not force:
|
|
1395
|
+
click.echo(f"✗ {config_path} already exists. Use --force to overwrite.", err=True)
|
|
1396
|
+
return
|
|
1397
|
+
|
|
1398
|
+
content = generate_config_template(
|
|
1399
|
+
tracker=tracker,
|
|
1400
|
+
store=store,
|
|
1401
|
+
orchestrator=orchestrator,
|
|
1402
|
+
registry=registry,
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
with open(config_path, "w") as f:
|
|
1406
|
+
f.write(content)
|
|
1407
|
+
|
|
1408
|
+
click.echo(f"✓ Created {config_path}")
|
|
1409
|
+
click.echo("\nNext steps:")
|
|
1410
|
+
click.echo(" 1. Edit flowyml.yaml with your settings")
|
|
1411
|
+
|
|
1412
|
+
# Show which plugins to install
|
|
1413
|
+
plugins_to_install = [p for p in [tracker, store, orchestrator, registry] if p]
|
|
1414
|
+
if plugins_to_install:
|
|
1415
|
+
click.echo(f" 2. Install plugins: flowyml plugin install {' '.join(plugins_to_install)}")
|
|
1416
|
+
|
|
1417
|
+
click.echo(" 3. Use in code:")
|
|
1418
|
+
click.echo(" from flowyml.plugins import start_run, log_metrics, save_model")
|
|
1419
|
+
click.echo(' start_run("my_training")')
|
|
1420
|
+
|
|
1421
|
+
|
|
1422
|
+
@stack.command("show")
|
|
1423
|
+
def stack_show() -> None:
|
|
1424
|
+
"""Show the currently configured stack.
|
|
1425
|
+
|
|
1426
|
+
Displays all plugins configured in flowyml.yaml and their status.
|
|
1427
|
+
"""
|
|
1428
|
+
from flowyml.plugins import validate_stack, get_config
|
|
1429
|
+
|
|
1430
|
+
config = get_config()
|
|
1431
|
+
plugins_config = config.plugins_config
|
|
1432
|
+
|
|
1433
|
+
if not plugins_config:
|
|
1434
|
+
click.echo("No stack configured. Run 'flowyml stack init' to create flowyml.yaml")
|
|
1435
|
+
return
|
|
1436
|
+
|
|
1437
|
+
click.echo("\n📦 Current Stack:\n")
|
|
1438
|
+
|
|
1439
|
+
validation = validate_stack()
|
|
1440
|
+
|
|
1441
|
+
for role, conf in plugins_config.items():
|
|
1442
|
+
if isinstance(conf, dict):
|
|
1443
|
+
plugin_type = conf.get("type", "unknown")
|
|
1444
|
+
is_installed = validation.get(role, False)
|
|
1445
|
+
status = "✓" if is_installed else "○"
|
|
1446
|
+
|
|
1447
|
+
click.echo(f" {status} {role}:")
|
|
1448
|
+
click.echo(f" type: {plugin_type}")
|
|
1449
|
+
|
|
1450
|
+
# Show key config values (not sensitive ones)
|
|
1451
|
+
for key, value in conf.items():
|
|
1452
|
+
if key != "type" and "key" not in key.lower() and "secret" not in key.lower():
|
|
1453
|
+
click.echo(f" {key}: {value}")
|
|
1454
|
+
click.echo()
|
|
1455
|
+
|
|
1456
|
+
# Show missing plugins
|
|
1457
|
+
missing = [role for role, installed in validation.items() if not installed]
|
|
1458
|
+
if missing:
|
|
1459
|
+
click.echo("⚠️ Some plugins are not installed:")
|
|
1460
|
+
for role in missing:
|
|
1461
|
+
plugin_type = plugins_config.get(role, {}).get("type")
|
|
1462
|
+
if plugin_type:
|
|
1463
|
+
click.echo(f" flowyml plugin install {plugin_type}")
|
|
1464
|
+
|
|
1465
|
+
|
|
1466
|
+
@stack.command("validate")
|
|
1467
|
+
def stack_validate() -> None:
|
|
1468
|
+
"""Validate the current stack configuration.
|
|
1469
|
+
|
|
1470
|
+
Checks that all configured plugins are installed and can be initialized.
|
|
1471
|
+
"""
|
|
1472
|
+
from flowyml.plugins import get_config, validate_stack
|
|
1473
|
+
|
|
1474
|
+
click.echo("Validating stack configuration...\n")
|
|
1475
|
+
|
|
1476
|
+
config = get_config()
|
|
1477
|
+
|
|
1478
|
+
if not config._config_path:
|
|
1479
|
+
click.echo("✗ No flowyml.yaml found", err=True)
|
|
1480
|
+
click.echo(" Run 'flowyml stack init' to create one")
|
|
1481
|
+
return
|
|
1482
|
+
|
|
1483
|
+
click.echo(f"Config file: {config._config_path}\n")
|
|
1484
|
+
|
|
1485
|
+
validation = validate_stack()
|
|
1486
|
+
|
|
1487
|
+
if not validation:
|
|
1488
|
+
click.echo("No plugins configured")
|
|
1489
|
+
return
|
|
1490
|
+
|
|
1491
|
+
all_valid = True
|
|
1492
|
+
for role, is_installed in validation.items():
|
|
1493
|
+
status = "✓" if is_installed else "✗"
|
|
1494
|
+
click.echo(f" {status} {role}")
|
|
1495
|
+
if not is_installed:
|
|
1496
|
+
all_valid = False
|
|
1497
|
+
|
|
1498
|
+
click.echo()
|
|
1499
|
+
if all_valid:
|
|
1500
|
+
click.echo("✓ All plugins are installed and ready!")
|
|
1501
|
+
else:
|
|
1502
|
+
click.echo("✗ Some plugins need to be installed")
|
|
1503
|
+
click.echo(" Run 'flowyml stack show' for installation commands")
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
@stack.command("install")
|
|
1507
|
+
def stack_install() -> None:
|
|
1508
|
+
"""Install all plugins configured in flowyml.yaml."""
|
|
1509
|
+
from flowyml.plugins import get_config, get_manager
|
|
1510
|
+
|
|
1511
|
+
config = get_config()
|
|
1512
|
+
plugins_config = config.plugins_config
|
|
1513
|
+
manager = get_manager()
|
|
1514
|
+
|
|
1515
|
+
if not plugins_config:
|
|
1516
|
+
click.echo("No stack configured. Run 'flowyml stack init' first.")
|
|
1517
|
+
return
|
|
1518
|
+
|
|
1519
|
+
click.echo("Installing stack plugins...\n")
|
|
1520
|
+
|
|
1521
|
+
for _role, conf in plugins_config.items():
|
|
1522
|
+
if isinstance(conf, dict):
|
|
1523
|
+
plugin_type = conf.get("type")
|
|
1524
|
+
if plugin_type:
|
|
1525
|
+
if manager.is_installed(plugin_type):
|
|
1526
|
+
click.echo(f" ✓ {plugin_type} (already installed)")
|
|
1527
|
+
else:
|
|
1528
|
+
click.echo(f" Installing {plugin_type}...")
|
|
1529
|
+
if manager.install(plugin_type):
|
|
1530
|
+
click.echo(f" ✓ {plugin_type} installed")
|
|
1531
|
+
else:
|
|
1532
|
+
click.echo(f" ✗ Failed to install {plugin_type}")
|
|
1533
|
+
|
|
1534
|
+
click.echo("\n✓ Stack installation complete!")
|
|
1535
|
+
click.echo(" Run 'flowyml stack validate' to verify the configuration")
|
|
1536
|
+
|
|
1537
|
+
|
|
829
1538
|
if __name__ == "__main__":
|
|
830
1539
|
cli()
|