phala-cloud 0.1.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.
@@ -0,0 +1 @@
1
+ .env.test
@@ -0,0 +1,23 @@
1
+ PYTHONPATH ?= src
2
+ PYTEST ?= python -m pytest
3
+
4
+ .PHONY: test e2e lint fmt check
5
+
6
+ lint:
7
+ ruff check .
8
+
9
+ fmt:
10
+ ruff format .
11
+
12
+ check: fmt lint test
13
+
14
+ test:
15
+ PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=$(PYTHONPATH) $(PYTEST) -q -p no:cacheprovider
16
+
17
+ e2e:
18
+ @bash -lc 'set -a; [ -f .env.test ] && source .env.test; set +a; \
19
+ if [ -z "$$PHALA_CLOUD_E2E_BASE_URL" ] || [ -z "$$PHALA_CLOUD_E2E_API_KEY" ]; then \
20
+ echo "Missing env: PHALA_CLOUD_E2E_BASE_URL / PHALA_CLOUD_E2E_API_KEY"; \
21
+ exit 1; \
22
+ fi; \
23
+ PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=$(PYTHONPATH) $(PYTEST) -m e2e -q'
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.4
2
+ Name: phala-cloud
3
+ Version: 0.1.1
4
+ Summary: Python SDK for Phala Cloud API (sync/async, pydantic, REPL-friendly)
5
+ Project-URL: Homepage, https://github.com/Phala-Network/phala-cloud
6
+ Project-URL: Repository, https://github.com/Phala-Network/phala-cloud
7
+ Project-URL: Issues, https://github.com/Phala-Network/phala-cloud/issues
8
+ Author: Phala Network
9
+ License: Apache-2.0
10
+ Keywords: cloud,cvm,phala,sdk,tee
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: dstack-sdk>=0.1.0
22
+ Requires-Dist: httpx>=0.27.0
23
+ Requires-Dist: pydantic>=2.7.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.10.0; extra == 'dev'
26
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Phala Cloud Python SDK
32
+
33
+ Python SDK for Phala Cloud API, aligned with `@phala/cloud` action surface.
34
+
35
+ ## Goals
36
+
37
+ - Pythonic API (`snake_case`, sync-first REPL experience)
38
+ - Sync + Async parity
39
+ - Pydantic request/response validation
40
+ - Safe calls (`safe_*`) without exceptions
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install phala-cloud
46
+ ```
47
+
48
+ ## Quickstart
49
+
50
+ ### Sync
51
+
52
+ ```python
53
+ from phala_cloud import create_client
54
+
55
+ client = create_client(api_key="<api-key>")
56
+ me = client.get_current_user()
57
+ print(me.model_dump())
58
+ ```
59
+
60
+ ### Async
61
+
62
+ ```python
63
+ import asyncio
64
+ from phala_cloud import create_async_client
65
+
66
+
67
+ async def main() -> None:
68
+ client = create_async_client(api_key="<api-key>")
69
+ me = await client.get_current_user()
70
+ print(me.model_dump())
71
+ await client.aclose()
72
+
73
+
74
+ asyncio.run(main())
75
+ ```
76
+
77
+ ## Configuration
78
+
79
+ ### Environment variables
80
+
81
+ - `PHALA_CLOUD_API_KEY`
82
+ - `PHALA_CLOUD_API_PREFIX` (e.g. `https://cloud-api.phala.com/api/v1`)
83
+
84
+ ### Client options
85
+
86
+ - `api_key`
87
+ - `base_url`
88
+ - `version` (`2025-10-28` / `2026-01-21`)
89
+ - `timeout`
90
+ - `headers`
91
+ - `use_cookie_auth`
92
+
93
+ ## API Style
94
+
95
+ - Direct methods: raise on HTTP/validation errors
96
+ - Safe methods (`safe_*`): return `SafeResult`
97
+ - Function-style wrappers available in `phala_cloud.actions`
98
+
99
+ Example:
100
+
101
+ ```python
102
+ result = client.safe_get_kms_list()
103
+ if result.ok:
104
+ print(result.data.items)
105
+ else:
106
+ print(result.error)
107
+ ```
108
+
109
+ ## E2E Test (full interface matrix)
110
+
111
+ E2E only needs two variables:
112
+
113
+ - `PHALA_CLOUD_E2E_BASE_URL`
114
+ - `PHALA_CLOUD_E2E_API_KEY`
115
+
116
+ It covers sync + async, direct + safe styles, and full interface paths.
117
+
118
+ ### Run
119
+
120
+ ```bash
121
+ cd python
122
+ PHALA_CLOUD_E2E_BASE_URL="https://<your-test-api>/api/v1" \
123
+ PHALA_CLOUD_E2E_API_KEY="<api-key>" \
124
+ make e2e
125
+ ```
126
+
127
+ Or put them in `python/.env.test`:
128
+
129
+ ```dotenv
130
+ PHALA_CLOUD_E2E_BASE_URL=https://<your-test-api>/api/v1
131
+ PHALA_CLOUD_E2E_API_KEY=<api-key>
132
+ ```
133
+
134
+ Then simply:
135
+
136
+ ```bash
137
+ cd python
138
+ make e2e
139
+ ```
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ cd python
145
+ make test
146
+ ```
147
+
148
+ ## License
149
+
150
+ Apache-2.0
@@ -0,0 +1,120 @@
1
+ # Phala Cloud Python SDK
2
+
3
+ Python SDK for Phala Cloud API, aligned with `@phala/cloud` action surface.
4
+
5
+ ## Goals
6
+
7
+ - Pythonic API (`snake_case`, sync-first REPL experience)
8
+ - Sync + Async parity
9
+ - Pydantic request/response validation
10
+ - Safe calls (`safe_*`) without exceptions
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pip install phala-cloud
16
+ ```
17
+
18
+ ## Quickstart
19
+
20
+ ### Sync
21
+
22
+ ```python
23
+ from phala_cloud import create_client
24
+
25
+ client = create_client(api_key="<api-key>")
26
+ me = client.get_current_user()
27
+ print(me.model_dump())
28
+ ```
29
+
30
+ ### Async
31
+
32
+ ```python
33
+ import asyncio
34
+ from phala_cloud import create_async_client
35
+
36
+
37
+ async def main() -> None:
38
+ client = create_async_client(api_key="<api-key>")
39
+ me = await client.get_current_user()
40
+ print(me.model_dump())
41
+ await client.aclose()
42
+
43
+
44
+ asyncio.run(main())
45
+ ```
46
+
47
+ ## Configuration
48
+
49
+ ### Environment variables
50
+
51
+ - `PHALA_CLOUD_API_KEY`
52
+ - `PHALA_CLOUD_API_PREFIX` (e.g. `https://cloud-api.phala.com/api/v1`)
53
+
54
+ ### Client options
55
+
56
+ - `api_key`
57
+ - `base_url`
58
+ - `version` (`2025-10-28` / `2026-01-21`)
59
+ - `timeout`
60
+ - `headers`
61
+ - `use_cookie_auth`
62
+
63
+ ## API Style
64
+
65
+ - Direct methods: raise on HTTP/validation errors
66
+ - Safe methods (`safe_*`): return `SafeResult`
67
+ - Function-style wrappers available in `phala_cloud.actions`
68
+
69
+ Example:
70
+
71
+ ```python
72
+ result = client.safe_get_kms_list()
73
+ if result.ok:
74
+ print(result.data.items)
75
+ else:
76
+ print(result.error)
77
+ ```
78
+
79
+ ## E2E Test (full interface matrix)
80
+
81
+ E2E only needs two variables:
82
+
83
+ - `PHALA_CLOUD_E2E_BASE_URL`
84
+ - `PHALA_CLOUD_E2E_API_KEY`
85
+
86
+ It covers sync + async, direct + safe styles, and full interface paths.
87
+
88
+ ### Run
89
+
90
+ ```bash
91
+ cd python
92
+ PHALA_CLOUD_E2E_BASE_URL="https://<your-test-api>/api/v1" \
93
+ PHALA_CLOUD_E2E_API_KEY="<api-key>" \
94
+ make e2e
95
+ ```
96
+
97
+ Or put them in `python/.env.test`:
98
+
99
+ ```dotenv
100
+ PHALA_CLOUD_E2E_BASE_URL=https://<your-test-api>/api/v1
101
+ PHALA_CLOUD_E2E_API_KEY=<api-key>
102
+ ```
103
+
104
+ Then simply:
105
+
106
+ ```bash
107
+ cd python
108
+ make e2e
109
+ ```
110
+
111
+ ## Development
112
+
113
+ ```bash
114
+ cd python
115
+ make test
116
+ ```
117
+
118
+ ## License
119
+
120
+ Apache-2.0
@@ -0,0 +1,14 @@
1
+ from phala_cloud import create_client
2
+
3
+ client = create_client()
4
+
5
+ me = client.safe_get_current_user()
6
+ if me.ok:
7
+ print("Current user:")
8
+ print(me.data)
9
+ else:
10
+ print(f"Error: {me.error}")
11
+
12
+ nodes = client.safe_get_available_nodes()
13
+ if nodes.ok and nodes.data:
14
+ print(f"Available nodes: {len(nodes.data.nodes)}")
@@ -0,0 +1,58 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "phala-cloud"
7
+ version = "0.1.1"
8
+ description = "Python SDK for Phala Cloud API (sync/async, pydantic, REPL-friendly)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "Phala Network" }]
13
+ keywords = ["phala", "cloud", "sdk", "tee", "cvm"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: Apache Software License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "httpx>=0.27.0",
27
+ "pydantic>=2.7.0",
28
+ "dstack-sdk>=0.1.0",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8.0.0",
34
+ "pytest-asyncio>=0.23.0",
35
+ "ruff>=0.6.0",
36
+ "mypy>=1.10.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/Phala-Network/phala-cloud"
41
+ Repository = "https://github.com/Phala-Network/phala-cloud"
42
+ Issues = "https://github.com/Phala-Network/phala-cloud/issues"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/phala_cloud"]
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+
50
+ [tool.ruff.lint]
51
+ select = ["E", "F", "I", "UP", "B"]
52
+
53
+ [tool.pytest.ini_options]
54
+ asyncio_mode = "auto"
55
+ asyncio_default_fixture_loop_scope = "function"
56
+ markers = [
57
+ "e2e: end-to-end tests against real API base URL",
58
+ ]
@@ -0,0 +1,36 @@
1
+ from .actions import * # noqa: F403
2
+ from .blockchains import add_compose_hash, deploy_app_auth
3
+ from .client import DEFAULT_API_VERSION, SUPPORTED_API_VERSIONS, ApiVersion
4
+ from .errors import ApiError, PhalaCloudError, RequestError, ValidationError
5
+ from .full_client import AsyncPhalaCloud, PhalaCloud, create_async_client, create_client
6
+ from .models import * # noqa: F403
7
+ from .result import SafeResult
8
+ from .utils import (
9
+ encrypt_env_vars,
10
+ get_compose_hash,
11
+ parse_env,
12
+ parse_env_vars,
13
+ verify_env_encrypt_public_key,
14
+ )
15
+
16
+ __all__ = [
17
+ "ApiError",
18
+ "ApiVersion",
19
+ "AsyncPhalaCloud",
20
+ "DEFAULT_API_VERSION",
21
+ "PhalaCloud",
22
+ "PhalaCloudError",
23
+ "RequestError",
24
+ "SUPPORTED_API_VERSIONS",
25
+ "SafeResult",
26
+ "ValidationError",
27
+ "add_compose_hash",
28
+ "deploy_app_auth",
29
+ "encrypt_env_vars",
30
+ "get_compose_hash",
31
+ "parse_env",
32
+ "parse_env_vars",
33
+ "verify_env_encrypt_public_key",
34
+ "create_async_client",
35
+ "create_client",
36
+ ]
@@ -0,0 +1,222 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import Field
4
+
5
+ from .models.auth import CurrentUserV20251028, CurrentUserV20260121
6
+ from .models.base import CloudModel
7
+ from .models.cvms import PaginatedCvmInfosV20251028, PaginatedCvmInfosV20260121
8
+
9
+
10
+ class GenericObject(CloudModel):
11
+ pass
12
+
13
+
14
+ class WorkspaceResponse(CloudModel):
15
+ id: str
16
+ name: str
17
+ slug: str | None = None
18
+ tier: str | None = None
19
+ role: str | None = None
20
+
21
+
22
+ class ListWorkspacesResponse(CloudModel):
23
+ data: list[WorkspaceResponse] = Field(default_factory=list)
24
+ pagination: GenericObject | None = None
25
+
26
+
27
+ class InstanceTypesAllResponse(CloudModel):
28
+ result: list[GenericObject] = Field(default_factory=list)
29
+
30
+
31
+ class InstanceTypesFamilyResponse(CloudModel):
32
+ items: list[GenericObject] = Field(default_factory=list)
33
+ total: int | None = None
34
+ family: str | None = None
35
+
36
+
37
+ class ProvisionCvmResponse(CloudModel):
38
+ app_id: str | None = None
39
+ app_env_encrypt_pubkey: str | None = None
40
+ compose_hash: str
41
+ kms_info: GenericObject | None = None
42
+ fmspc: str | None = None
43
+ device_id: str | None = None
44
+ os_image_hash: str | None = None
45
+ instance_type: str | None = None
46
+ node_id: int | None = None
47
+ kms_id: str | None = None
48
+
49
+
50
+ class CommitCvmProvisionResponse(CloudModel):
51
+ id: int | str
52
+ name: str
53
+ status: str
54
+
55
+
56
+ class ComposeHashPreconditionResponse(CloudModel):
57
+ status: str = "precondition_required"
58
+ message: str
59
+ compose_hash: str
60
+ app_id: str
61
+ device_id: str
62
+ kms_info: GenericObject | None = None
63
+
64
+
65
+ class InProgressResponse(CloudModel):
66
+ status: str = "in_progress"
67
+ message: str | None = None
68
+ correlation_id: str | None = None
69
+
70
+
71
+ class CvmStateResponse(CloudModel):
72
+ id: str | None = None
73
+ instance_id: str | None = None
74
+ name: str | None = None
75
+ status: str
76
+
77
+
78
+ class AppFilterOptionsResponse(CloudModel):
79
+ statuses: list[str] = Field(default_factory=list)
80
+ image_versions: list[str] = Field(default_factory=list)
81
+ instance_types: list[str] = Field(default_factory=list)
82
+ kms_slugs: list[str] = Field(default_factory=list)
83
+ kms_types: list[str] = Field(default_factory=list)
84
+ regions: list[str] = Field(default_factory=list)
85
+ nodes: list[str] = Field(default_factory=list)
86
+
87
+
88
+ class SshKeyResponse(CloudModel):
89
+ id: str
90
+ name: str
91
+ public_key: str
92
+
93
+
94
+ class ImportGithubProfileResponse(CloudModel):
95
+ github_username: str
96
+ keys_added: int
97
+ keys_skipped: int
98
+ errors: list[str] = Field(default_factory=list)
99
+
100
+
101
+ class SyncGithubSshKeysResponse(CloudModel):
102
+ synced_count: int
103
+ keys_added: int
104
+ keys_updated: int
105
+ keys_removed: int
106
+ errors: list[str] = Field(default_factory=list)
107
+
108
+
109
+ class AppEnvPubkeyResponse(CloudModel):
110
+ public_key: str
111
+ signature: str
112
+
113
+
114
+ class NextAppIdsResponse(CloudModel):
115
+ app_ids: list[GenericObject] = Field(default_factory=list)
116
+
117
+
118
+ class WorkspaceNodesResponse(CloudModel):
119
+ items: list[GenericObject] = Field(default_factory=list)
120
+ total: int | None = None
121
+ page: int | None = None
122
+ page_size: int | None = None
123
+ pages: int | None = None
124
+
125
+
126
+ class WorkspaceQuotasResponse(CloudModel):
127
+ team_slug: str | None = None
128
+ tier: str | None = None
129
+ quotas: GenericObject | None = None
130
+ reserved_nodes: GenericObject | None = None
131
+ reserved_gpu: GenericObject | None = None
132
+ as_of: str | None = None
133
+
134
+
135
+ class CvmInfoResponse(CloudModel):
136
+ id: str | int | None = None
137
+ name: str | None = None
138
+ status: str | None = None
139
+
140
+
141
+ class ComposeFileResponse(CloudModel):
142
+ docker_compose_file: str | None = None
143
+
144
+
145
+ class CvmActionResponse(CloudModel):
146
+ id: int | str | None = None
147
+ name: str | None = None
148
+ status: str | None = None
149
+
150
+
151
+ class CvmStatsResponse(CloudModel):
152
+ is_online: bool | None = None
153
+ status: str | None = None
154
+
155
+
156
+ class CvmNetworkResponse(CloudModel):
157
+ is_online: bool | None = None
158
+
159
+
160
+ class CvmContainersResponse(CloudModel):
161
+ is_online: bool | None = None
162
+
163
+
164
+ class CvmAttestationResponse(CloudModel):
165
+ is_online: bool | None = None
166
+
167
+
168
+ class CvmVisibilityResponse(CloudModel):
169
+ status: str | None = None
170
+
171
+
172
+ class CvmUserConfigResponse(CloudModel):
173
+ hostname: str | None = None
174
+ ssh_authorized_keys: list[str] = Field(default_factory=list)
175
+
176
+
177
+ class RefreshInstanceIdResponse(CloudModel):
178
+ status: str | None = None
179
+
180
+
181
+ class RefreshInstanceIdsResponse(CloudModel):
182
+ total: int
183
+ scanned: int
184
+ updated: int
185
+ unchanged: int
186
+ skipped: int
187
+ conflicts: int
188
+ errors: int
189
+ items: list[GenericObject] = Field(default_factory=list)
190
+
191
+
192
+ class AppListResponse(CloudModel):
193
+ items: list[GenericObject] = Field(default_factory=list)
194
+ total: int | None = None
195
+ page: int | None = None
196
+ page_size: int | None = None
197
+ pages: int | None = None
198
+
199
+
200
+ class AppInfoResponse(CloudModel):
201
+ id: str | None = None
202
+ name: str | None = None
203
+
204
+
205
+ class AppRevisionsResponse(CloudModel):
206
+ items: list[GenericObject] = Field(default_factory=list)
207
+ total: int | None = None
208
+ page: int | None = None
209
+ page_size: int | None = None
210
+ pages: int | None = None
211
+
212
+
213
+ class AppRevisionDetailResponse(CloudModel):
214
+ id: str | None = None
215
+
216
+
217
+ class AppAttestationResponse(CloudModel):
218
+ instances: list[GenericObject] = Field(default_factory=list)
219
+
220
+
221
+ CurrentUserResponse = CurrentUserV20260121 | CurrentUserV20251028
222
+ PaginatedCvmListResponse = PaginatedCvmInfosV20260121 | PaginatedCvmInfosV20251028