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.
- praisonai_deploy-0.0.1/PKG-INFO +93 -0
- praisonai_deploy-0.0.1/README.md +71 -0
- praisonai_deploy-0.0.1/praisonai_deploy/__init__.py +91 -0
- praisonai_deploy-0.0.1/praisonai_deploy/__main__.py +18 -0
- praisonai_deploy-0.0.1/praisonai_deploy/_bootstrap.py +37 -0
- praisonai_deploy-0.0.1/praisonai_deploy/_code_bridge.py +28 -0
- praisonai_deploy-0.0.1/praisonai_deploy/_plugin_registry.py +150 -0
- praisonai_deploy-0.0.1/praisonai_deploy/_version.py +1 -0
- praisonai_deploy-0.0.1/praisonai_deploy/_wrapper_bridge.py +31 -0
- praisonai_deploy-0.0.1/praisonai_deploy/api.py +263 -0
- praisonai_deploy-0.0.1/praisonai_deploy/cli/__init__.py +1 -0
- praisonai_deploy-0.0.1/praisonai_deploy/cli/app.py +12 -0
- praisonai_deploy-0.0.1/praisonai_deploy/cli/commands/__init__.py +1 -0
- praisonai_deploy-0.0.1/praisonai_deploy/cli/commands/deploy.py +209 -0
- praisonai_deploy-0.0.1/praisonai_deploy/cli/features/deploy.py +625 -0
- praisonai_deploy-0.0.1/praisonai_deploy/docker.py +519 -0
- praisonai_deploy-0.0.1/praisonai_deploy/doctor.py +367 -0
- praisonai_deploy-0.0.1/praisonai_deploy/main.py +337 -0
- praisonai_deploy-0.0.1/praisonai_deploy/models.py +179 -0
- praisonai_deploy-0.0.1/praisonai_deploy/providers/__init__.py +33 -0
- praisonai_deploy-0.0.1/praisonai_deploy/providers/_registry.py +45 -0
- praisonai_deploy-0.0.1/praisonai_deploy/providers/aws.py +331 -0
- praisonai_deploy-0.0.1/praisonai_deploy/providers/azure.py +371 -0
- praisonai_deploy-0.0.1/praisonai_deploy/providers/base.py +94 -0
- praisonai_deploy-0.0.1/praisonai_deploy/providers/gcp.py +314 -0
- praisonai_deploy-0.0.1/praisonai_deploy/scheduler/__init__.py +1 -0
- praisonai_deploy-0.0.1/praisonai_deploy/scheduler/deployment.py +232 -0
- praisonai_deploy-0.0.1/praisonai_deploy/schema.py +208 -0
- praisonai_deploy-0.0.1/praisonai_deploy.egg-info/PKG-INFO +93 -0
- praisonai_deploy-0.0.1/praisonai_deploy.egg-info/SOURCES.txt +42 -0
- praisonai_deploy-0.0.1/praisonai_deploy.egg-info/dependency_links.txt +1 -0
- praisonai_deploy-0.0.1/praisonai_deploy.egg-info/entry_points.txt +7 -0
- praisonai_deploy-0.0.1/praisonai_deploy.egg-info/requires.txt +13 -0
- praisonai_deploy-0.0.1/praisonai_deploy.egg-info/top_level.txt +1 -0
- praisonai_deploy-0.0.1/pyproject.toml +59 -0
- praisonai_deploy-0.0.1/setup.cfg +4 -0
- praisonai_deploy-0.0.1/tests/test_api.py +138 -0
- praisonai_deploy-0.0.1/tests/test_api_auth_default.py +19 -0
- praisonai_deploy-0.0.1/tests/test_cli.py +390 -0
- praisonai_deploy-0.0.1/tests/test_docker.py +196 -0
- praisonai_deploy-0.0.1/tests/test_doctor.py +322 -0
- praisonai_deploy-0.0.1/tests/test_models.py +263 -0
- praisonai_deploy-0.0.1/tests/test_providers.py +339 -0
- 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)
|