stackmachine 0.3.0__tar.gz → 0.3.2__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 (34) hide show
  1. {stackmachine-0.3.0 → stackmachine-0.3.2}/PKG-INFO +19 -21
  2. {stackmachine-0.3.0 → stackmachine-0.3.2}/README.md +17 -20
  3. {stackmachine-0.3.0 → stackmachine-0.3.2}/pyproject.toml +14 -1
  4. stackmachine-0.3.2/src/stackmachine/__init__.py +144 -0
  5. stackmachine-0.3.2/src/stackmachine/_async_client.py +198 -0
  6. stackmachine-0.3.2/src/stackmachine/_client.py +196 -0
  7. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_config.py +4 -0
  8. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_pagination.py +2 -1
  9. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_transport.py +63 -35
  10. stackmachine-0.3.2/src/stackmachine/_types.py +268 -0
  11. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_uploads.py +9 -8
  12. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_utils.py +7 -5
  13. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/_shared.py +3 -2
  14. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/apps.py +39 -26
  15. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/deployments.py +27 -20
  16. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/domains.py +19 -16
  17. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/files.py +8 -8
  18. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/ssh.py +57 -41
  19. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/versions.py +20 -12
  20. {stackmachine-0.3.0 → stackmachine-0.3.2}/tests/test_package.py +38 -6
  21. stackmachine-0.3.0/src/stackmachine/__init__.py +0 -72
  22. stackmachine-0.3.0/src/stackmachine/_async_client.py +0 -122
  23. stackmachine-0.3.0/src/stackmachine/_client.py +0 -120
  24. {stackmachine-0.3.0 → stackmachine-0.3.2}/.gitignore +0 -0
  25. {stackmachine-0.3.0 → stackmachine-0.3.2}/examples/async_usage.py +0 -0
  26. {stackmachine-0.3.0 → stackmachine-0.3.2}/examples/basic_usage.py +0 -0
  27. {stackmachine-0.3.0 → stackmachine-0.3.2}/examples/deploy_app.py +0 -0
  28. {stackmachine-0.3.0 → stackmachine-0.3.2}/examples/list_apps.py +0 -0
  29. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_errors.py +0 -0
  30. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_graphql/__init__.py +0 -0
  31. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_graphql/operations.py +0 -0
  32. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/_models.py +0 -0
  33. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/py.typed +0 -0
  34. {stackmachine-0.3.0 → stackmachine-0.3.2}/src/stackmachine/resources/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackmachine
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Python SDK for StackMachine.
5
5
  Project-URL: Homepage, https://github.com/stackmachine/sdks/tree/main/python
6
6
  Project-URL: Repository, https://github.com/stackmachine/sdks
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Typing :: Typed
16
16
  Requires-Python: >=3.9
17
17
  Requires-Dist: httpx<1,>=0.27
18
+ Requires-Dist: typing-extensions<5,>=4.10
18
19
  Requires-Dist: websocket-client<2,>=1.8
19
20
  Requires-Dist: websockets<16,>=12
20
21
  Description-Content-Type: text/markdown
@@ -60,15 +61,13 @@ stackmachine = StackMachine("sk_stackmachine_...")
60
61
  async_stackmachine = AsyncStackMachine("sk_stackmachine_...")
61
62
  ```
62
63
 
63
- Both clients accept JavaScript-style aliases during initialization:
64
+ Both clients accept configuration options during initialization:
64
65
 
65
66
  ```python
