commutator-sdk 0.1.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.
commutator/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ from commutator.client import CommutatorClient
4
+ from commutator.exceptions import (
5
+ APIError,
6
+ AuthenticationError,
7
+ CommutatorError,
8
+ NotFoundError,
9
+ ValidationError,
10
+ )
11
+
12
+ __all__ = [
13
+ "CommutatorClient",
14
+ "CommutatorError",
15
+ "APIError",
16
+ "AuthenticationError",
17
+ "ValidationError",
18
+ "NotFoundError",
19
+ ]
commutator/client.py ADDED
@@ -0,0 +1,155 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ import os
4
+ import typing
5
+ from enum import Enum
6
+
7
+ import requests
8
+ from commutator.exceptions import raise_for_status
9
+ from commutator.fleet import FleetModule
10
+ from commutator.identity import IdentityModule
11
+ from commutator.jobs import JobsModule
12
+ from commutator.optimizer import OptimizerModule
13
+
14
+
15
+ class Environment(str, Enum):
16
+ """The target environment for the Commutator SDK."""
17
+
18
+ PROD = "prod"
19
+ STAGE = "stage"
20
+ DEMO = "demo"
21
+ LOCAL = "local"
22
+
23
+
24
+ class CommutatorClient:
25
+ """The central client for interacting with Commutator Studios Platform."""
26
+
27
+ def __init__(
28
+ self,
29
+ *,
30
+ api_key: typing.Optional[str] = None,
31
+ token: typing.Optional[str] = None,
32
+ organization_id: typing.Optional[str] = None,
33
+ _internal_base_url: typing.Optional[str] = None,
34
+ ):
35
+ """Initializes the Commutator SDK Client.
36
+
37
+ Args:
38
+ api_key: The user's API Key. If not provided, will look for the
39
+ 'COMMUTATOR_API_KEY' environment variable.
40
+ token: A JWT bearer token retrieved via login (if not using API Key).
41
+ organization_id: The organization ID. If not provided, will look for the
42
+ 'COMMUTATOR_ORGANIZATION_ID' environment variable.
43
+ """
44
+ self._api_key = api_key or os.environ.get("COMMUTATOR_API_KEY")
45
+ self._token = token
46
+ self._organization_id = organization_id or os.environ.get("COMMUTATOR_ORGANIZATION_ID")
47
+
48
+ if not self._api_key and not self._token:
49
+ raise ValueError(
50
+ "Either api_key or token must be provided, or "
51
+ "COMMUTATOR_API_KEY environment variable must be set."
52
+ )
53
+
54
+ if _internal_base_url:
55
+ self.base_url = _internal_base_url
56
+ elif self._api_key:
57
+ if self._api_key.startswith("cs_loc_"):
58
+ self.base_url = "http://localhost:3000"
59
+ elif self._api_key.startswith("cs_stage_"):
60
+ self.base_url = "https://commutatorstaging.com"
61
+ elif self._api_key.startswith("cs_demo_"):
62
+ self.base_url = "https://commutator.tech"
63
+ else:
64
+ self.base_url = "https://commutator.app"
65
+ else:
66
+ self.base_url = "https://commutator.app"
67
+
68
+ # Initialize submodules
69
+ self.identity = IdentityModule(client=self)
70
+ self.admin = self.identity # Alias for documentation consistency
71
+ self.jobs = JobsModule(client=self)
72
+ self.fleet = FleetModule(client=self)
73
+ self.optimizer = OptimizerModule(client=self)
74
+
75
+ @classmethod
76
+ def login_with_api_key(cls, *, api_key: str) -> "CommutatorClient":
77
+ """Initialize the client using an explicit API key.
78
+
79
+ Args:
80
+ api_key: The user's API Key.
81
+ """
82
+ return cls(api_key=api_key)
83
+
84
+ @classmethod
85
+ def login_with_email(
86
+ cls,
87
+ *,
88
+ email: str,
89
+ password: str,
90
+ environment: Environment = Environment.PROD,
91
+ ) -> "CommutatorClient":
92
+ """Authenticate using email & password to retrieve a JWT token.
93
+
94
+ POST /api/v1/identity/login
95
+ """
96
+ env_map = {
97
+ Environment.PROD: "https://commutator.app",
98
+ Environment.STAGE: "https://commutatorstaging.com",
99
+ Environment.DEMO: "https://commutator.tech",
100
+ Environment.LOCAL: "http://localhost:3000",
101
+ }
102
+ target_url = env_map.get(environment, "https://commutator.app")
103
+
104
+ response = requests.post(
105
+ f"{target_url}/api/v1/identity/login",
106
+ json={"email": email, "password": password},
107
+ timeout=60,
108
+ )
109
+ raise_for_status(response)
110
+
111
+ auth_response = response.json()
112
+ data_block = auth_response.get("data", {})
113
+ token = data_block.get("token")
114
+
115
+ if not token:
116
+ raise ValueError("Authentication did not return a valid token.")
117
+
118
+ user_info = data_block.get("user", {})
119
+ org_id = user_info.get("organizationId") or user_info.get("organization_id")
120
+
121
+ return cls(token=token, organization_id=org_id, _internal_base_url=target_url)
122
+
123
+ def _request(
124
+ self,
125
+ *,
126
+ method: str,
127
+ path: str,
128
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
129
+ json: typing.Optional[typing.Dict[str, typing.Any]] = None,
130
+ data: typing.Optional[typing.Dict[str, typing.Any]] = None,
131
+ files: typing.Optional[typing.Dict[str, typing.Any]] = None,
132
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
133
+ ) -> requests.Response:
134
+ """Sends an HTTP request with authenticating headers."""
135
+ url = f"{self.base_url}{path}"
136
+
137
+ request_headers = headers or {}
138
+ if self._token:
139
+ request_headers["Authorization"] = f"Bearer {self._token}"
140
+ elif self._api_key:
141
+ request_headers["X-API-Key"] = self._api_key
142
+
143
+ if self._organization_id:
144
+ request_headers["X-Organization-Id"] = self._organization_id
145
+
146
+ return requests.request(
147
+ method=method,
148
+ url=url,
149
+ params=params,
150
+ json=json,
151
+ data=data,
152
+ files=files,
153
+ headers=request_headers,
154
+ timeout=60,
155
+ )
@@ -0,0 +1,106 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ import typing
4
+
5
+
6
+ class CommutatorError(Exception):
7
+ """Base exception for all Commutator SDK errors."""
8
+
9
+ def __init__(self, *, message: str):
10
+ super().__init__(message)
11
+ self.message = message
12
+
13
+
14
+ class APIError(CommutatorError):
15
+ """Exception raised when the API returns an error status code."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ status_code: int,
21
+ code: str,
22
+ message: str,
23
+ details: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = None,
24
+ request_id: typing.Optional[str] = None,
25
+ documentation_url: typing.Optional[str] = None,
26
+ ):
27
+ super().__init__(message=message)
28
+ self.status_code = status_code
29
+ self.code = code
30
+ self.details = details or []
31
+ self.request_id = request_id
32
+ self.documentation_url = documentation_url
33
+
34
+ def __str__(self) -> str:
35
+ err_str = f"[{self.status_code} {self.code}] {self.message}"
36
+ if self.request_id:
37
+ err_str += f" (Request ID: {self.request_id})"
38
+ return err_str
39
+
40
+
41
+ class AuthenticationError(APIError):
42
+ """Raised on authentication failure (e.g. invalid API key or login)."""
43
+
44
+
45
+ class ValidationError(APIError):
46
+ """Raised when the API returns validation issues."""
47
+
48
+
49
+ class NotFoundError(APIError):
50
+ """Raised when a resource (e.g., job) does not exist."""
51
+
52
+
53
+ def raise_for_status(response: typing.Any) -> None:
54
+ """Helper to parse error JSON and raise appropriate APIError."""
55
+ if response.status_code >= 400:
56
+ try:
57
+ err_data = response.json()
58
+ status_code = err_data.get("status", response.status_code)
59
+ code = err_data.get("code", "API_ERROR")
60
+ message = err_data.get("message", response.text)
61
+ details = err_data.get("details", [])
62
+ request_id = err_data.get("requestId")
63
+ documentation_url = err_data.get("documentationUrl")
64
+ except ValueError:
65
+ status_code = response.status_code
66
+ code = "API_ERROR"
67
+ message = response.text
68
+ details = []
69
+ request_id = None
70
+ documentation_url = None
71
+
72
+ if status_code == 401:
73
+ raise AuthenticationError(
74
+ status_code=status_code,
75
+ code=code,
76
+ message=message,
77
+ details=details,
78
+ request_id=request_id,
79
+ documentation_url=documentation_url,
80
+ )
81
+ if status_code == 400:
82
+ raise ValidationError(
83
+ status_code=status_code,
84
+ code=code,
85
+ message=message,
86
+ details=details,
87
+ request_id=request_id,
88
+ documentation_url=documentation_url,
89
+ )
90
+ if status_code == 404:
91
+ raise NotFoundError(
92
+ status_code=status_code,
93
+ code=code,
94
+ message=message,
95
+ details=details,
96
+ request_id=request_id,
97
+ documentation_url=documentation_url,
98
+ )
99
+ raise APIError(
100
+ status_code=status_code,
101
+ code=code,
102
+ message=message,
103
+ details=details,
104
+ request_id=request_id,
105
+ documentation_url=documentation_url,
106
+ )
commutator/fleet.py ADDED
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ import typing
4
+
5
+ from commutator.exceptions import raise_for_status
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from commutator.client import CommutatorClient
9
+
10
+
11
+ class FleetModule:
12
+ """Module for querying active quantum hardware backends."""
13
+
14
+ def __init__(self, *, client: "CommutatorClient"):
15
+ self._client = client
16
+
17
+ def list_backends(self) -> typing.List[typing.Dict[str, typing.Any]]:
18
+ """Get available quantum backends and fleet status.
19
+
20
+ GET /api/v1/fleet
21
+ """
22
+ response = self._client._request(method="GET", path="/api/v1/backends")
23
+ raise_for_status(response)
24
+ return typing.cast(
25
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
26
+ )
27
+
28
+ def update_backend(self, *, backend_id: str, enabled: bool) -> typing.Dict[str, typing.Any]:
29
+ """Update fleet configurations or backend preferences.
30
+
31
+ PATCH /api/v1/fleet
32
+ """
33
+ payload = {"id": backend_id, "enabled": enabled}
34
+ response = self._client._request(method="PATCH", path="/api/v1/backends", json=payload)
35
+ raise_for_status(response)
36
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
37
+
38
+ def discover(self) -> typing.List[typing.Dict[str, typing.Any]]:
39
+ """Discover detailed topology and calibration data.
40
+
41
+ POST /api/v1/backends/discovery
42
+ """
43
+ response = self._client._request(method="POST", path="/api/v1/backends/discovery")
44
+ raise_for_status(response)
45
+ # Fetch and return the newly synchronized detailed list
46
+ return self.list_backends()
47
+
48
+ def activate(self, *, backend_id: str) -> bool:
49
+ """Request exclusive access or activation of a specific reserved backend.
50
+
51
+ PATCH /api/v1/backend/[id]/activate
52
+ """
53
+ response = self._client._request(
54
+ method="PATCH", path=f"/api/v1/backend/{backend_id}/activate", json={"isActive": True}
55
+ )
56
+ if response.status_code in (200, 204):
57
+ return True
58
+ raise_for_status(response)
59
+ return False
60
+
61
+ def deactivate(self, *, backend_id: str) -> bool:
62
+ """Deactivate a specific reserved backend.
63
+
64
+ PATCH /api/v1/backend/[id]/activate
65
+ """
66
+ response = self._client._request(
67
+ method="PATCH", path=f"/api/v1/backend/{backend_id}/activate", json={"isActive": False}
68
+ )
69
+ if response.status_code in (200, 204):
70
+ return True
71
+ raise_for_status(response)
72
+ return False
commutator/identity.py ADDED
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ import typing
4
+
5
+ from commutator.exceptions import raise_for_status
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from commutator.client import CommutatorClient
9
+
10
+
11
+ class IdentityModule:
12
+ """Module for identity and authentication-related APIs."""
13
+
14
+ def __init__(self, *, client: "CommutatorClient"):
15
+ self._client = client
16
+
17
+ def list_api_keys(self) -> typing.List[typing.Dict[str, typing.Any]]:
18
+ """Retrieve a list of the user's API keys.
19
+
20
+ GET /api/v1/identity/keys
21
+ """
22
+ response = self._client._request(method="GET", path="/api/v1/identity/keys")
23
+ raise_for_status(response)
24
+ return typing.cast(
25
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
26
+ )
27
+
28
+ def create_api_key(
29
+ self, *, name: str, expires_in_days: int = 30
30
+ ) -> typing.Dict[str, typing.Any]:
31
+ """Generate a new API key.
32
+
33
+ POST /api/v1/identity/keys
34
+ """
35
+ payload = {"name": name, "expiresInDays": expires_in_days}
36
+ response = self._client._request(method="POST", path="/api/v1/identity/keys", json=payload)
37
+ raise_for_status(response)
38
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
39
+
40
+ def revoke_api_key(self, *, key_id: str) -> bool:
41
+ """Revoke an active API key immediately.
42
+
43
+ DELETE /api/v1/identity/keys/[id]
44
+ """
45
+ response = self._client._request(method="DELETE", path=f"/api/v1/identity/keys/{key_id}")
46
+ if response.status_code in (200, 204):
47
+ return True
48
+ raise_for_status(response)
49
+ return False
50
+
51
+ def get_team(self) -> typing.List[typing.Dict[str, typing.Any]]:
52
+ """Get details about the user's team.
53
+
54
+ GET /api/v1/identity/team
55
+ """
56
+ response = self._client._request(method="GET", path="/api/v1/identity/team")
57
+ raise_for_status(response)
58
+ return typing.cast(
59
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
60
+ )
61
+
62
+ def get_audit_logs(self, *, limit: int = 50) -> typing.List[typing.Dict[str, typing.Any]]:
63
+ """Retrieve recent security and access audit logs for the team.
64
+
65
+ GET /api/v1/audit/logs
66
+ """
67
+ response = self._client._request(
68
+ method="GET", path="/api/v1/audit/logs", params={"limit": limit}
69
+ )
70
+ raise_for_status(response)
71
+ return typing.cast(
72
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
73
+ )
74
+
75
+ def add_ibm_key(
76
+ self, *, api_key: str, name: str = "IBM API Key"
77
+ ) -> typing.Union[typing.Dict[str, typing.Any], str]:
78
+ """Set your IBM API key for quantum simulations.
79
+
80
+ POST /api/v1/identity/credentials
81
+ """
82
+ payload = {"provider": "IBM", "name": name, "config": {"apiToken": api_key, "crns": []}}
83
+ response = self._client._request(
84
+ method="POST", path="/api/v1/identity/credentials", json=payload
85
+ )
86
+
87
+ if response.status_code == 409:
88
+ return "this key is already set"
89
+
90
+ raise_for_status(response)
91
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
commutator/jobs.py ADDED
@@ -0,0 +1,159 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ import typing
4
+
5
+ from commutator.exceptions import raise_for_status
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from commutator.client import CommutatorClient
9
+
10
+
11
+ class JobsModule:
12
+ """Module for job-related APIs."""
13
+
14
+ def __init__(self, *, client: "CommutatorClient"):
15
+ self._client = client
16
+
17
+ def list(self) -> typing.List[typing.Dict[str, typing.Any]]:
18
+ """List job history.
19
+
20
+ GET /api/v1/jobs
21
+ """
22
+ response = self._client._request(method="GET", path="/api/v1/jobs")
23
+ raise_for_status(response)
24
+ return typing.cast(
25
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
26
+ )
27
+
28
+ def create(
29
+ self,
30
+ *,
31
+ name: str,
32
+ algorithm: str,
33
+ file_path: str,
34
+ organization_id: typing.Optional[str] = None,
35
+ ) -> typing.Dict[str, typing.Any]:
36
+ """Submit a new quantum job via file configuration.
37
+
38
+ POST /api/v1/job
39
+ """
40
+ data = {
41
+ "name": name,
42
+ "algorithm": algorithm,
43
+ }
44
+ if organization_id:
45
+ data["organization_id"] = organization_id
46
+ with open(file_path, "rb") as f:
47
+ files = {"file": (file_path, f, "application/json")}
48
+ response = self._client._request(
49
+ method="POST", path="/api/v1/job", data=data, files=files
50
+ )
51
+ raise_for_status(response)
52
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
53
+
54
+ def get(self, *, job_id: str) -> typing.Dict[str, typing.Any]:
55
+ """Get details for a specific job by its ID.
56
+
57
+ GET /api/v1/job/[id]
58
+ """
59
+ response = self._client._request(method="GET", path=f"/api/v1/job/{job_id}")
60
+ raise_for_status(response)
61
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
62
+
63
+ def update(self, *, job_id: str, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
64
+ """Updates the job details.
65
+
66
+ PATCH /api/v1/jobs/[id]
67
+ """
68
+ payload = {}
69
+ for k, v in kwargs.items():
70
+ camel_k = "".join(x.capitalize() or "_" for x in k.split("_"))
71
+ camel_k = camel_k[0].lower() + camel_k[1:]
72
+ payload[camel_k] = v
73
+
74
+ response = self._client._request(method="PATCH", path=f"/api/v1/job/{job_id}", json=payload)
75
+ raise_for_status(response)
76
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
77
+
78
+ def list_algorithms(self) -> typing.List[typing.Dict[str, typing.Any]]:
79
+ """Retrieve the list of supported quantum algorithms available for execution.
80
+
81
+ GET /api/v1/job/algorithms
82
+ """
83
+ response = self._client._request(method="GET", path="/api/v1/job/algorithms")
84
+ raise_for_status(response)
85
+ return typing.cast(
86
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
87
+ )
88
+
89
+ def execute(
90
+ self,
91
+ *,
92
+ job_id: str,
93
+ backend: str,
94
+ optimization_level: int = 3,
95
+ dynamic_decoupling: bool = False,
96
+ max_iterations: typing.Optional[int] = None,
97
+ convergence: typing.Optional[float] = None,
98
+ predicted_shots: typing.Optional[int] = None,
99
+ **kwargs: typing.Any,
100
+ ) -> typing.Dict[str, typing.Any]:
101
+ """Executes a previously created quantum job on target hardware.
102
+
103
+ PATCH /api/v1/job/[id]
104
+ """
105
+ payload: typing.Dict[str, typing.Any] = {
106
+ "status": "Pending",
107
+ "hardware": backend,
108
+ "optimizationLevel": optimization_level,
109
+ "dynamicDecoupling": dynamic_decoupling,
110
+ }
111
+ if max_iterations is not None:
112
+ payload["maxIterations"] = max_iterations
113
+ if convergence is not None:
114
+ payload["convergence"] = convergence
115
+ if predicted_shots is not None:
116
+ payload["predictedShots"] = predicted_shots
117
+
118
+ for k, v in kwargs.items():
119
+ camel_k = "".join(x.capitalize() or "_" for x in k.split("_"))
120
+ camel_k = camel_k[0].lower() + camel_k[1:]
121
+ if camel_k not in payload:
122
+ payload[camel_k] = v
123
+
124
+ response = self._client._request(method="PATCH", path=f"/api/v1/job/{job_id}", json=payload)
125
+ raise_for_status(response)
126
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
127
+
128
+ def cancel(self, *, job_id: str) -> bool:
129
+ """Attempt to cancel a pending or queued job.
130
+
131
+ DELETE /api/v1/job/[id]
132
+ """
133
+ response = self._client._request(method="DELETE", path=f"/api/v1/job/{job_id}")
134
+ if response.status_code in (200, 204):
135
+ return True
136
+ raise_for_status(response)
137
+ return False
138
+
139
+ def get_download_url(self, *, job_id: str) -> str:
140
+ """Retrieves a presigned URL to download the job results.
141
+
142
+ GET /api/v1/jobs/download-url?jobId=[id]
143
+ """
144
+ response = self._client._request(
145
+ method="GET", path="/api/v1/jobs/download-url", params={"jobId": job_id}
146
+ )
147
+ raise_for_status(response)
148
+ return typing.cast(str, response.json().get("url", ""))
149
+
150
+ def download_results(self, *, job_id: str) -> typing.Dict[str, typing.Any]:
151
+ """Downloads the job results data.
152
+
153
+ GET /api/v1/jobs/download-data?jobId=[id]
154
+ """
155
+ response = self._client._request(
156
+ method="GET", path="/api/v1/jobs/download-data", params={"jobId": job_id}
157
+ )
158
+ raise_for_status(response)
159
+ return typing.cast(typing.Dict[str, typing.Any], response.json())
@@ -0,0 +1,66 @@
1
+ # Copyright (c) 2026. Commutator Studios GmbH.
2
+
3
+ import typing
4
+
5
+ from commutator.exceptions import raise_for_status
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from commutator.client import CommutatorClient
9
+
10
+
11
+ class OptimizerModule:
12
+ """Module for mathematical optimization and hardware recommendation engines."""
13
+
14
+ def __init__(self, *, client: "CommutatorClient"):
15
+ self._client = client
16
+
17
+ def get_metadata(self) -> typing.List[typing.Dict[str, typing.Any]]:
18
+ """Get current optimizer algorithm metadata.
19
+
20
+ GET /api/v1/optimizer
21
+ """
22
+ response = self._client._request(method="GET", path="/api/v1/optimizer")
23
+ raise_for_status(response)
24
+ return typing.cast(
25
+ typing.List[typing.Dict[str, typing.Any]], response.json().get("data", [])
26
+ )
27
+
28
+ def run(
29
+ self, *, algorithm: str, qubits: int, circuit_depth: int, **kwargs: typing.Any
30
+ ) -> typing.Dict[str, typing.Any]:
31
+ """Configure or create a new optimizer run.
32
+
33
+ POST /api/v1/optimizer
34
+ """
35
+ payload = {"algorithm": algorithm, "qubits": qubits, "circuit_depth": circuit_depth}
36
+ payload.update(kwargs)
37
+ response = self._client._request(method="POST", path="/api/v1/optimizer", json=payload)
38
+ raise_for_status(response)
39
+ return typing.cast(typing.Dict[str, typing.Any], response.json().get("data", {}))
40
+
41
+ def recommend(
42
+ self,
43
+ *,
44
+ execution_id: str,
45
+ target_backend: typing.Optional[str] = None,
46
+ target_dd: bool = True,
47
+ plan_only: bool = True,
48
+ ) -> typing.List[typing.Dict[str, typing.Any]]:
49
+ """Request optimization and hardware recommendations.
50
+
51
+ POST /api/v1/optimizer/recommendations
52
+ """
53
+ payload = {
54
+ "executionId": execution_id,
55
+ "targetBackend": target_backend,
56
+ "targetDD": target_dd,
57
+ "planOnly": plan_only,
58
+ }
59
+ response = self._client._request(
60
+ method="POST", path="/api/v1/optimizer/recommendations", json=payload
61
+ )
62
+ raise_for_status(response)
63
+ return typing.cast(
64
+ typing.List[typing.Dict[str, typing.Any]],
65
+ response.json().get("data", {}).get("recommendations", []),
66
+ )
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: commutator-sdk
3
+ Version: 0.1.0
4
+ Summary: Commutator Studios Python SDK
5
+ Author-email: Hadi <hadi@commutatorstudios.com>
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: requests>=2.32.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
11
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
12
+ Requires-Dist: responses>=0.23.0; extra == "dev"
13
+ Requires-Dist: black>=24.0.0; extra == "dev"
14
+ Requires-Dist: mypy>=1.9.0; extra == "dev"
15
+ Requires-Dist: rich>=13.0.0; extra == "dev"
16
+ Requires-Dist: pre-commit>=3.5.0; extra == "dev"
@@ -0,0 +1,11 @@
1
+ commutator/__init__.py,sha256=qtRzSu37k2hSszzPaGRpSh4Byw8wksldIn1XPJYkSlQ,383
2
+ commutator/client.py,sha256=Sx2erc6ZfpT4EofYHKgU44OLAnSBmEElk8FqV9XhftQ,5355
3
+ commutator/exceptions.py,sha256=wsbhNebkHNJNhIM4JeStPJWu5u1jdre0eTwgcOOOSDg,3368
4
+ commutator/fleet.py,sha256=ssLdElZlK-AJ54Aze0PuD71vSl44o7d1Npu6QFpKOvE,2578
5
+ commutator/identity.py,sha256=aP0JrYKow3IGuOsZv7BZrF72Xmk9ammvdxcDXhLPRNI,3256
6
+ commutator/jobs.py,sha256=DvhybYwId-4jATj1QK9vdstV7rTNpbitPfun0tLyVcI,5694
7
+ commutator/optimizer.py,sha256=YqE7r_FUMQRrzTlxeFQxs6-JeTbhGHQTRJzHtnZEvjI,2262
8
+ commutator_sdk-0.1.0.dist-info/METADATA,sha256=7UV3F829pYEO-Y053QXbswtkz1FHeAeVZCvewpYb3qU,585
9
+ commutator_sdk-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
10
+ commutator_sdk-0.1.0.dist-info/top_level.txt,sha256=5AcEIflVnu7tXN66zH2MRcDEF1CgoBxwG9sNQjobuDA,11
11
+ commutator_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ commutator