getdx 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.
Files changed (43) hide show
  1. getdx/__init__.py +51 -0
  2. getdx/client.py +71 -0
  3. getdx/config.py +95 -0
  4. getdx/data_cloud/__init__.py +4 -0
  5. getdx/data_cloud/client.py +301 -0
  6. getdx/data_cloud/operations.py +319 -0
  7. getdx/data_cloud/services/__init__.py +28 -0
  8. getdx/data_cloud/services/ai_tool_metrics.py +60 -0
  9. getdx/data_cloud/services/custom_data.py +137 -0
  10. getdx/data_cloud/services/deployments.py +68 -0
  11. getdx/data_cloud/services/incidents.py +46 -0
  12. getdx/data_cloud/services/pipeline_runs.py +58 -0
  13. getdx/data_cloud/services/repo_groups.py +132 -0
  14. getdx/errors.py +77 -0
  15. getdx/helpers.py +20 -0
  16. getdx/http/__init__.py +3 -0
  17. getdx/http/retry.py +49 -0
  18. getdx/http/transport.py +135 -0
  19. getdx/py.typed +0 -0
  20. getdx/types.py +12 -0
  21. getdx/web/__init__.py +12 -0
  22. getdx/web/aggregates.py +165 -0
  23. getdx/web/client.py +55 -0
  24. getdx/web/operations.py +822 -0
  25. getdx/web/services/__init__.py +52 -0
  26. getdx/web/services/entities.py +201 -0
  27. getdx/web/services/entity_relations.py +38 -0
  28. getdx/web/services/entity_types.py +133 -0
  29. getdx/web/services/events.py +36 -0
  30. getdx/web/services/initiatives.py +74 -0
  31. getdx/web/services/orgfiles.py +113 -0
  32. getdx/web/services/platformx.py +56 -0
  33. getdx/web/services/queries.py +31 -0
  34. getdx/web/services/scorecards.py +121 -0
  35. getdx/web/services/snapshots.py +93 -0
  36. getdx/web/services/teams.py +94 -0
  37. getdx/web/services/user_groups.py +123 -0
  38. getdx/web/services/users.py +77 -0
  39. getdx/web/services/workflow_runs.py +121 -0
  40. getdx-0.1.0.dist-info/METADATA +146 -0
  41. getdx-0.1.0.dist-info/RECORD +43 -0
  42. getdx-0.1.0.dist-info/WHEEL +4 -0
  43. getdx-0.1.0.dist-info/licenses/LICENSE +21 -0