66
- stackmachine = StackMachine.init(
67
- {
68
- "token": "sk_stackmachine_...",
69
- "apiUrl": "https://api.stackmachine.com/graphql",
70
- "maxNetworkRetries": 2,
71
- }
67
+ stackmachine = StackMachine(
68
+ "sk_stackmachine_...",
69
+ apiUrl="https://api.stackmachine.com/graphql",
70
+ maxNetworkRetries=2,
72
71
  )
73
72
  ```
74
73
 
@@ -100,10 +99,8 @@ async for app in apps:
100
99
 
101
100
  ```python
102
101
  deployment = stackmachine.deployments.create(
103
- {
104
- "file": "https://example.com/app.zip",
105
- "name": "my-app",
106
- }
102
+ upload_url="https://example.com/app.zip",
103
+ app_name="my-app",
107
104
  )
108
105
 
109
106
  version = deployment.wait()
@@ -111,10 +108,8 @@ version = deployment.wait()
111
108
 
112
109
  ```python
113
110
  deployment = stackmachine.apps.autobuild(
114
- {
115
- "file": "https://example.com/app.zip",
116
- "name": "my-app",
117
- }
111
+ upload_url="https://example.com/app.zip",
112
+ app_name="my-app",
118
113
  )
119
114
  ```
120
115
 
@@ -161,13 +156,15 @@ key = stackmachine.apps.ssh.users.authorized_keys.create(
161
156
  Most methods accept `request_options` for per-request configuration:
162
157
 
163
158
  ```python
159
+ from stackmachine import RequestOptions
160
+
164
161
  app = stackmachine.apps.retrieve(
165
162
  "app_id",
166
- request_options={
167
- "api_key": "sk_stackmachine_other",
168
- "timeout": 30,
169
- "idempotency_key": "deploy-123",
170
- },
163
+ request_options=RequestOptions(
164
+ api_key="sk_stackmachine_other",
165
+ timeout=30,
166
+ idempotency_key="deploy-123",
167
+ ),
171
168
  )
172
169
  ```
173
170
 
@@ -177,6 +174,7 @@ app = stackmachine.apps.retrieve(
177
174
  cd python
178
175
  uv sync --dev
179
176
  uv run ruff check src examples tests
177
+ uv run mypy
180
178
  uv run pytest
181
179
  uv build --no-sources
182
180
  ```
@@ -39,15 +39,13 @@ stackmachine = StackMachine("sk_stackmachine_...")
39
39
  async_stackmachine = AsyncStackMachine("sk_stackmachine_...")
40
40
  ```
41
41
 
42
- Both clients accept JavaScript-style aliases during initialization:
42
+ Both clients accept configuration options during initialization:
43
43
 
44
44
  ```python
45
- stackmachine = StackMachine.init(
46
- {
47
- "token": "sk_stackmachine_...",
48
- "apiUrl": "https://api.stackmachine.com/graphql",
49
- "maxNetworkRetries": 2,
50
- }
45
+ stackmachine = StackMachine(
46
+ "sk_stackmachine_...",
47
+ apiUrl="https://api.stackmachine.com/graphql",
48
+ maxNetworkRetries=2,
51
49
  )
52
50
  ```
53
51
 
@@ -79,10 +77,8 @@ async for app in apps:
79
77
 
80
78
  ```python
81
79
  deployment = stackmachine.deployments.create(
82
- {
83
- "file": "https://example.com/app.zip",
84
- "name": "my-app",
85
- }
80
+ upload_url="https://example.com/app.zip",
81
+ app_name="my-app",
86
82
  )
87
83
 
88
84
  version = deployment.wait()
@@ -90,10 +86,8 @@ version = deployment.wait()
90
86
 
91
87
  ```python
92
88
  deployment = stackmachine.apps.autobuild(
93
- {
94
- "file": "https://example.com/app.zip",
95
- "name": "my-app",
96
- }
89
+ upload_url="https://example.com/app.zip",
90
+ app_name="my-app",
97
91
  )
98
92
  ```
99
93
 
@@ -140,13 +134,15 @@ key = stackmachine.apps.ssh.users.authorized_keys.create(
140
134
  Most methods accept `request_options` for per-request configuration:
141
135
 
142
136
  ```python
137
+ from stackmachine import RequestOptions
138
+
143
139
  app = stackmachine.apps.retrieve(
144
140
  "app_id",
145
- request_options={
146
- "api_key": "sk_stackmachine_other",
147
- "timeout": 30,
148
- "idempotency_key": "deploy-123",
149
- },
141
+ request_options=RequestOptions(
142
+ api_key="sk_stackmachine_other",
143
+ timeout=30,
144
+ idempotency_key="deploy-123",
145
+ ),
150
146
  )
151
147
  ```
152
148
 
@@ -156,6 +152,7 @@ app = stackmachine.apps.retrieve(
156
152
  cd python
157
153
  uv sync --dev
158
154
  uv run ruff check src examples tests
155
+ uv run mypy
159
156
  uv run pytest
160
157
  uv build --no-sources
161
158
  ```
@@ -4,12 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stackmachine"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Python SDK for StackMachine."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  dependencies = [
12
12
  "httpx>=0.27,<1",
13
+ "typing-extensions>=4.10,<5",
13
14
  "websocket-client>=1.8,<2",
14
15
  "websockets>=12,<16",
15
16
  ]
@@ -31,6 +32,7 @@ Issues = "https://github.com/stackmachine/sdks/issues"
31
32
 
32
33
  [dependency-groups]
33
34
  dev = [
35
+ "mypy>=1.13,<2",
34
36
  "pytest>=8.0",
35
37
  "pytest-asyncio>=0.24",
36
38
  "ruff>=0.8",
@@ -43,6 +45,17 @@ target-version = "py39"
43
45
  [tool.ruff.lint]
44
46
  select = ["E", "F", "I", "B"]
45
47
 
48
+ [tool.mypy]
49
+ python_version = "3.9"
50
+ files = ["src", "examples", "tests"]
51
+ pretty = true
52
+ show_error_codes = true
53
+ warn_unused_ignores = true
54
+
55
+ [[tool.mypy.overrides]]
56
+ module = ["pytest", "pytest.*"]
57
+ follow_imports = "skip"
58
+
46
59
  [tool.pytest.ini_options]
47
60
  testpaths = ["tests"]
48
61
  asyncio_mode = "auto"
@@ -0,0 +1,144 @@
1
+ """StackMachine Python SDK."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ from ._async_client import AsyncStackMachine
6
+ from ._client import StackMachine
7
+ from ._config import RequestOptions
8
+ from ._errors import (
9
+ StackMachineAPIError,
10
+ StackMachineAuthenticationError,
11
+ StackMachineConnectionError,
12
+ StackMachineError,
13
+ StackMachineGraphQLError,
14
+ StackMachineInvalidRequestError,
15
+ StackMachinePermissionError,
16
+ StackMachineRateLimitError,
17
+ StackMachineValidationError,
18
+ is_stackmachine_error,
19
+ )
20
+ from ._models import (
21
+ AppAlias,
22
+ AppSshServer,
23
+ DeployApp,
24
+ DeployAppKindWordPress,
25
+ DeployAppVersion,
26
+ DeploymentProgress,
27
+ ExpectedDNSRecord,
28
+ Log,
29
+ SshAuthorizedKey,
30
+ SshUser,
31
+ UploadProgress,
32
+ Viewer,
33
+ )
34
+ from ._pagination import AsyncStackMachineList, StackMachineList
35
+ from ._types import (
36
+ AppAliasSortBy,
37
+ AppsDomainsCreateInput,
38
+ AppsDomainsListInput,
39
+ AppsSshAuthorizedKeysCreateInput,
40
+ AppsSshAuthorizedKeysDeleteInput,
41
+ AppsSshAuthorizedKeysListInput,
42
+ AppsSshServerUpdateInput,
43
+ AppsSshUsersListInput,
44
+ AppsSshUsersUpdateInput,
45
+ AppsVersionsListInput,
46
+ AppsVersionsLogsListInput,
47
+ AsyncStackMachineInitSettings,
48
+ CreateZipFiles,
49
+ DeployAppAutobuildExtraData,
50
+ DeployAppAutobuildInput,
51
+ DeployAppEnvVarInput,
52
+ DeployAppJobDefinitionInput,
53
+ DeployAppsListInput,
54
+ DeployAppsSortBy,
55
+ DeployAppVersionsSortBy,
56
+ DeployAppWordPressExtraData,
57
+ DeploymentProgressCallback,
58
+ FileInput,
59
+ Headers,
60
+ LogStream,
61
+ PaginationOptions,
62
+ Readable,
63
+ RequestOptionsInput,
64
+ RequestOptionsLike,
65
+ SshAuthenticationMethod,
66
+ SshTokenCreateResult,
67
+ SshUserPasswordRevealResult,
68
+ SshUserPasswordRotateResult,
69
+ StackMachineInitSettings,
70
+ UploadProgressCallback,
71
+ )
72
+ from ._uploads import create_zip
73
+
74
+ try:
75
+ __version__ = version("stackmachine")
76
+ except PackageNotFoundError:
77
+ __version__ = "0.3.2"
78
+
79
+ __all__ = [
80
+ "AppAlias",
81
+ "AppAliasSortBy",
82
+ "AppSshServer",
83
+ "AppsDomainsCreateInput",
84
+ "AppsDomainsListInput",
85
+ "AppsSshAuthorizedKeysCreateInput",
86
+ "AppsSshAuthorizedKeysDeleteInput",
87
+ "AppsSshAuthorizedKeysListInput",
88
+ "AppsSshServerUpdateInput",
89
+ "AppsSshUsersListInput",
90
+ "AppsSshUsersUpdateInput",
91
+ "AppsVersionsListInput",
92
+ "AppsVersionsLogsListInput",
93
+ "AsyncStackMachine",
94
+ "AsyncStackMachineInitSettings",
95
+ "AsyncStackMachineList",
96
+ "CreateZipFiles",
97
+ "DeployApp",
98
+ "DeployAppAutobuildExtraData",
99
+ "DeployAppAutobuildInput",
100
+ "DeployAppEnvVarInput",
101
+ "DeployAppJobDefinitionInput",
102
+ "DeployAppKindWordPress",
103
+ "DeployAppVersion",
104
+ "DeployAppVersionsSortBy",
105
+ "DeployAppWordPressExtraData",
106
+ "DeployAppsListInput",
107
+ "DeployAppsSortBy",
108
+ "DeploymentProgress",
109
+ "DeploymentProgressCallback",
110
+ "ExpectedDNSRecord",
111
+ "FileInput",
112
+ "Headers",
113
+ "Log",
114
+ "LogStream",
115
+ "PaginationOptions",
116
+ "Readable",
117
+ "RequestOptions",
118
+ "RequestOptionsInput",
119
+ "RequestOptionsLike",
120
+ "SshAuthenticationMethod",
121
+ "SshAuthorizedKey",
122
+ "SshTokenCreateResult",
123
+ "SshUser",
124
+ "SshUserPasswordRevealResult",
125
+ "SshUserPasswordRotateResult",
126
+ "StackMachine",
127
+ "StackMachineAPIError",
128
+ "StackMachineAuthenticationError",
129
+ "StackMachineConnectionError",
130
+ "StackMachineError",
131
+ "StackMachineGraphQLError",
132
+ "StackMachineInvalidRequestError",
133
+ "StackMachineList",
134
+ "StackMachineInitSettings",
135
+ "StackMachinePermissionError",
136
+ "StackMachineRateLimitError",
137
+ "StackMachineValidationError",
138
+ "UploadProgress",
139
+ "UploadProgressCallback",
140
+ "Viewer",
141
+ "__version__",
142
+ "create_zip",
143
+ "is_stackmachine_error",
144
+ ]
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping, Optional
4
+
5
+ import httpx
6
+
7
+ from ._config import (
8
+ DEFAULT_API_URL,
9
+ DEFAULT_MAX_NETWORK_RETRIES,
10
+ DEFAULT_TIMEOUT,
11
+ ClientConfig,
12
+ )
13
+ from ._graphql import operations as gql
14
+ from ._models import Viewer
15
+ from ._transport import AsyncTransport
16
+ from ._types import AsyncStackMachineInitSettings, Headers, RequestOptionsLike
17
+ from ._uploads import AsyncUploader
18
+ from .resources.apps import AsyncDeployAppsResource
19
+ from .resources.deployments import AsyncDeploymentsResource
20
+ from .resources.files import AsyncFilesResource
21
+
22
+
23
+ class AsyncStackMachine:
24
+ def __init__(
25
+ self,
26
+ api_key: str,
27
+ *,
28
+ api_url: Optional[str] = None,
29
+ apiUrl: Optional[str] = None,
30
+ headers: Optional[Headers] = None,
31
+ timeout: float = DEFAULT_TIMEOUT,
32
+ max_network_retries: Optional[int] = None,
33
+ maxNetworkRetries: Optional[int] = None,
34
+ http_client: Optional[httpx.AsyncClient] = None,
35
+ http_transport: Optional[httpx.AsyncBaseTransport] = None,
36
+ ) -> None:
37
+ resolved_api_url = (
38
+ api_url if api_url is not None else apiUrl or DEFAULT_API_URL
39
+ )
40
+ resolved_max_retries = (
41
+ max_network_retries
42
+ if max_network_retries is not None
43
+ else maxNetworkRetries
44
+ if maxNetworkRetries is not None
45
+ else DEFAULT_MAX_NETWORK_RETRIES
46
+ )
47
+ self.api_key = api_key
48
+ self.api_url = resolved_api_url
49
+ self.apiUrl = resolved_api_url
50
+ self.timeout = timeout
51
+ self.max_network_retries = resolved_max_retries
52
+ self.maxNetworkRetries = resolved_max_retries
53
+ self._config = ClientConfig(
54
+ api_url=resolved_api_url,
55
+ headers=headers,
56
+ timeout=timeout,
57
+ max_network_retries=resolved_max_retries,
58
+ )
59
+ self._transport = AsyncTransport(
60
+ api_key,
61
+ self._config,
62
+ http_client=http_client,
63
+ http_transport=http_transport,
64
+ )
65
+ self.deployments = AsyncDeploymentsResource(self)
66
+ self.apps = AsyncDeployAppsResource(self, self.deployments)
67
+ self.files = AsyncFilesResource(self, AsyncUploader(self._transport))
68
+
69
+ @classmethod
70
+ def init(
71
+ cls,
72
+ settings: Optional[AsyncStackMachineInitSettings] = None,
73
+ *,
74
+ api_key: Optional[str] = None,
75
+ apiKey: Optional[str] = None,
76
+ token: Optional[str] = None,
77
+ api_url: Optional[str] = None,
78
+ apiUrl: Optional[str] = None,
79
+ headers: Optional[Headers] = None,
80
+ timeout: Optional[float] = None,
81
+ max_network_retries: Optional[int] = None,
82
+ maxNetworkRetries: Optional[int] = None,
83
+ http_client: Optional[httpx.AsyncClient] = None,
84
+ http_transport: Optional[httpx.AsyncBaseTransport] = None,
85
+ ) -> "AsyncStackMachine":
86
+ values: AsyncStackMachineInitSettings = {} if settings is None else settings
87
+ resolved_api_key = (
88
+ api_key
89
+ if api_key is not None
90
+ else apiKey
91
+ if apiKey is not None
92
+ else token
93
+ if token is not None
94
+ else values.get("api_key")
95
+ or values.get("apiKey")
96
+ or values.get("token")
97
+ or ""
98
+ )
99
+ settings_timeout = values.get("timeout")
100
+ resolved_timeout = (
101
+ timeout
102
+ if timeout is not None
103
+ else settings_timeout
104
+ if settings_timeout is not None
105
+ else DEFAULT_TIMEOUT
106
+ )
107
+ resolved_api_url = (
108
+ api_url
109
+ if api_url is not None
110
+ else apiUrl
111
+ if apiUrl is not None
112
+ else values.get("api_url")
113
+ or values.get("apiUrl")
114
+ or DEFAULT_API_URL
115
+ )
116
+ settings_max_retries = (
117
+ values.get("max_network_retries")
118
+ if values.get("max_network_retries") is not None
119
+ else values.get("maxNetworkRetries")
120
+ )
121
+ resolved_max_retries = (
122
+ max_network_retries
123
+ if max_network_retries is not None
124
+ else maxNetworkRetries
125
+ if maxNetworkRetries is not None
126
+ else settings_max_retries
127
+ if settings_max_retries is not None
128
+ else DEFAULT_MAX_NETWORK_RETRIES
129
+ )
130
+ return cls(
131
+ resolved_api_key,
132
+ api_url=resolved_api_url,
133
+ headers=headers if headers is not None else values.get("headers"),
134
+ timeout=resolved_timeout,
135
+ max_network_retries=resolved_max_retries,
136
+ http_client=(
137
+ http_client if http_client is not None else values.get("http_client")
138
+ ),
139
+ http_transport=(
140
+ http_transport
141
+ if http_transport is not None
142
+ else values.get("http_transport")
143
+ ),
144
+ )
145
+
146
+ async def close(self) -> None:
147
+ await self._transport.close()
148
+
149
+ async def __aenter__(self) -> "AsyncStackMachine":
150
+ return self
151
+
152
+ async def __aexit__(self, *exc_info: object) -> None:
153
+ await self.close()
154
+
155
+ async def _query(
156
+ self,
157
+ query: str,
158
+ variables: Optional[Mapping[str, Any]] = None,
159
+ *,
160
+ request_options: Optional[RequestOptionsLike] = None,
161
+ ) -> Any:
162
+ return await self._transport.execute(
163
+ query,
164
+ variables,
165
+ request_options=request_options,
166
+ )
167
+
168
+ async def _mutation(
169
+ self,
170
+ query: str,
171
+ variables: Optional[Mapping[str, Any]] = None,
172
+ *,
173
+ request_options: Optional[RequestOptionsLike] = None,
174
+ ) -> Any:
175
+ return await self._transport.execute(
176
+ query,
177
+ variables,
178
+ request_options=request_options,
179
+ mutation=True,
180
+ )
181
+
182
+ def _subscribe_deployment(
183
+ self, build_id: str, request_options: Optional[RequestOptionsLike] = None
184
+ ):
185
+ return self._transport.subscribe(
186
+ gql.AUTOBUILD_SUBSCRIPTION,
187
+ {"buildId": build_id},
188
+ request_options=request_options,
189
+ )
190
+
191
+ async def viewer(
192
+ self, *, request_options: Optional[RequestOptionsLike] = None
193
+ ) -> Optional[Viewer]:
194
+ response = await self._query(
195
+ gql.VIEWER_QUERY, {}, request_options=request_options
196
+ )
197
+ viewer = response.get("viewer") if response else None
198
+ return Viewer(username=viewer["username"]) if viewer else None