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 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.0.0"
40
+ __version__ = "0.3.0"
9
41
 
10
- __all__ = ["__version__"]
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
@@ -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
@@ -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
@@ -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