getdx/__init__.py ADDED
@@ -0,0 +1,51 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("getdx")
5
+ except PackageNotFoundError:
6
+ __version__ = "unknown"
7
+
8
+ from getdx.client import DXClient
9
+ from getdx.config import DXClientConfig, DXDataCloudConfig, DXWebConfig, RetryConfig
10
+ from getdx.data_cloud.client import DXDataCloudClient
11
+ from getdx.errors import (
12
+ DXAPIError,
13
+ DXArgumentError,
14
+ DXAuthError,
15
+ DXClientConfigurationError,
16
+ DXClientNotConfiguredError,
17
+ DXFeatureUnavailableError,
18
+ DXPermissionError,
19
+ DXRateLimitError,
20
+ DXResponseError,
21
+ DXServerError,
22
+ DXTransportError,
23
+ DXValidationError,
24
+ )
25
+ from getdx.web.aggregates import EntityOverview, EntityRelationsGraph
26
+ from getdx.web.client import DXWebClient
27
+
28
+ __all__ = [
29
+ "DXAPIError",
30
+ "DXArgumentError",
31
+ "DXAuthError",
32
+ "DXClient",
33
+ "DXClientConfig",
34
+ "DXClientConfigurationError",
35
+ "DXClientNotConfiguredError",
36
+ "DXDataCloudClient",
37
+ "DXDataCloudConfig",
38
+ "DXFeatureUnavailableError",
39
+ "DXPermissionError",
40
+ "DXRateLimitError",
41
+ "DXResponseError",
42
+ "DXServerError",
43
+ "DXTransportError",
44
+ "DXValidationError",
45
+ "DXWebClient",
46
+ "DXWebConfig",
47
+ "EntityOverview",
48
+ "EntityRelationsGraph",
49
+ "RetryConfig",
50
+ "__version__",
51
+ ]
getdx/client.py ADDED
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from getdx.config import DXClientConfig, DXDataCloudConfig, DXWebConfig
4
+ from getdx.data_cloud.client import DXDataCloudClient
5
+ from getdx.errors import DXClientConfigurationError, DXClientNotConfiguredError
6
+ from getdx.web.client import DXWebClient
7
+
8
+
9
+ class DXClient:
10
+ def __init__(
11
+ self,
12
+ *,
13
+ web: DXWebConfig | None = None,
14
+ data_cloud: DXDataCloudConfig | None = None,
15
+ config: DXClientConfig | None = None,
16
+ ) -> None:
17
+ resolved_web = web if web is not None else (config.web if config is not None else None)
18
+ resolved_data_cloud = (
19
+ data_cloud
20
+ if data_cloud is not None
21
+ else (config.data_cloud if config is not None else None)
22
+ )
23
+
24
+ if resolved_web is None and resolved_data_cloud is None:
25
+ raise DXClientConfigurationError(
26
+ "DXClient requires at least one API config. Provide web=DXWebConfig(...) "
27
+ "and/or data_cloud=DXDataCloudConfig(...)."
28
+ )
29
+
30
+ self._web = DXWebClient(resolved_web) if resolved_web is not None else None
31
+ self._data_cloud = (
32
+ DXDataCloudClient(resolved_data_cloud) if resolved_data_cloud is not None else None
33
+ )
34
+
35
+ @property
36
+ def has_web(self) -> bool:
37
+ return self._web is not None
38
+
39
+ @property
40
+ def has_data_cloud(self) -> bool:
41
+ return self._data_cloud is not None
42
+
43
+ @property
44
+ def web(self) -> DXWebClient:
45
+ if self._web is None:
46
+ raise DXClientNotConfiguredError(
47
+ "DXClient.web is not configured. "
48
+ "Pass web=DXWebConfig(...) when constructing DXClient."
49
+ )
50
+ return self._web
51
+
52
+ @property
53
+ def data_cloud(self) -> DXDataCloudClient:
54
+ if self._data_cloud is None:
55
+ raise DXClientNotConfiguredError(
56
+ "DXClient.data_cloud is not configured. Pass data_cloud=DXDataCloudConfig(...) "
57
+ "when constructing DXClient."
58
+ )
59
+ return self._data_cloud
60
+
61
+ def close(self) -> None:
62
+ if self._web is not None:
63
+ self._web.close()
64
+ if self._data_cloud is not None:
65
+ self._data_cloud.close()
66
+
67
+ def __enter__(self) -> DXClient:
68
+ return self
69
+
70
+ def __exit__(self, exc_type, exc, tb) -> None: # type: ignore[no-untyped-def]
71
+ self.close()
getdx/config.py ADDED
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+
6
+ DEFAULT_WEB_BASE_URL = "https://api.getdx.com"
7
+ DEFAULT_TIMEOUT_SECONDS = 30.0
8
+ DEFAULT_DATA_CLOUD_URL_TEMPLATE = "https://{instance}.getdx.net/api"
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class RetryConfig:
13
+ max_attempts: int = 3
14
+ backoff_base_seconds: float = 0.25
15
+ backoff_max_seconds: float = 2.0
16
+ retry_statuses: tuple[int, ...] = (429, 500, 502, 503, 504)
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class APITransportConfig:
21
+ base_url: str
22
+ timeout_seconds: float
23
+ user_agent: str
24
+ retry: RetryConfig
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class DXWebConfig:
29
+ token: str | None = None
30
+ base_url: str = DEFAULT_WEB_BASE_URL
31
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
32
+ user_agent: str = "getdx/web"
33
+ retry: RetryConfig = field(default_factory=RetryConfig)
34
+
35
+ def resolved_token(self) -> str:
36
+ token = self.token or os.getenv("DX_WEB_API_TOKEN")
37
+ if not token:
38
+ raise ValueError(
39
+ "DX Web API token missing. Set DX_WEB_API_TOKEN or pass token in DXWebConfig."
40
+ )
41
+ return token
42
+
43
+ def resolved_transport_config(self) -> APITransportConfig:
44
+ return APITransportConfig(
45
+ base_url=self.base_url.rstrip("/"),
46
+ timeout_seconds=self.timeout_seconds,
47
+ user_agent=self.user_agent,
48
+ retry=self.retry,
49
+ )
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class DXDataCloudConfig:
54
+ token: str | None = None
55
+ instance: str | None = None
56
+ base_url: str | None = None
57
+ timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS
58
+ user_agent: str = "getdx/data-cloud"
59
+ retry: RetryConfig = field(default_factory=RetryConfig)
60
+
61
+ def resolved_token(self) -> str:
62
+ token = self.token or os.getenv("DX_DATA_CLOUD_TOKEN")
63
+ if not token:
64
+ raise ValueError(
65
+ "DX Data Cloud token missing. "
66
+ "Set DX_DATA_CLOUD_TOKEN or pass token in DXDataCloudConfig."
67
+ )
68
+ return token
69
+
70
+ def resolved_base_url(self) -> str:
71
+ if self.base_url:
72
+ return self.base_url.rstrip("/")
73
+
74
+ instance = self.instance or os.getenv("DX_DATA_CLOUD_INSTANCE")
75
+ if not instance:
76
+ raise ValueError(
77
+ "DX Data Cloud base URL cannot be resolved. "
78
+ "Set DX_DATA_CLOUD_INSTANCE or pass instance/base_url in DXDataCloudConfig."
79
+ )
80
+
81
+ return DEFAULT_DATA_CLOUD_URL_TEMPLATE.format(instance=instance).rstrip("/")
82
+
83
+ def resolved_transport_config(self) -> APITransportConfig:
84
+ return APITransportConfig(
85
+ base_url=self.resolved_base_url(),
86
+ timeout_seconds=self.timeout_seconds,
87
+ user_agent=self.user_agent,
88
+ retry=self.retry,
89
+ )
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class DXClientConfig:
94
+ web: DXWebConfig | None = None
95
+ data_cloud: DXDataCloudConfig | None = None
@@ -0,0 +1,4 @@
1
+ from getdx.data_cloud.client import DXDataCloudClient
2
+ from getdx.data_cloud.operations import OPERATION_INDEX, OPERATIONS
3
+
4
+ __all__ = ["OPERATIONS", "OPERATION_INDEX", "DXDataCloudClient"]
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import Any
5
+
6
+ from getdx.config import DXDataCloudConfig
7
+ from getdx.data_cloud.services import (
8
+ AiToolMetricsService,
9
+ CustomDataService,
10
+ DeploymentsService,
11
+ IncidentsService,
12
+ PipelineRunsService,
13
+ RepoGroupsService,
14
+ )
15
+ from getdx.errors import DXArgumentError
16
+ from getdx.helpers import JSONReturn
17
+ from getdx.http.transport import DXTransport
18
+ from getdx.types import BodyValue
19
+
20
+
21
+ class CustomDataAPI:
22
+ def __init__(self, service: CustomDataService) -> None:
23
+ self._service = service
24
+
25
+ def get(
26
+ self,
27
+ *,
28
+ id: str | None = None,
29
+ reference: str | None = None,
30
+ key: str | None = None,
31
+ ) -> JSONReturn:
32
+ if id is not None:
33
+ if reference is not None or key is not None:
34
+ raise DXArgumentError(
35
+ "Pass either id or reference+key for custom_data.get, not both."
36
+ )
37
+ return self._service.get(id=id)
38
+
39
+ if reference is not None and key is not None:
40
+ return self._service.get(reference=reference, key=key)
41
+
42
+ raise DXArgumentError("custom_data.get requires id or both reference and key.")
43
+
44
+ def get_all_by_reference(self, reference: str) -> JSONReturn:
45
+ return self._service.get_all_by_reference(reference=reference)
46
+
47
+ def set(
48
+ self,
49
+ *,
50
+ reference: BodyValue,
51
+ key: BodyValue,
52
+ value: BodyValue,
53
+ timestamp: BodyValue | None = None,
54
+ ) -> JSONReturn:
55
+ return self._service.set(reference=reference, key=key, value=value, timestamp=timestamp)
56
+
57
+ def set_all(self, *, data: BodyValue) -> JSONReturn:
58
+ return self._service.set_all(data=data)
59
+
60
+ def delete(
61
+ self,
62
+ *,
63
+ id: str | None = None,
64
+ reference: str | None = None,
65
+ key: str | None = None,
66
+ ) -> JSONReturn:
67
+ if id is not None:
68
+ if reference is not None or key is not None:
69
+ raise DXArgumentError(
70
+ "Pass either id or reference+key for custom_data.delete, not both."
71
+ )
72
+ return self._service.delete(payload={"id": id})
73
+
74
+ if reference is not None and key is not None:
75
+ return self._service.delete(payload={"reference": reference, "key": key})
76
+
77
+ raise DXArgumentError("custom_data.delete requires id or both reference and key.")
78
+
79
+ def delete_by_id(self, id: str) -> JSONReturn:
80
+ return self._service.delete(payload={"id": id})
81
+
82
+ def delete_by_reference_key(self, reference: str, key: str) -> JSONReturn:
83
+ return self._service.delete(payload={"reference": reference, "key": key})
84
+
85
+ def delete_all_by_reference(self, *, reference: BodyValue) -> JSONReturn:
86
+ return self._service.delete_all_by_reference(reference=reference)
87
+
88
+
89
+ class RepoGroupsAPI:
90
+ def __init__(self, service: RepoGroupsService) -> None:
91
+ self._service = service
92
+
93
+ def get(self, *, id: str | None = None, reference_id: str | None = None) -> JSONReturn:
94
+ selector = _exactly_one_selector(
95
+ id=id, reference_id=reference_id, method_name="repo_groups.get"
96
+ )
97
+ return self._service.get(**selector)
98
+
99
+ def delete(
100
+ self, *, id: BodyValue | None = None, reference_id: BodyValue | None = None
101
+ ) -> JSONReturn:
102
+ selector = _exactly_one_selector(
103
+ id=id,
104
+ reference_id=reference_id,
105
+ method_name="repo_groups.delete",
106
+ )
107
+ return self._service.delete(**selector)
108
+
109
+ def upsert(
110
+ self,
111
+ *,
112
+ name: BodyValue,
113
+ id: BodyValue | None = None,
114
+ reference_id: BodyValue | None = None,
115
+ parent_id: BodyValue | None = None,
116
+ repos: BodyValue | None = None,
117
+ ) -> JSONReturn:
118
+ return self._service.upsert(
119
+ name=name,
120
+ id=id,
121
+ reference_id=reference_id,
122
+ parent_id=parent_id,
123
+ repos=repos,
124
+ )
125
+
126
+ def add_repos(
127
+ self,
128
+ *,
129
+ repos: BodyValue,
130
+ id: BodyValue | None = None,
131
+ reference_id: BodyValue | None = None,
132
+ ) -> JSONReturn:
133
+ selector = _exactly_one_selector(
134
+ id=id,
135
+ reference_id=reference_id,
136
+ method_name="repo_groups.add_repos",
137
+ )
138
+ return self._service.add_repos(repos=repos, **selector)
139
+
140
+ def remove_repos(
141
+ self,
142
+ *,
143
+ repos: BodyValue,
144
+ id: BodyValue | None = None,
145
+ reference_id: BodyValue | None = None,
146
+ ) -> JSONReturn:
147
+ selector = _exactly_one_selector(
148
+ id=id,
149
+ reference_id=reference_id,
150
+ method_name="repo_groups.remove_repos",
151
+ )
152
+ return self._service.remove_repos(repos=repos, **selector)
153
+
154
+
155
+ class DeploymentsAPI:
156
+ def __init__(self, service: DeploymentsService) -> None:
157
+ self._service = service
158
+
159
+ def create(
160
+ self,
161
+ *,
162
+ deployed_at: BodyValue,
163
+ service: BodyValue,
164
+ commit_sha: BodyValue | None = None,
165
+ integration_branch: BodyValue | None = None,
166
+ merge_commit_shas: BodyValue | None = None,
167
+ metadata: BodyValue | None = None,
168
+ reference_id: BodyValue | None = None,
169
+ repository: BodyValue | None = None,
170
+ source_name: BodyValue | None = None,
171
+ source_url: BodyValue | None = None,
172
+ success: BodyValue | None = None,
173
+ ) -> JSONReturn:
174
+ return self._service.create(
175
+ deployed_at=deployed_at,
176
+ service=service,
177
+ commit_sha=commit_sha,
178
+ integration_branch=integration_branch,
179
+ merge_commit_shas=merge_commit_shas,
180
+ metadata=metadata,
181
+ reference_id=reference_id,
182
+ repository=repository,
183
+ source_name=source_name,
184
+ source_url=source_url,
185
+ success=success,
186
+ )
187
+
188
+ def set_pull_services(self, *, payload: dict[str, Any]) -> JSONReturn:
189
+ return self._service.set_pull_services(payload=payload)
190
+
191
+ def set_pull_services_by_github_pull_id(
192
+ self,
193
+ *,
194
+ github_pull_id: int,
195
+ services: Sequence[dict[str, Any]],
196
+ ) -> JSONReturn:
197
+ return self._service.set_pull_services(
198
+ payload={
199
+ "github_pull_id": github_pull_id,
200
+ "services": list(services),
201
+ }
202
+ )
203
+
204
+ def set_pull_services_by_repo_and_number(
205
+ self,
206
+ *,
207
+ repository: str,
208
+ pull_number: int,
209
+ services: Sequence[dict[str, Any]],
210
+ ) -> JSONReturn:
211
+ return self._service.set_pull_services(
212
+ payload={
213
+ "repository": repository,
214
+ "pull_number": pull_number,
215
+ "services": list(services),
216
+ }
217
+ )
218
+
219
+
220
+ class PipelineRunsAPI:
221
+ def __init__(self, service: PipelineRunsService) -> None:
222
+ self._service = service
223
+
224
+ def upsert(
225
+ self,
226
+ *,
227
+ pipeline_name: BodyValue,
228
+ pipeline_source: BodyValue,
229
+ reference_id: BodyValue,
230
+ started_at: BodyValue,
231
+ commit_sha: BodyValue | None = None,
232
+ email: BodyValue | None = None,
233
+ finished_at: BodyValue | None = None,
234
+ github_username: BodyValue | None = None,
235
+ gitlab_username: BodyValue | None = None,
236
+ head_branch: BodyValue | None = None,
237
+ pr_number: BodyValue | None = None,
238
+ repository: BodyValue | None = None,
239
+ source_url: BodyValue | None = None,
240
+ status: BodyValue | None = None,
241
+ ) -> JSONReturn:
242
+ if (commit_sha is not None or pr_number is not None) and repository is None:
243
+ raise DXArgumentError(
244
+ "pipeline_runs.upsert requires repository when commit_sha/pr_number is set."
245
+ )
246
+
247
+ return self._service.upsert(
248
+ pipeline_name=pipeline_name,
249
+ pipeline_source=pipeline_source,
250
+ reference_id=reference_id,
251
+ started_at=started_at,
252
+ commit_sha=commit_sha,
253
+ email=email,
254
+ finished_at=finished_at,
255
+ github_username=github_username,
256
+ gitlab_username=gitlab_username,
257
+ head_branch=head_branch,
258
+ pr_number=pr_number,
259
+ repository=repository,
260
+ source_url=source_url,
261
+ status=status,
262
+ )
263
+
264
+
265
+ class DXDataCloudClient:
266
+ def __init__(self, config: DXDataCloudConfig) -> None:
267
+ self._config = config
268
+ self._transport = DXTransport(
269
+ config=config.resolved_transport_config(),
270
+ token=config.resolved_token(),
271
+ api_name="data_cloud",
272
+ )
273
+
274
+ self.ai_tool_metrics = AiToolMetricsService(self._transport)
275
+ self.custom_data = CustomDataAPI(CustomDataService(self._transport))
276
+ self.deployments = DeploymentsAPI(DeploymentsService(self._transport))
277
+ self.incidents = IncidentsService(self._transport)
278
+ self.pipeline_runs = PipelineRunsAPI(PipelineRunsService(self._transport))
279
+ self.repo_groups = RepoGroupsAPI(RepoGroupsService(self._transport))
280
+
281
+ @property
282
+ def config(self) -> DXDataCloudConfig:
283
+ return self._config
284
+
285
+ def close(self) -> None:
286
+ self._transport.close()
287
+
288
+
289
+ def _exactly_one_selector(
290
+ *,
291
+ id: Any,
292
+ reference_id: Any,
293
+ method_name: str,
294
+ ) -> dict[str, Any]:
295
+ if id is not None and reference_id is not None:
296
+ raise DXArgumentError(f"{method_name} accepts either id or reference_id, not both.")
297
+ if id is None and reference_id is None:
298
+ raise DXArgumentError(f"{method_name} requires id or reference_id.")
299
+ if id is not None:
300
+ return {"id": id}
301
+ return {"reference_id": reference_id}