praisonai-deploy 0.0.1__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.
Files changed (44) hide show
  1. praisonai_deploy-0.0.1/PKG-INFO +93 -0
  2. praisonai_deploy-0.0.1/README.md +71 -0
  3. praisonai_deploy-0.0.1/praisonai_deploy/__init__.py +91 -0
  4. praisonai_deploy-0.0.1/praisonai_deploy/__main__.py +18 -0
  5. praisonai_deploy-0.0.1/praisonai_deploy/_bootstrap.py +37 -0
  6. praisonai_deploy-0.0.1/praisonai_deploy/_code_bridge.py +28 -0
  7. praisonai_deploy-0.0.1/praisonai_deploy/_plugin_registry.py +150 -0
  8. praisonai_deploy-0.0.1/praisonai_deploy/_version.py +1 -0
  9. praisonai_deploy-0.0.1/praisonai_deploy/_wrapper_bridge.py +31 -0
  10. praisonai_deploy-0.0.1/praisonai_deploy/api.py +263 -0
  11. praisonai_deploy-0.0.1/praisonai_deploy/cli/__init__.py +1 -0
  12. praisonai_deploy-0.0.1/praisonai_deploy/cli/app.py +12 -0
  13. praisonai_deploy-0.0.1/praisonai_deploy/cli/commands/__init__.py +1 -0
  14. praisonai_deploy-0.0.1/praisonai_deploy/cli/commands/deploy.py +209 -0
  15. praisonai_deploy-0.0.1/praisonai_deploy/cli/features/deploy.py +625 -0
  16. praisonai_deploy-0.0.1/praisonai_deploy/docker.py +519 -0
  17. praisonai_deploy-0.0.1/praisonai_deploy/doctor.py +367 -0
  18. praisonai_deploy-0.0.1/praisonai_deploy/main.py +337 -0
  19. praisonai_deploy-0.0.1/praisonai_deploy/models.py +179 -0
  20. praisonai_deploy-0.0.1/praisonai_deploy/providers/__init__.py +33 -0
  21. praisonai_deploy-0.0.1/praisonai_deploy/providers/_registry.py +45 -0
  22. praisonai_deploy-0.0.1/praisonai_deploy/providers/aws.py +331 -0
  23. praisonai_deploy-0.0.1/praisonai_deploy/providers/azure.py +371 -0
  24. praisonai_deploy-0.0.1/praisonai_deploy/providers/base.py +94 -0
  25. praisonai_deploy-0.0.1/praisonai_deploy/providers/gcp.py +314 -0
  26. praisonai_deploy-0.0.1/praisonai_deploy/scheduler/__init__.py +1 -0
  27. praisonai_deploy-0.0.1/praisonai_deploy/scheduler/deployment.py +232 -0
  28. praisonai_deploy-0.0.1/praisonai_deploy/schema.py +208 -0
  29. praisonai_deploy-0.0.1/praisonai_deploy.egg-info/PKG-INFO +93 -0
  30. praisonai_deploy-0.0.1/praisonai_deploy.egg-info/SOURCES.txt +42 -0
  31. praisonai_deploy-0.0.1/praisonai_deploy.egg-info/dependency_links.txt +1 -0
  32. praisonai_deploy-0.0.1/praisonai_deploy.egg-info/entry_points.txt +7 -0
  33. praisonai_deploy-0.0.1/praisonai_deploy.egg-info/requires.txt +13 -0
  34. praisonai_deploy-0.0.1/praisonai_deploy.egg-info/top_level.txt +1 -0
  35. praisonai_deploy-0.0.1/pyproject.toml +59 -0
  36. praisonai_deploy-0.0.1/setup.cfg +4 -0
  37. praisonai_deploy-0.0.1/tests/test_api.py +138 -0
  38. praisonai_deploy-0.0.1/tests/test_api_auth_default.py +19 -0
  39. praisonai_deploy-0.0.1/tests/test_cli.py +390 -0
  40. praisonai_deploy-0.0.1/tests/test_docker.py +196 -0
  41. praisonai_deploy-0.0.1/tests/test_doctor.py +322 -0
  42. praisonai_deploy-0.0.1/tests/test_models.py +263 -0
  43. praisonai_deploy-0.0.1/tests/test_providers.py +339 -0
  44. praisonai_deploy-0.0.1/tests/test_schema.py +339 -0
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: praisonai-deploy
3
+ Version: 0.0.1
4
+ Summary: Deployment for PraisonAI — API, Docker, and cloud (AWS, Azure, GCP) deployments extracted from the praisonai wrapper.
5
+ Author: Mervin Praison
6
+ License: MIT
7
+ Project-URL: Homepage, https://docs.praison.ai
8
+ Project-URL: Repository, https://github.com/mervinpraison/PraisonAI
9
+ Requires-Python: <3.15,>=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: praisonaiagents>=1.6.126
12
+ Requires-Dist: rich>=13.7
13
+ Requires-Dist: typer>=0.12.0
14
+ Requires-Dist: click>=8.4.2
15
+ Requires-Dist: PyYAML>=6.0
16
+ Requires-Dist: pydantic>=2.0
17
+ Provides-Extra: api
18
+ Requires-Dist: flask>=3.0.0; extra == "api"
19
+ Requires-Dist: flask-cors>=4.0.0; extra == "api"
20
+ Provides-Extra: all
21
+ Requires-Dist: praisonai-deploy[api]; extra == "all"
22
+
23
+ # praisonai-deploy
24
+
25
+ Deployment tooling for PraisonAI — API servers, Docker images, and cloud providers (AWS, Azure, GCP).
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install praisonai-deploy
31
+ pip install "praisonai-deploy[api]" # Flask API server generation
32
+ pip install "praisonai[deploy]" # full umbrella with wrapper shims
33
+ ```
34
+
35
+ ## CLI
36
+
37
+ ```bash
38
+ praisonai deploy run --file agents.yaml
39
+ praisonai deploy doctor --all
40
+ praisonai deploy validate --file agents.yaml
41
+ praisonai deploy plan --file agents.yaml
42
+ praisonai deploy status --file agents.yaml
43
+ praisonai deploy destroy --file agents.yaml --yes
44
+ praisonai deploy docker agents.yaml --tag v1
45
+ praisonai deploy aws agents.yaml --region us-east-1
46
+ ```
47
+
48
+ Standalone console script:
49
+
50
+ ```bash
51
+ praisonai-deploy --help
52
+ ```
53
+
54
+ ## Python API
55
+
56
+ ```python
57
+ from praisonai_deploy import Deploy
58
+
59
+ deploy = Deploy.from_yaml("agents.yaml")
60
+ result = deploy.deploy()
61
+ status = deploy.status()
62
+ ```
63
+
64
+ Legacy import paths (`praisonai.deploy.*`) remain available when the `praisonai` wrapper is installed.
65
+
66
+ ## Runtime dependency (generated servers)
67
+
68
+ Generated API servers and Docker images install **`praisonai`** (full wrapper) at runtime — they embed `from praisonai import PraisonAI`. The deploy package owns orchestration (generate, build, plan, doctor); containers need `pip install praisonai flask gunicorn`.
69
+
70
+ ## Cloud provider notes
71
+
72
+ | Provider | Behaviour |
73
+ |----------|-----------|
74
+ | AWS ECS | Update-only path; greenfield deploy requires pre-existing VPC/service config |
75
+ | Azure | Container Apps create/update |
76
+ | GCP | Cloud Run create-or-update |
77
+
78
+ Host CLIs required: `docker`, `aws`, `az`, `gcloud` (no boto3/Azure SDK in this package).
79
+
80
+ ## Monorepo development
81
+
82
+ Editable install from this directory:
83
+
84
+ ```bash
85
+ cd src/praisonai-deploy
86
+ uv pip install -e .
87
+ ```
88
+
89
+ Regression gates:
90
+
91
+ - `scripts/check_c14_deploy_imports.sh`
92
+ - `src/praisonai/tests/unit/test_c14_deploy_backward_compat.py`
93
+ - `src/praisonai-deploy/tests/`
@@ -0,0 +1,71 @@
1
+ # praisonai-deploy
2
+
3
+ Deployment tooling for PraisonAI — API servers, Docker images, and cloud providers (AWS, Azure, GCP).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install praisonai-deploy
9
+ pip install "praisonai-deploy[api]" # Flask API server generation
10
+ pip install "praisonai[deploy]" # full umbrella with wrapper shims
11
+ ```
12
+
13
+ ## CLI
14
+
15
+ ```bash
16
+ praisonai deploy run --file agents.yaml
17
+ praisonai deploy doctor --all
18
+ praisonai deploy validate --file agents.yaml
19
+ praisonai deploy plan --file agents.yaml
20
+ praisonai deploy status --file agents.yaml
21
+ praisonai deploy destroy --file agents.yaml --yes
22
+ praisonai deploy docker agents.yaml --tag v1
23
+ praisonai deploy aws agents.yaml --region us-east-1
24
+ ```
25
+
26
+ Standalone console script:
27
+
28
+ ```bash
29
+ praisonai-deploy --help
30
+ ```
31
+
32
+ ## Python API
33
+
34
+ ```python
35
+ from praisonai_deploy import Deploy
36
+
37
+ deploy = Deploy.from_yaml("agents.yaml")
38
+ result = deploy.deploy()
39
+ status = deploy.status()
40
+ ```
41
+
42
+ Legacy import paths (`praisonai.deploy.*`) remain available when the `praisonai` wrapper is installed.
43
+
44
+ ## Runtime dependency (generated servers)
45
+
46
+ Generated API servers and Docker images install **`praisonai`** (full wrapper) at runtime — they embed `from praisonai import PraisonAI`. The deploy package owns orchestration (generate, build, plan, doctor); containers need `pip install praisonai flask gunicorn`.
47
+
48
+ ## Cloud provider notes
49
+
50
+ | Provider | Behaviour |
51
+ |----------|-----------|
52
+ | AWS ECS | Update-only path; greenfield deploy requires pre-existing VPC/service config |
53
+ | Azure | Container Apps create/update |
54
+ | GCP | Cloud Run create-or-update |
55
+
56
+ Host CLIs required: `docker`, `aws`, `az`, `gcloud` (no boto3/Azure SDK in this package).
57
+
58
+ ## Monorepo development
59
+
60
+ Editable install from this directory:
61
+
62
+ ```bash
63
+ cd src/praisonai-deploy
64
+ uv pip install -e .
65
+ ```
66
+
67
+ Regression gates:
68
+
69
+ - `scripts/check_c14_deploy_imports.sh`
70
+ - `src/praisonai/tests/unit/test_c14_deploy_backward_compat.py`
71
+ - `src/praisonai-deploy/tests/`
@@ -0,0 +1,91 @@
1
+ """
2
+ Deploy module for PraisonAI - API, Docker, and Cloud deployments.
3
+ """
4
+ from typing import TYPE_CHECKING, Optional, Dict, Any
5
+
6
+ from praisonai_deploy._version import __version__
7
+
8
+ if TYPE_CHECKING:
9
+ from .models import DeployConfig, DeployResult, DeployType, CloudProvider
10
+ from .schema import validate_agents_yaml, generate_sample_yaml
11
+ from .doctor import DoctorReport, run_all_checks
12
+
13
+
14
+ def __getattr__(name):
15
+ """Lazy load deploy modules."""
16
+ if name == 'Deploy':
17
+ from .main import Deploy
18
+ return Deploy
19
+ elif name == 'DeployConfig':
20
+ from .models import DeployConfig
21
+ return DeployConfig
22
+ elif name == 'DeployType':
23
+ from .models import DeployType
24
+ return DeployType
25
+ elif name == 'CloudProvider':
26
+ from .models import CloudProvider
27
+ return CloudProvider
28
+ elif name == 'DeployResult':
29
+ from .models import DeployResult
30
+ return DeployResult
31
+ elif name == 'DeployStatus':
32
+ from .models import DeployStatus
33
+ return DeployStatus
34
+ elif name == 'DestroyResult':
35
+ from .models import DestroyResult
36
+ return DestroyResult
37
+ elif name == 'ServiceState':
38
+ from .models import ServiceState
39
+ return ServiceState
40
+ elif name == 'validate_agents_yaml':
41
+ from .schema import validate_agents_yaml
42
+ return validate_agents_yaml
43
+ elif name == 'generate_sample_yaml':
44
+ from .schema import generate_sample_yaml
45
+ return generate_sample_yaml
46
+ elif name == 'run_all_checks':
47
+ from .doctor import run_all_checks
48
+ return run_all_checks
49
+ elif name == 'get_deployment_status':
50
+ return get_deployment_status
51
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
52
+
53
+
54
+ def get_deployment_status(
55
+ deployment_name: Optional[str] = None,
56
+ *,
57
+ agents_file: str = "agents.yaml",
58
+ ) -> Dict[str, Any]:
59
+ """Return deployment status for MCP and tooling."""
60
+ from .main import Deploy
61
+
62
+ deploy = Deploy.from_yaml(agents_file)
63
+ status = deploy.status()
64
+ payload: Dict[str, Any] = {
65
+ "state": status.state.value if hasattr(status.state, "value") else str(status.state),
66
+ "message": status.message,
67
+ "url": status.url,
68
+ "service_name": status.service_name,
69
+ "provider": status.provider,
70
+ "healthy": status.healthy,
71
+ }
72
+ if deployment_name:
73
+ payload["deployment_name"] = deployment_name
74
+ return payload
75
+
76
+
77
+ __all__ = [
78
+ 'Deploy',
79
+ 'DeployConfig',
80
+ 'DeployType',
81
+ 'CloudProvider',
82
+ 'DeployResult',
83
+ 'DeployStatus',
84
+ 'DestroyResult',
85
+ 'ServiceState',
86
+ 'validate_agents_yaml',
87
+ 'generate_sample_yaml',
88
+ 'run_all_checks',
89
+ 'get_deployment_status',
90
+ '__version__',
91
+ ]
@@ -0,0 +1,18 @@
1
+ """Console entry: ``praisonai-deploy``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+
8
+ def main(argv: list[str] | None = None) -> None:
9
+ from praisonai_deploy.cli.app import app
10
+
11
+ args = argv if argv is not None else sys.argv[1:]
12
+ if not args:
13
+ args = ["--help"]
14
+ app(args=args, prog_name="praisonai-deploy")
15
+
16
+
17
+ if __name__ == "__main__":
18
+ main()
@@ -0,0 +1,37 @@
1
+ """Monorepo bootstrap for ``praisonai_deploy`` and optional ``praisonai_code``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ def ensure_praisonai_deploy() -> None:
10
+ """Ensure ``praisonai_deploy`` is importable in monorepo dev layouts."""
11
+ try:
12
+ import praisonai_deploy # noqa: F401
13
+ return
14
+ except ImportError:
15
+ pass
16
+
17
+ here = Path(__file__).resolve().parents[1] # .../src/praisonai-deploy
18
+ if (here / "praisonai_deploy").is_dir():
19
+ root = str(here)
20
+ if root not in sys.path:
21
+ sys.path.insert(0, root)
22
+
23
+
24
+ def ensure_praisonai_code() -> None:
25
+ """Optional code-tier imports when co-installed."""
26
+ try:
27
+ import praisonai_code # noqa: F401
28
+ return
29
+ except ImportError:
30
+ pass
31
+
32
+ deploy_src = Path(__file__).resolve().parents[1]
33
+ code_src = deploy_src.parent / "praisonai-code"
34
+ if (code_src / "praisonai_code").is_dir():
35
+ root = str(code_src)
36
+ if root not in sys.path:
37
+ sys.path.insert(0, root)
@@ -0,0 +1,28 @@
1
+ """Lazy access from praisonai-deploy to optional praisonai-code modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from typing import Any
7
+
8
+ from praisonai_deploy._bootstrap import ensure_praisonai_code
9
+
10
+
11
+ def code_available() -> bool:
12
+ ensure_praisonai_code()
13
+ try:
14
+ import praisonai_code # noqa: F401
15
+ return True
16
+ except ImportError:
17
+ return False
18
+
19
+
20
+ def import_code_module(name: str) -> Any:
21
+ ensure_praisonai_code()
22
+ try:
23
+ return importlib.import_module(name)
24
+ except ImportError as exc:
25
+ raise ImportError(
26
+ f"Optional code module {name!r} requires praisonai-code. "
27
+ "Install with: pip install praisonai-code"
28
+ ) from exc
@@ -0,0 +1,150 @@
1
+ """Plugin registry base for praisonai-deploy.
2
+
3
+ Prefers the shared implementation from ``praisonai-code`` when installed so the
4
+ two packages stay in lock-step, but falls back to a self-contained
5
+ implementation so ``praisonai-deploy`` works standalone (``pip install
6
+ praisonai-deploy`` without ``praisonai-code``).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ __all__ = ["PluginRegistry", "create_lazy_getattr", "logger"]
16
+
17
+ try: # Prefer the shared praisonai-code registry when available.
18
+ from praisonai_deploy._code_bridge import code_available
19
+
20
+ if not code_available():
21
+ raise ImportError("praisonai-code not installed")
22
+
23
+ from praisonai_deploy._code_bridge import import_code_module
24
+
25
+ _registry_mod = import_code_module("praisonai_code._registry")
26
+ PluginRegistry = _registry_mod.PluginRegistry
27
+ create_lazy_getattr = _registry_mod.create_lazy_getattr
28
+ except ImportError:
29
+ import threading
30
+ from importlib.metadata import entry_points
31
+ from typing import Callable, Dict, Generic, Optional, Type, TypeVar
32
+
33
+ T = TypeVar("T")
34
+
35
+ class PluginRegistry(Generic[T]):
36
+ """Self-contained fallback registry (builtins + entry points)."""
37
+
38
+ _default_locks_guard = threading.Lock()
39
+
40
+ def __init__(
41
+ self,
42
+ *,
43
+ entry_point_group: str,
44
+ builtins: Optional[Dict[str, Callable[[], Type[T]]]] = None,
45
+ discover_entry_points: bool = True,
46
+ ) -> None:
47
+ self._entry_point_group = entry_point_group
48
+ self._loaders: Dict[str, Callable[[], Type[T]]] = {}
49
+ self._items: Dict[str, Type[T]] = {}
50
+ self._lock = threading.RLock()
51
+
52
+ if builtins:
53
+ for name, loader in builtins.items():
54
+ self._loaders[name.lower()] = loader
55
+
56
+ if discover_entry_points:
57
+ try:
58
+ for ep in entry_points(group=self._entry_point_group):
59
+ self._loaders[ep.name.lower()] = ep.load
60
+ except Exception:
61
+ logger.debug(
62
+ "Entry points not available for group %s",
63
+ self._entry_point_group,
64
+ )
65
+
66
+ def register(self, name: str, cls: Type[T]) -> None:
67
+ with self._lock:
68
+ key = name.lower()
69
+ self._loaders[key] = lambda: cls
70
+ self._items[key] = cls
71
+
72
+ def resolve(self, name: str) -> Type[T]:
73
+ key = name.lower()
74
+ with self._lock:
75
+ cls = self._items.get(key)
76
+ if cls is not None:
77
+ return cls
78
+ loader = self._loaders.get(key)
79
+ if loader is None:
80
+ available = sorted(self._loaders.keys())
81
+ raise ValueError(
82
+ f"Unknown {self._entry_point_group} plugin: {name!r}. "
83
+ f"Available: {available}"
84
+ )
85
+ try:
86
+ cls = loader()
87
+ except ImportError as exc:
88
+ raise ValueError(
89
+ f"Plugin {name!r} is registered but its dependencies "
90
+ f"are not installed: {exc}"
91
+ ) from exc
92
+ except Exception as exc: # noqa: BLE001 -- external plugin boundary
93
+ raise ValueError(
94
+ f"Plugin {name!r} failed to load: {exc}"
95
+ ) from exc
96
+ with self._lock:
97
+ if self._loaders.get(key) is loader:
98
+ self._items[key] = cls
99
+ return cls
100
+
101
+ def create(self, name: str, *args, **kwargs) -> T:
102
+ return self.resolve(name)(*args, **kwargs)
103
+
104
+ def list_names(self) -> list[str]:
105
+ with self._lock:
106
+ return sorted(self._loaders.keys())
107
+
108
+ def is_available(self, name: str) -> bool:
109
+ try:
110
+ self.resolve(name)
111
+ return True
112
+ except ValueError:
113
+ return False
114
+
115
+ @classmethod
116
+ def default(cls) -> "PluginRegistry[T]":
117
+ cache_key = "_default_instance"
118
+ lock_key = "_default_instance_lock"
119
+ if lock_key not in cls.__dict__:
120
+ with PluginRegistry._default_locks_guard:
121
+ if lock_key not in cls.__dict__:
122
+ setattr(cls, lock_key, threading.Lock())
123
+ cache = cls.__dict__.get(cache_key)
124
+ if cache is not None:
125
+ return cache
126
+ with getattr(cls, lock_key):
127
+ cache = cls.__dict__.get(cache_key)
128
+ if cache is None:
129
+ cache = cls()
130
+ setattr(cls, cache_key, cache)
131
+ return cache
132
+
133
+ def create_lazy_getattr(registry: "PluginRegistry[T]") -> Callable[[str], T]:
134
+ import inspect
135
+
136
+ frame = inspect.currentframe()
137
+ if frame and frame.f_back:
138
+ module_name = frame.f_back.f_globals.get("__name__", "unknown")
139
+ else:
140
+ module_name = "unknown"
141
+
142
+ def __getattr__(name: str) -> T:
143
+ try:
144
+ return registry.resolve(name)
145
+ except ValueError:
146
+ raise AttributeError(
147
+ f"module {module_name!r} has no attribute {name!r}"
148
+ ) from None
149
+
150
+ return __getattr__
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,31 @@
1
+ """Lazy access from praisonai-deploy to optional praisonai wrapper modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from typing import Any
7
+
8
+
9
+ def wrapper_available() -> bool:
10
+ try:
11
+ import praisonai # noqa: F401
12
+ return True
13
+ except ImportError:
14
+ return False
15
+
16
+
17
+ def import_wrapper_module(name: str) -> Any:
18
+ if not wrapper_available():
19
+ raise ImportError(
20
+ f"Optional wrapper module {name!r} requires the praisonai package. "
21
+ "Install with: pip install praisonai"
22
+ )
23
+ return importlib.import_module(name)
24
+
25
+
26
+ def optional_wrapper_attr(module: str, attr: str, default: Any = None) -> Any:
27
+ try:
28
+ mod = import_wrapper_module(module)
29
+ except ImportError:
30
+ return default
31
+ return getattr(mod, attr, default)