stackmachine 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- stackmachine/__init__.py +64 -2
- stackmachine/_async_client.py +122 -0
- stackmachine/_client.py +120 -0
- stackmachine/_config.py +27 -0
- stackmachine/_errors.py +97 -0
- stackmachine/_graphql/__init__.py +1 -0
- stackmachine/_graphql/operations.py +551 -0
- stackmachine/_models.py +229 -0
- stackmachine/_pagination.py +387 -0
- stackmachine/_transport.py +562 -0
- stackmachine/_uploads.py +372 -0
- stackmachine/_utils.py +75 -0
- stackmachine/resources/__init__.py +12 -0
- stackmachine/resources/_shared.py +43 -0
- stackmachine/resources/apps.py +271 -0
- stackmachine/resources/deployments.py +398 -0
- stackmachine/resources/domains.py +298 -0
- stackmachine/resources/files.py +82 -0
- stackmachine/resources/ssh.py +633 -0
- stackmachine/resources/versions.py +210 -0
- stackmachine-0.3.0.dist-info/METADATA +193 -0
- stackmachine-0.3.0.dist-info/RECORD +24 -0
- {stackmachine-0.2.1.dist-info → stackmachine-0.3.0.dist-info}/WHEEL +1 -1
- stackmachine-0.2.1.dist-info/METADATA +0 -42
- stackmachine-0.2.1.dist-info/RECORD +0 -5
stackmachine/__init__.py
CHANGED
|
@@ -2,9 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
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 ._uploads import create_zip
|
|
36
|
+
|
|
5
37
|
try:
|
|
6
38
|
__version__ = version("stackmachine")
|
|
7
39
|
except PackageNotFoundError:
|
|
8
|
-
__version__ = "0.
|
|
40
|
+
__version__ = "0.3.0"
|
|
9
41
|
|
|
10
|
-
__all__ = [
|
|
42
|
+
__all__ = [
|
|
43
|
+
"AppAlias",
|
|
44
|
+
"AppSshServer",
|
|
45
|
+
"AsyncStackMachine",
|
|
46
|
+
"AsyncStackMachineList",
|
|
47
|
+
"DeployApp",
|
|
48
|
+
"DeployAppKindWordPress",
|
|
49
|
+
"DeployAppVersion",
|
|
50
|
+
"DeploymentProgress",
|
|
51
|
+
"ExpectedDNSRecord",
|
|
52
|
+
"Log",
|
|
53
|
+
"RequestOptions",
|
|
54
|
+
"SshAuthorizedKey",
|
|
55
|
+
"SshUser",
|
|
56
|
+
"StackMachine",
|
|
57
|
+
"StackMachineAPIError",
|
|
58
|
+
"StackMachineAuthenticationError",
|
|
59
|
+
"StackMachineConnectionError",
|
|
60
|
+
"StackMachineError",
|
|
61
|
+
"StackMachineGraphQLError",
|
|
62
|
+
"StackMachineInvalidRequestError",
|
|
63
|
+
"StackMachineList",
|
|
64
|
+
"StackMachinePermissionError",
|
|
65
|
+
"StackMachineRateLimitError",
|
|
66
|
+
"StackMachineValidationError",
|
|
67
|
+
"UploadProgress",
|
|
68
|
+
"Viewer",
|
|
69
|
+
"__version__",
|
|
70
|
+
"create_zip",
|
|
71
|
+
"is_stackmachine_error",
|
|
72
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
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 ._uploads import AsyncUploader
|
|
17
|
+
from .resources.apps import AsyncDeployAppsResource
|
|
18
|
+
from .resources.deployments import AsyncDeploymentsResource
|
|
19
|
+
from .resources.files import AsyncFilesResource
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AsyncStackMachine:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
api_key: str,
|
|
26
|
+
*,
|
|
27
|
+
api_url: str = DEFAULT_API_URL,
|
|
28
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
29
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
30
|
+
max_network_retries: int = DEFAULT_MAX_NETWORK_RETRIES,
|
|
31
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
32
|
+
http_transport: Optional[httpx.AsyncBaseTransport] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self.api_key = api_key
|
|
35
|
+
self.api_url = api_url
|
|
36
|
+
self.apiUrl = api_url
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
self.max_network_retries = max_network_retries
|
|
39
|
+
self.maxNetworkRetries = max_network_retries
|
|
40
|
+
self._config = ClientConfig(
|
|
41
|
+
api_url=api_url,
|
|
42
|
+
headers=headers,
|
|
43
|
+
timeout=timeout,
|
|
44
|
+
max_network_retries=max_network_retries,
|
|
45
|
+
)
|
|
46
|
+
self._transport = AsyncTransport(
|
|
47
|
+
api_key,
|
|
48
|
+
self._config,
|
|
49
|
+
http_client=http_client,
|
|
50
|
+
http_transport=http_transport,
|
|
51
|
+
)
|
|
52
|
+
self.deployments = AsyncDeploymentsResource(self)
|
|
53
|
+
self.apps = AsyncDeployAppsResource(self, self.deployments)
|
|
54
|
+
self.files = AsyncFilesResource(self, AsyncUploader(self._transport))
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def init(
|
|
58
|
+
cls,
|
|
59
|
+
settings: Optional[Mapping[str, Any]] = None,
|
|
60
|
+
**kwargs: Any,
|
|
61
|
+
) -> "AsyncStackMachine":
|
|
62
|
+
values = {**dict(settings or {}), **kwargs}
|
|
63
|
+
api_key = values.pop("api_key", None) or values.pop("token", None) or ""
|
|
64
|
+
if "apiUrl" in values:
|
|
65
|
+
values["api_url"] = values.pop("apiUrl")
|
|
66
|
+
if "maxNetworkRetries" in values:
|
|
67
|
+
values["max_network_retries"] = values.pop("maxNetworkRetries")
|
|
68
|
+
return cls(api_key, **values)
|
|
69
|
+
|
|
70
|
+
async def close(self) -> None:
|
|
71
|
+
await self._transport.close()
|
|
72
|
+
|
|
73
|
+
async def __aenter__(self) -> "AsyncStackMachine":
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
async def __aexit__(self, *exc_info: object) -> None:
|
|
77
|
+
await self.close()
|
|
78
|
+
|
|
79
|
+
async def _query(
|
|
80
|
+
self,
|
|
81
|
+
query: str,
|
|
82
|
+
variables: Optional[Mapping[str, Any]] = None,
|
|
83
|
+
*,
|
|
84
|
+
request_options: Optional[Mapping[str, Any]] = None,
|
|
85
|
+
) -> Any:
|
|
86
|
+
return await self._transport.execute(
|
|
87
|
+
query,
|
|
88
|
+
variables,
|
|
89
|
+
request_options=request_options,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def _mutation(
|
|
93
|
+
self,
|
|
94
|
+
query: str,
|
|
95
|
+
variables: Optional[Mapping[str, Any]] = None,
|
|
96
|
+
*,
|
|
97
|
+
request_options: Optional[Mapping[str, Any]] = None,
|
|
98
|
+
) -> Any:
|
|
99
|
+
return await self._transport.execute(
|
|
100
|
+
query,
|
|
101
|
+
variables,
|
|
102
|
+
request_options=request_options,
|
|
103
|
+
mutation=True,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _subscribe_deployment(
|
|
107
|
+
self, build_id: str, request_options: Optional[Mapping[str, Any]] = None
|
|
108
|
+
):
|
|
109
|
+
return self._transport.subscribe(
|
|
110
|
+
gql.AUTOBUILD_SUBSCRIPTION,
|
|
111
|
+
{"buildId": build_id},
|
|
112
|
+
request_options=request_options,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def viewer(
|
|
116
|
+
self, *, request_options: Optional[Mapping[str, Any]] = None
|
|
117
|
+
) -> Optional[Viewer]:
|
|
118
|
+
response = await self._query(
|
|
119
|
+
gql.VIEWER_QUERY, {}, request_options=request_options
|
|
120
|
+
)
|
|
121
|
+
viewer = response.get("viewer") if response else None
|
|
122
|
+
return Viewer(username=viewer["username"]) if viewer else None
|
stackmachine/_client.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
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 SyncTransport
|
|
16
|
+
from ._uploads import SyncUploader
|
|
17
|
+
from .resources.apps import DeployAppsResource
|
|
18
|
+
from .resources.deployments import DeploymentsResource
|
|
19
|
+
from .resources.files import FilesResource
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StackMachine:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
api_key: str,
|
|
26
|
+
*,
|
|
27
|
+
api_url: str = DEFAULT_API_URL,
|
|
28
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
29
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
30
|
+
max_network_retries: int = DEFAULT_MAX_NETWORK_RETRIES,
|
|
31
|
+
http_client: Optional[httpx.Client] = None,
|
|
32
|
+
http_transport: Optional[httpx.BaseTransport] = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self.api_key = api_key
|
|
35
|
+
self.api_url = api_url
|
|
36
|
+
self.apiUrl = api_url
|
|
37
|
+
self.timeout = timeout
|
|
38
|
+
self.max_network_retries = max_network_retries
|
|
39
|
+
self.maxNetworkRetries = max_network_retries
|
|
40
|
+
self._config = ClientConfig(
|
|
41
|
+
api_url=api_url,
|
|
42
|
+
headers=headers,
|
|
43
|
+
timeout=timeout,
|
|
44
|
+
max_network_retries=max_network_retries,
|
|
45
|
+
)
|
|
46
|
+
self._transport = SyncTransport(
|
|
47
|
+
api_key,
|
|
48
|
+
self._config,
|
|
49
|
+
http_client=http_client,
|
|
50
|
+
http_transport=http_transport,
|
|
51
|
+
)
|
|
52
|
+
self.deployments = DeploymentsResource(self)
|
|
53
|
+
self.apps = DeployAppsResource(self, self.deployments)
|
|
54
|
+
self.files = FilesResource(self, SyncUploader(self._transport))
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def init(
|
|
58
|
+
cls,
|
|
59
|
+
settings: Optional[Mapping[str, Any]] = None,
|
|
60
|
+
**kwargs: Any,
|
|
61
|
+
) -> "StackMachine":
|
|
62
|
+
values = {**dict(settings or {}), **kwargs}
|
|
63
|
+
api_key = values.pop("api_key", None) or values.pop("token", None) or ""
|
|
64
|
+
if "apiUrl" in values:
|
|
65
|
+
values["api_url"] = values.pop("apiUrl")
|
|
66
|
+
if "maxNetworkRetries" in values:
|
|
67
|
+
values["max_network_retries"] = values.pop("maxNetworkRetries")
|
|
68
|
+
return cls(api_key, **values)
|
|
69
|
+
|
|
70
|
+
def close(self) -> None:
|
|
71
|
+
self._transport.close()
|
|
72
|
+
|
|
73
|
+
def __enter__(self) -> "StackMachine":
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
77
|
+
self.close()
|
|
78
|
+
|
|
79
|
+
def _query(
|
|
80
|
+
self,
|
|
81
|
+
query: str,
|
|
82
|
+
variables: Optional[Mapping[str, Any]] = None,
|
|
83
|
+
*,
|
|
84
|
+
request_options: Optional[Mapping[str, Any]] = None,
|
|
85
|
+
) -> Any:
|
|
86
|
+
return self._transport.execute(
|
|
87
|
+
query,
|
|
88
|
+
variables,
|
|
89
|
+
request_options=request_options,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _mutation(
|
|
93
|
+
self,
|
|
94
|
+
query: str,
|
|
95
|
+
variables: Optional[Mapping[str, Any]] = None,
|
|
96
|
+
*,
|
|
97
|
+
request_options: Optional[Mapping[str, Any]] = None,
|
|
98
|
+
) -> Any:
|
|
99
|
+
return self._transport.execute(
|
|
100
|
+
query,
|
|
101
|
+
variables,
|
|
102
|
+
request_options=request_options,
|
|
103
|
+
mutation=True,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _subscribe_deployment(
|
|
107
|
+
self, build_id: str, request_options: Optional[Mapping[str, Any]] = None
|
|
108
|
+
):
|
|
109
|
+
return self._transport.subscribe(
|
|
110
|
+
gql.AUTOBUILD_SUBSCRIPTION,
|
|
111
|
+
{"buildId": build_id},
|
|
112
|
+
request_options=request_options,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def viewer(
|
|
116
|
+
self, *, request_options: Optional[Mapping[str, Any]] = None
|
|
117
|
+
) -> Optional[Viewer]:
|
|
118
|
+
response = self._query(gql.VIEWER_QUERY, {}, request_options=request_options)
|
|
119
|
+
viewer = response.get("viewer") if response else None
|
|
120
|
+
return Viewer(username=viewer["username"]) if viewer else None
|
stackmachine/_config.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Mapping, Optional
|
|
5
|
+
|
|
6
|
+
DEFAULT_API_URL = "https://api.stackmachine.com/graphql"
|
|
7
|
+
DEFAULT_TIMEOUT = 80.0
|
|
8
|
+
DEFAULT_MAX_NETWORK_RETRIES = 1
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class RequestOptions:
|
|
13
|
+
api_key: Optional[str] = None
|
|
14
|
+
headers: Optional[Mapping[str, str]] = None
|
|
15
|
+
timeout: Optional[float] = None
|
|
16
|
+
max_network_retries: Optional[int] = None
|
|
17
|
+
idempotency_key: Optional[str] = None
|
|
18
|
+
client_mutation_id: Optional[str] = None
|
|
19
|
+
force: Optional[bool] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ClientConfig:
|
|
24
|
+
api_url: str = DEFAULT_API_URL
|
|
25
|
+
headers: Optional[Mapping[str, str]] = None
|
|
26
|
+
timeout: float = DEFAULT_TIMEOUT
|
|
27
|
+
max_network_retries: int = DEFAULT_MAX_NETWORK_RETRIES
|
stackmachine/_errors.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping, Optional, Sequence
|
|
4
|
+
|
|
5
|
+
GraphQLErrorPayload = Mapping[str, Any]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StackMachineError(Exception):
|
|
9
|
+
"""Base exception raised by the StackMachine SDK."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
*,
|
|
15
|
+
operation_name: Optional[str] = None,
|
|
16
|
+
status_code: Optional[int] = None,
|
|
17
|
+
request_id: Optional[str] = None,
|
|
18
|
+
code: Optional[str] = None,
|
|
19
|
+
param: Optional[str] = None,
|
|
20
|
+
graphql_errors: Optional[Sequence[GraphQLErrorPayload]] = None,
|
|
21
|
+
cause: Optional[BaseException] = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(message)
|
|
24
|
+
self.message = message
|
|
25
|
+
self.type = type(self).__name__
|
|
26
|
+
self.operation_name = operation_name
|
|
27
|
+
self.status_code = status_code
|
|
28
|
+
self.request_id = request_id
|
|
29
|
+
self.code = code
|
|
30
|
+
self.param = param
|
|
31
|
+
self.graphql_errors = graphql_errors
|
|
32
|
+
self.__cause__ = cause
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StackMachineConnectionError(StackMachineError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StackMachineAPIError(StackMachineError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class StackMachineGraphQLError(StackMachineError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class StackMachineAuthenticationError(StackMachineAPIError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class StackMachinePermissionError(StackMachineAPIError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class StackMachineRateLimitError(StackMachineAPIError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class StackMachineInvalidRequestError(StackMachineAPIError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class StackMachineValidationError(StackMachineError):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_stackmachine_error(error: BaseException) -> bool:
|
|
68
|
+
return isinstance(error, StackMachineError)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def stackmachine_error_from_graphql_errors(
|
|
72
|
+
graphql_errors: Sequence[GraphQLErrorPayload],
|
|
73
|
+
operation_name: Optional[str] = None,
|
|
74
|
+
) -> StackMachineGraphQLError:
|
|
75
|
+
first = graphql_errors[0] if graphql_errors else {}
|
|
76
|
+
extensions = first.get("extensions") if isinstance(first, Mapping) else None
|
|
77
|
+
message = first.get("message") if isinstance(first, Mapping) else None
|
|
78
|
+
return StackMachineGraphQLError(
|
|
79
|
+
str(message or "StackMachine GraphQL request failed."),
|
|
80
|
+
operation_name=operation_name,
|
|
81
|
+
graphql_errors=graphql_errors,
|
|
82
|
+
code=extensions.get("code") if isinstance(extensions, Mapping) else None,
|
|
83
|
+
param=extensions.get("param") if isinstance(extensions, Mapping) else None,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def stackmachine_error_from_unknown(
|
|
88
|
+
error: BaseException,
|
|
89
|
+
operation_name: Optional[str] = None,
|
|
90
|
+
) -> StackMachineError:
|
|
91
|
+
if isinstance(error, StackMachineError):
|
|
92
|
+
return error
|
|
93
|
+
return StackMachineConnectionError(
|
|
94
|
+
str(error) or "StackMachine request failed.",
|
|
95
|
+
operation_name=operation_name,
|
|
96
|
+
cause=error,
|
|
97
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .operations import * # noqa: F403
|