deploypod 0.1.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.
Files changed (38) hide show
  1. deploypod-0.1.0/LICENSE +21 -0
  2. deploypod-0.1.0/PKG-INFO +104 -0
  3. deploypod-0.1.0/README.md +79 -0
  4. deploypod-0.1.0/pyproject.toml +42 -0
  5. deploypod-0.1.0/setup.cfg +4 -0
  6. deploypod-0.1.0/src/deploypod/__init__.py +31 -0
  7. deploypod-0.1.0/src/deploypod/_http.py +73 -0
  8. deploypod-0.1.0/src/deploypod/auth.py +90 -0
  9. deploypod-0.1.0/src/deploypod/client.py +122 -0
  10. deploypod-0.1.0/src/deploypod/env.py +49 -0
  11. deploypod-0.1.0/src/deploypod/exceptions.py +39 -0
  12. deploypod-0.1.0/src/deploypod/py.typed +0 -0
  13. deploypod-0.1.0/src/deploypod/resources/__init__.py +0 -0
  14. deploypod-0.1.0/src/deploypod/resources/agents.py +430 -0
  15. deploypod-0.1.0/src/deploypod/resources/async_tasks.py +84 -0
  16. deploypod-0.1.0/src/deploypod/resources/connections.py +57 -0
  17. deploypod-0.1.0/src/deploypod/resources/conversations.py +41 -0
  18. deploypod-0.1.0/src/deploypod/resources/custom_tools.py +126 -0
  19. deploypod-0.1.0/src/deploypod/resources/groups.py +71 -0
  20. deploypod-0.1.0/src/deploypod/resources/jobs.py +28 -0
  21. deploypod-0.1.0/src/deploypod/resources/knowledge_bases.py +47 -0
  22. deploypod-0.1.0/src/deploypod/resources/knowledge_sources.py +141 -0
  23. deploypod-0.1.0/src/deploypod/resources/mcp_servers.py +117 -0
  24. deploypod-0.1.0/src/deploypod/resources/organization.py +150 -0
  25. deploypod-0.1.0/src/deploypod/resources/providers.py +64 -0
  26. deploypod-0.1.0/src/deploypod/resources/skills.py +75 -0
  27. deploypod-0.1.0/src/deploypod/resources/users.py +56 -0
  28. deploypod-0.1.0/src/deploypod/types.py +390 -0
  29. deploypod-0.1.0/src/deploypod.egg-info/PKG-INFO +104 -0
  30. deploypod-0.1.0/src/deploypod.egg-info/SOURCES.txt +36 -0
  31. deploypod-0.1.0/src/deploypod.egg-info/dependency_links.txt +1 -0
  32. deploypod-0.1.0/src/deploypod.egg-info/requires.txt +6 -0
  33. deploypod-0.1.0/src/deploypod.egg-info/top_level.txt +1 -0
  34. deploypod-0.1.0/tests/test_agent_run.py +21 -0
  35. deploypod-0.1.0/tests/test_client_auth.py +101 -0
  36. deploypod-0.1.0/tests/test_empty_responses.py +59 -0
  37. deploypod-0.1.0/tests/test_invite_user.py +63 -0
  38. deploypod-0.1.0/tests/test_mcp_tool_test.py +28 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Deploypod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: deploypod
3
+ Version: 0.1.0
4
+ Summary: Python client for the Deploypod API: run AI agents, manage their tools and knowledge bases.
5
+ Author: Deploypod
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://deploypod.ai
8
+ Project-URL: Documentation, https://docs.deploypod.ai/en/gelistirici/sdk-ve-widget/
9
+ Keywords: deploypod,ai,agents,llm,sdk
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: httpx>=0.25.0
20
+ Requires-Dist: pydantic>=2.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-httpx>=0.30.0; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Deploypod Python SDK
27
+
28
+ The Python client for the [Deploypod](https://deploypod.ai) API. Run your AI
29
+ agents from code, and manage their instructions, tools and knowledge bases.
30
+
31
+ ```bash
32
+ pip install deploypod
33
+ ```
34
+
35
+ Python 3.9 or newer.
36
+
37
+ ## Sign in
38
+
39
+ Ask your Deploypod administrator for an application (`app_id`, `app_secret`)
40
+ and your organization's number. The client gets the access token itself and
41
+ renews it before it expires.
42
+
43
+ ```python
44
+ from deploypod import DeploypodClient
45
+
46
+ client = DeploypodClient(
47
+ org_id="42",
48
+ app_id="YOUR_APP_ID",
49
+ app_secret="YOUR_APP_SECRET",
50
+ )
51
+ ```
52
+
53
+ Keep `app_secret` out of your code: read it from the environment.
54
+
55
+ ```bash
56
+ export DEPLOYPOD_ORG_ID=42
57
+ export DEPLOYPOD_APP_ID=...
58
+ export DEPLOYPOD_APP_SECRET=...
59
+ ```
60
+
61
+ ```python
62
+ from deploypod import get_client_from_env
63
+
64
+ client = get_client_from_env()
65
+ ```
66
+
67
+ Already have an access token? Pass `token="..."` instead of the app
68
+ credentials. For an on-premises installation, pass
69
+ `base_url="https://your-host/api"`.
70
+
71
+ ## Run an agent
72
+
73
+ ```python
74
+ for agent in client.agents.list():
75
+ print(agent.uuid, agent.name)
76
+
77
+ result = client.agents.run(
78
+ "AGENT_UUID",
79
+ "How many days of annual leave do I have?",
80
+ run_exploration=True,
81
+ timeout=300,
82
+ )
83
+ print(result["result_summary"])
84
+ ```
85
+
86
+ `run` waits for the answer. Other request fields go in as keyword arguments,
87
+ for example `output_schema={...}` to get the answer as JSON in
88
+ `result["final_emit"]`. Requests through `deploypod.ai` are cut after 100
89
+ seconds; for longer work (web research, presentations) use the WebSocket API.
90
+
91
+ ## Errors
92
+
93
+ Every error is a `deploypod.DeploypodError` with `status_code` and `detail`.
94
+ Subclasses: `AuthenticationError` (401/403), `NotFoundError`,
95
+ `ValidationError`, `ConflictError`, `LicenseError`, `ServerError`.
96
+
97
+ ## Documentation
98
+
99
+ Full guides, the REST and WebSocket APIs, and the chat widget:
100
+ <https://docs.deploypod.ai/en/>
101
+
102
+ ## License
103
+
104
+ MIT
@@ -0,0 +1,79 @@
1
+ # Deploypod Python SDK
2
+
3
+ The Python client for the [Deploypod](https://deploypod.ai) API. Run your AI
4
+ agents from code, and manage their instructions, tools and knowledge bases.
5
+
6
+ ```bash
7
+ pip install deploypod
8
+ ```
9
+
10
+ Python 3.9 or newer.
11
+
12
+ ## Sign in
13
+
14
+ Ask your Deploypod administrator for an application (`app_id`, `app_secret`)
15
+ and your organization's number. The client gets the access token itself and
16
+ renews it before it expires.
17
+
18
+ ```python
19
+ from deploypod import DeploypodClient
20
+
21
+ client = DeploypodClient(
22
+ org_id="42",
23
+ app_id="YOUR_APP_ID",
24
+ app_secret="YOUR_APP_SECRET",
25
+ )
26
+ ```
27
+
28
+ Keep `app_secret` out of your code: read it from the environment.
29
+
30
+ ```bash
31
+ export DEPLOYPOD_ORG_ID=42
32
+ export DEPLOYPOD_APP_ID=...
33
+ export DEPLOYPOD_APP_SECRET=...
34
+ ```
35
+
36
+ ```python
37
+ from deploypod import get_client_from_env
38
+
39
+ client = get_client_from_env()
40
+ ```
41
+
42
+ Already have an access token? Pass `token="..."` instead of the app
43
+ credentials. For an on-premises installation, pass
44
+ `base_url="https://your-host/api"`.
45
+
46
+ ## Run an agent
47
+
48
+ ```python
49
+ for agent in client.agents.list():
50
+ print(agent.uuid, agent.name)
51
+
52
+ result = client.agents.run(
53
+ "AGENT_UUID",
54
+ "How many days of annual leave do I have?",
55
+ run_exploration=True,
56
+ timeout=300,
57
+ )
58
+ print(result["result_summary"])
59
+ ```
60
+
61
+ `run` waits for the answer. Other request fields go in as keyword arguments,
62
+ for example `output_schema={...}` to get the answer as JSON in
63
+ `result["final_emit"]`. Requests through `deploypod.ai` are cut after 100
64
+ seconds; for longer work (web research, presentations) use the WebSocket API.
65
+
66
+ ## Errors
67
+
68
+ Every error is a `deploypod.DeploypodError` with `status_code` and `detail`.
69
+ Subclasses: `AuthenticationError` (401/403), `NotFoundError`,
70
+ `ValidationError`, `ConflictError`, `LicenseError`, `ServerError`.
71
+
72
+ ## Documentation
73
+
74
+ Full guides, the REST and WebSocket APIs, and the chat widget:
75
+ <https://docs.deploypod.ai/en/>
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "deploypod"
7
+ version = "0.1.0"
8
+ description = "Python client for the Deploypod API: run AI agents, manage their tools and knowledge bases."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Deploypod" }]
13
+ requires-python = ">=3.9"
14
+ dependencies = [
15
+ "httpx>=0.25.0",
16
+ "pydantic>=2.0",
17
+ ]
18
+ keywords = ["deploypod", "ai", "agents", "llm", "sdk"]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://deploypod.ai"
30
+ Documentation = "https://docs.deploypod.ai/en/gelistirici/sdk-ve-widget/"
31
+
32
+ [project.optional-dependencies]
33
+ dev = [
34
+ "pytest>=7.0",
35
+ "pytest-httpx>=0.30.0",
36
+ ]
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["src"]
40
+
41
+ [tool.setuptools.package-data]
42
+ deploypod = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ __version__ = "0.1.0"
2
+
3
+ from deploypod.auth import AppCredentials
4
+ from deploypod.client import DeployPodClient, DeploypodClient
5
+ from deploypod.env import get_client_from_env
6
+ from deploypod.exceptions import (
7
+ DeploypodError,
8
+ DeployPodError,
9
+ AuthenticationError,
10
+ NotFoundError,
11
+ ValidationError,
12
+ ConflictError,
13
+ LicenseError,
14
+ ServerError,
15
+ )
16
+
17
+ __all__ = [
18
+ "__version__",
19
+ "AppCredentials",
20
+ "DeploypodClient",
21
+ "DeployPodClient",
22
+ "get_client_from_env",
23
+ "DeploypodError",
24
+ "DeployPodError",
25
+ "AuthenticationError",
26
+ "NotFoundError",
27
+ "ValidationError",
28
+ "ConflictError",
29
+ "LicenseError",
30
+ "ServerError",
31
+ ]
@@ -0,0 +1,73 @@
1
+ """Shared HTTP utilities — imported by both client.py and resource modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ from deploypod.exceptions import (
10
+ AuthenticationError,
11
+ ConflictError,
12
+ DeploypodError,
13
+ LicenseError,
14
+ NotFoundError,
15
+ ServerError,
16
+ ValidationError,
17
+ )
18
+
19
+
20
+ _STATUS_MAP: dict[int, type[DeploypodError]] = {
21
+ 401: AuthenticationError,
22
+ 403: AuthenticationError,
23
+ 404: NotFoundError,
24
+ 409: ConflictError,
25
+ 422: ValidationError,
26
+ }
27
+
28
+
29
+ def raise_for_status(resp: httpx.Response) -> None:
30
+ """Raise a typed DeploypodError for non-2xx responses."""
31
+ if resp.is_success:
32
+ return
33
+
34
+ try:
35
+ body = resp.json()
36
+ except Exception:
37
+ body = {"detail": resp.text}
38
+
39
+ detail = body.get("detail") or body.get("message") or resp.text
40
+
41
+ # License-specific 403
42
+ if resp.status_code == 403 and "license" in str(detail).lower():
43
+ raise LicenseError(resp.status_code, detail, body)
44
+
45
+ exc_cls = _STATUS_MAP.get(resp.status_code)
46
+ if exc_cls:
47
+ raise exc_cls(resp.status_code, detail, body)
48
+
49
+ if resp.status_code >= 500:
50
+ raise ServerError(resp.status_code, detail, body)
51
+
52
+ raise DeploypodError(resp.status_code, detail, body)
53
+
54
+
55
+ def json_body(resp: httpx.Response) -> Any:
56
+ """The response's JSON, or {} when it carries none.
57
+
58
+ Several endpoints answer a successful delete or assign with 204 and no
59
+ body; parsing that as JSON raised JSONDecodeError after the server had
60
+ already done the work.
61
+ """
62
+ if resp.status_code == 204 or not resp.content:
63
+ return {}
64
+ return resp.json()
65
+
66
+
67
+ def message_result(resp: httpx.Response) -> "MessageResult":
68
+ """A {message: ...} response as MessageResult; empty for a 204."""
69
+ from deploypod.types import MessageResult
70
+
71
+ body = json_body(resp)
72
+ message = body.get("message", "") if isinstance(body, dict) else ""
73
+ return MessageResult(message=message)
@@ -0,0 +1,90 @@
1
+ """Signing in with an application's own credentials (machine to machine).
2
+
3
+ An administrator gives an application an ``app_id`` and an ``app_secret``.
4
+ The application trades them for an access token at Deploypod's identity
5
+ provider; the token lasts an hour. ``AppCredentials`` does the trade, keeps
6
+ the token, and fetches a new one shortly before it expires or when the API
7
+ answers 401.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import threading
13
+ import time
14
+ from typing import Generator, Optional
15
+
16
+ import httpx
17
+
18
+ from deploypod.exceptions import AuthenticationError
19
+
20
+ TOKEN_URL = "https://auth.deploypod.ai/oidc/token"
21
+ API_RESOURCE = "https://api.deploypod.io"
22
+
23
+ # A token is replaced this many seconds before it expires, so a request
24
+ # never leaves with one that runs out on the way.
25
+ _EARLY_REFRESH_S = 60
26
+
27
+
28
+ class AppCredentials(httpx.Auth):
29
+ """httpx auth that sends a bearer token obtained with client credentials."""
30
+
31
+ # The request may be sent twice (see auth_flow), so httpx must keep its
32
+ # body, file uploads included, instead of streaming it once.
33
+ requires_request_body = True
34
+
35
+ def __init__(
36
+ self,
37
+ app_id: str,
38
+ app_secret: str,
39
+ *,
40
+ token_url: str = TOKEN_URL,
41
+ resource: str = API_RESOURCE,
42
+ transport: Optional[httpx.BaseTransport] = None,
43
+ headers: Optional[dict[str, str]] = None,
44
+ ):
45
+ self._app_id = app_id
46
+ self._app_secret = app_secret
47
+ self._token_url = token_url
48
+ self._resource = resource
49
+ self._transport = transport
50
+ self._headers = headers or {}
51
+ self._token: Optional[str] = None
52
+ self._expires_at = 0.0
53
+ self._lock = threading.Lock()
54
+
55
+ def _fetch(self) -> None:
56
+ with httpx.Client(transport=self._transport, headers=self._headers, timeout=30.0) as c:
57
+ resp = c.post(
58
+ self._token_url,
59
+ auth=(self._app_id, self._app_secret),
60
+ data={
61
+ "grant_type": "client_credentials",
62
+ "resource": self._resource,
63
+ "scope": "all",
64
+ },
65
+ )
66
+ if not resp.is_success:
67
+ raise AuthenticationError(
68
+ resp.status_code,
69
+ f"could not get an access token with app_id {self._app_id!r}: {resp.text}",
70
+ None,
71
+ )
72
+ body = resp.json()
73
+ self._token = body["access_token"]
74
+ self._expires_at = time.monotonic() + float(body.get("expires_in", 3600))
75
+
76
+ def token(self, *, renew: bool = False) -> str:
77
+ """The current access token, fetched or renewed when needed."""
78
+ with self._lock:
79
+ if renew or self._token is None or time.monotonic() >= self._expires_at - _EARLY_REFRESH_S:
80
+ self._fetch()
81
+ assert self._token is not None
82
+ return self._token
83
+
84
+ def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
85
+ request.headers["Authorization"] = f"Bearer {self.token()}"
86
+ response = yield request
87
+ if response.status_code == 401:
88
+ # Revoked or rotated early: one fresh token, one retry.
89
+ request.headers["Authorization"] = f"Bearer {self.token(renew=True)}"
90
+ yield request
@@ -0,0 +1,122 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from deploypod import __version__
8
+ from deploypod.auth import AppCredentials
9
+ from deploypod.resources.agents import AgentsResource
10
+ from deploypod.resources.knowledge_bases import KnowledgeBasesResource
11
+ from deploypod.resources.knowledge_sources import KnowledgeSourcesResource
12
+ from deploypod.resources.skills import SkillsResource
13
+ from deploypod.resources.custom_tools import CustomToolsResource
14
+ from deploypod.resources.mcp_servers import MCPServersResource
15
+ from deploypod.resources.providers import ProvidersResource
16
+ from deploypod.resources.connections import ConnectionsResource
17
+ from deploypod.resources.conversations import ConversationsResource
18
+ from deploypod.resources.users import UsersResource
19
+ from deploypod.resources.groups import GroupsResource
20
+ from deploypod.resources.organization import OrganizationResource
21
+ from deploypod.resources.async_tasks import AsyncTasksResource
22
+ from deploypod.resources.jobs import JobsResource
23
+
24
+
25
+ DEFAULT_BASE_URL = "https://deploypod.ai/api"
26
+
27
+
28
+ class DeploypodClient:
29
+ """Synchronous Python client for the Deploypod API.
30
+
31
+ Sign in with your application's credentials (an administrator creates
32
+ them; the client gets and renews the access token itself)::
33
+
34
+ from deploypod import DeploypodClient
35
+
36
+ client = DeploypodClient(org_id="42", app_id="...", app_secret="...")
37
+ for agent in client.agents.list():
38
+ print(agent.uuid, agent.name)
39
+
40
+ or with an access token you already have::
41
+
42
+ client = DeploypodClient(org_id="42", token="<access token>")
43
+
44
+ ``base_url`` defaults to Deploypod's cloud; pass your own for an
45
+ on-premises installation.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ base_url: str = DEFAULT_BASE_URL,
51
+ org_id: str | None = None,
52
+ token: str | None = None,
53
+ *,
54
+ app_id: str | None = None,
55
+ app_secret: str | None = None,
56
+ token_url: str | None = None,
57
+ timeout: float = 30.0,
58
+ extra_headers: dict[str, str] | None = None,
59
+ transport: httpx.BaseTransport | None = None,
60
+ ):
61
+ if org_id is None or str(org_id) == "":
62
+ raise ValueError("org_id is required: your organization's number in Deploypod")
63
+ if token and (app_id or app_secret):
64
+ raise ValueError("pass either token, or app_id and app_secret, not both")
65
+ if not token and not (app_id and app_secret):
66
+ raise ValueError("pass token, or app_id and app_secret")
67
+
68
+ # Cloudflare in front of deploypod.ai refuses requests that carry
69
+ # no User-Agent; name the SDK so they always have one.
70
+ headers: dict[str, str] = {
71
+ "org_id": str(org_id),
72
+ "User-Agent": f"deploypod-python/{__version__}",
73
+ }
74
+ auth: httpx.Auth | None = None
75
+ if token:
76
+ headers["Authorization"] = f"Bearer {token}"
77
+ else:
78
+ kw = {"token_url": token_url} if token_url else {}
79
+ auth = AppCredentials(
80
+ app_id, app_secret, transport=transport,
81
+ headers={"User-Agent": headers["User-Agent"]}, **kw,
82
+ )
83
+ if extra_headers:
84
+ headers.update(extra_headers)
85
+
86
+ self._http = httpx.Client(
87
+ base_url=base_url,
88
+ headers=headers,
89
+ auth=auth,
90
+ timeout=timeout,
91
+ transport=transport,
92
+ )
93
+
94
+ # Resource namespaces
95
+ self.agents = AgentsResource(self._http)
96
+ self.knowledge_bases = KnowledgeBasesResource(self._http)
97
+ self.knowledge_sources = KnowledgeSourcesResource(self._http)
98
+ self.skills = SkillsResource(self._http)
99
+ self.custom_tools = CustomToolsResource(self._http)
100
+ self.mcp_servers = MCPServersResource(self._http)
101
+ self.providers = ProvidersResource(self._http)
102
+ self.connections = ConnectionsResource(self._http)
103
+ self.conversations = ConversationsResource(self._http)
104
+ self.users = UsersResource(self._http)
105
+ self.groups = GroupsResource(self._http)
106
+ self.organization = OrganizationResource(self._http)
107
+ self.async_tasks = AsyncTasksResource(self._http)
108
+ self.jobs = JobsResource(self._http)
109
+
110
+ def close(self) -> None:
111
+ self._http.close()
112
+
113
+ def __enter__(self) -> DeploypodClient:
114
+ return self
115
+
116
+ def __exit__(self, *args: Any) -> None:
117
+ self.close()
118
+
119
+
120
+ # The name before the package went public. Kept so existing code runs
121
+ # unchanged; new code uses DeploypodClient.
122
+ DeployPodClient = DeploypodClient
@@ -0,0 +1,49 @@
1
+ """A client built from environment variables, for scripts."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import subprocess
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from deploypod.client import DEFAULT_BASE_URL, DeploypodClient
10
+
11
+
12
+ def get_client_from_env(
13
+ deploypod_repo: Optional[Path] = None,
14
+ ) -> DeploypodClient:
15
+ """Build a DeploypodClient from environment variables.
16
+
17
+ DEPLOYPOD_ORG_ID — your organization's number. Required.
18
+ DEPLOYPOD_APP_ID,
19
+ DEPLOYPOD_APP_SECRET — your application's credentials; the client gets
20
+ and renews the access token itself.
21
+ DEPLOYPOD_TOKEN — or an access token you already have. If neither
22
+ is set and ``deploypod_repo`` is given, the token
23
+ comes from ``<deploypod_repo>/scripts/get_token.sh``
24
+ (Deploypod's own development scripts).
25
+ DEPLOYPOD_BASE_URL — API address, default https://deploypod.ai/api.
26
+
27
+ Raises RuntimeError if the organization or the credentials are missing.
28
+ """
29
+ org_id = os.environ.get("DEPLOYPOD_ORG_ID")
30
+ if not org_id:
31
+ raise RuntimeError("DEPLOYPOD_ORG_ID is not set: export your organization's number.")
32
+ base_url = os.environ.get("DEPLOYPOD_BASE_URL", DEFAULT_BASE_URL)
33
+
34
+ app_id = os.environ.get("DEPLOYPOD_APP_ID")
35
+ app_secret = os.environ.get("DEPLOYPOD_APP_SECRET")
36
+ if app_id and app_secret:
37
+ return DeploypodClient(base_url, org_id, app_id=app_id, app_secret=app_secret)
38
+
39
+ token = os.environ.get("DEPLOYPOD_TOKEN")
40
+ if not token and deploypod_repo is not None:
41
+ script = Path(deploypod_repo) / "scripts" / "get_token.sh"
42
+ if script.exists():
43
+ token = subprocess.check_output([str(script)], text=True).strip()
44
+ if not token:
45
+ raise RuntimeError(
46
+ "No credentials: export DEPLOYPOD_APP_ID and DEPLOYPOD_APP_SECRET, "
47
+ "or DEPLOYPOD_TOKEN."
48
+ )
49
+ return DeploypodClient(base_url, org_id, token)
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class DeploypodError(Exception):
5
+ """Base exception for all Deploypod SDK errors."""
6
+
7
+ def __init__(self, status_code: int, detail: str, response_body: dict | None = None):
8
+ self.status_code = status_code
9
+ self.detail = detail
10
+ self.response_body = response_body
11
+ super().__init__(f"[{status_code}] {detail}")
12
+
13
+
14
+ # The name before the package went public, kept for existing code.
15
+ DeployPodError = DeploypodError
16
+
17
+
18
+ class AuthenticationError(DeploypodError):
19
+ """401/403 — invalid or missing credentials."""
20
+
21
+
22
+ class NotFoundError(DeploypodError):
23
+ """404 — resource does not exist."""
24
+
25
+
26
+ class ValidationError(DeploypodError):
27
+ """422 — request body failed validation."""
28
+
29
+
30
+ class ConflictError(DeploypodError):
31
+ """409 — resource conflict (e.g. duplicate slug)."""
32
+
33
+
34
+ class LicenseError(DeploypodError):
35
+ """403 — license limit exceeded or feature not available."""
36
+
37
+
38
+ class ServerError(DeploypodError):
39
+ """5xx — unexpected server error."""
File without changes
File without changes