clear-skies-cortex 2.0.3__tar.gz → 2.0.4__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.
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.copier-answers.yml +1 -1
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.github/workflows/docs.yaml +2 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/CHANGELOG.md +11 -0
- clear_skies_cortex-2.0.4/LATEST_CHANGELOG.md +9 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/PKG-INFO +1 -1
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/pyproject.toml +4 -1
- clear_skies_cortex-2.0.4/src/clearskies_cortex/backends/cortex_backend.py +236 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/columns/string_list.py +69 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/dataclasses.py +243 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/defaults/default_cortex_auth.py +55 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/defaults/default_cortex_url.py +45 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_catalog_entity.py +247 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/cortex_catalog_entity_domain.py +29 -1
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_catalog_entity_group.py +47 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_catalog_entity_scorecard.py +82 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/cortex_catalog_entity_service.py +35 -1
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/cortex_catalog_entity_team.py +30 -1
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_catalog_entity_types.py +62 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_entity_relationships.py +64 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_model.py +32 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_scorecard.py +68 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/cortex_team.py +98 -1
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_team_category_tree.py +67 -0
- clear_skies_cortex-2.0.4/src/clearskies_cortex/models/cortex_team_department.py +61 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/uv.lock +17 -1
- clear_skies_cortex-2.0.3/LATEST_CHANGELOG.md +0 -6
- clear_skies_cortex-2.0.3/src/clearskies_cortex/backends/cortex_backend.py +0 -101
- clear_skies_cortex-2.0.3/src/clearskies_cortex/columns/string_list.py +0 -25
- clear_skies_cortex-2.0.3/src/clearskies_cortex/dataclasses.py +0 -73
- clear_skies_cortex-2.0.3/src/clearskies_cortex/defaults/default_cortex_auth.py +0 -9
- clear_skies_cortex-2.0.3/src/clearskies_cortex/defaults/default_cortex_url.py +0 -7
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_catalog_entity.py +0 -90
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_catalog_entity_group.py +0 -22
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_catalog_entity_scorecard.py +0 -33
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_catalog_entity_types.py +0 -25
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_entity_relationships.py +0 -25
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_model.py +0 -9
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_scorecard.py +0 -27
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_team_category_tree.py +0 -27
- clear_skies_cortex-2.0.3/src/clearskies_cortex/models/cortex_team_department.py +0 -25
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.editorconfig +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.github/workflows/create-version.yaml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.github/workflows/run-tests.yml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.github/workflows/tests-matrix.yaml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.github/workflows/tests.yaml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.gitignore +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.pre-commit-config.yaml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.python-version +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/.vscode/settings.json +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/LICENSE +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/README.md +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/cliff.toml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/ruff.toml +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/__init__.py +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/backends/__init__.py +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/backends/cortex_team_relationship_backend.py +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/columns/__init__.py +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/defaults/__init__.py +0 -0
- {clear_skies_cortex-2.0.3 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/__init__.py +0 -0
|
@@ -5,9 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.4] - 2026-01-28
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Update to v0.0.46
|
|
12
|
+
- Document all the files
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Set the count data correctly
|
|
16
|
+
|
|
8
17
|
## [2.0.3] - 2026-01-27
|
|
9
18
|
|
|
10
19
|
### Changed
|
|
20
|
+
- Bump version to v2.0.3 by @github-actions[bot]
|
|
11
21
|
- Udpate to QueryResult
|
|
12
22
|
- Update to latest copier version
|
|
13
23
|
|
|
@@ -41,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
51
|
* @tnijboer made their first contribution in [#1](https://github.com/clearskies-py/cortex/pull/1)
|
|
42
52
|
* @cmancone made their first contribution
|
|
43
53
|
* @ made their first contribution
|
|
54
|
+
[2.0.4]: https://github.com/clearskies-py/cortex/compare/v2.0.3..v2.0.4
|
|
44
55
|
[2.0.3]: https://github.com/clearskies-py/cortex/compare/v2.0.2..v2.0.3
|
|
45
56
|
[2.0.2]: https://github.com/clearskies-py/cortex/compare/v2.0.1..v2.0.2
|
|
46
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clear-skies-cortex
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: Cortex module for Clearskies
|
|
5
5
|
Project-URL: Docs, https://https://clearskies.info/modules/clear-skies-cortex
|
|
6
6
|
Project-URL: Repository, https://github.com/clearskies-py/cortex
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "clear-skies-cortex"
|
|
3
3
|
description = "Cortex module for Clearskies"
|
|
4
|
-
version = "2.0.
|
|
4
|
+
version = "2.0.4"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
readme = "./README.md"
|
|
7
7
|
authors = [{name = "Tom Nijboer", email = "tom.nijboer@cimpress.com"}]
|
|
@@ -65,3 +65,6 @@ dev = [
|
|
|
65
65
|
"pytest-cov>=6.2.1",
|
|
66
66
|
"ruff>=0.12.10",
|
|
67
67
|
]
|
|
68
|
+
doc = [
|
|
69
|
+
"clear-skies-doc-builder>=2.0.11",
|
|
70
|
+
]
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import clearskies
|
|
4
|
+
import requests
|
|
5
|
+
from clearskies import configs
|
|
6
|
+
from clearskies.authentication import Authentication
|
|
7
|
+
from clearskies.decorators import parameters_to_properties
|
|
8
|
+
from clearskies.di import inject
|
|
9
|
+
from clearskies.query import Query
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CortexBackend(clearskies.backends.ApiBackend):
|
|
13
|
+
"""
|
|
14
|
+
Backend for interacting with the Cortex.io API.
|
|
15
|
+
|
|
16
|
+
This backend extends the ApiBackend to provide seamless integration with the Cortex.io platform.
|
|
17
|
+
It handles the specific pagination and response format used by Cortex APIs, where pagination
|
|
18
|
+
information (`page`, `totalPages`, `total`) is returned in the response body rather than headers.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
The CortexBackend is typically used with models that represent Cortex entities:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import clearskies
|
|
26
|
+
from clearskies_cortex.backends import CortexBackend
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CortexService(clearskies.Model):
|
|
30
|
+
backend = CortexBackend()
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def destination_name(cls) -> str:
|
|
34
|
+
return "catalog/services"
|
|
35
|
+
|
|
36
|
+
tag = clearskies.columns.String()
|
|
37
|
+
name = clearskies.columns.String()
|
|
38
|
+
description = clearskies.columns.String()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Authentication
|
|
42
|
+
|
|
43
|
+
By default, the backend uses the `cortex_auth` binding for authentication, which should be
|
|
44
|
+
configured in your application's dependency injection container. You can also provide a custom
|
|
45
|
+
authentication instance:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
backend = CortexBackend(
|
|
49
|
+
authentication=clearskies.authentication.SecretBearer(
|
|
50
|
+
environment_key="CORTEX_API_KEY",
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Pagination
|
|
56
|
+
|
|
57
|
+
The Cortex API uses page-based pagination with the following response format:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"entities": [...],
|
|
62
|
+
"page": 1,
|
|
63
|
+
"totalPages": 5,
|
|
64
|
+
"total": 100
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The backend automatically handles extracting pagination data and provides the next page
|
|
69
|
+
information to clearskies for seamless iteration through results.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
The base URL for the Cortex API.
|
|
74
|
+
"""
|
|
75
|
+
base_url = configs.String(default="https://api.getcortexapp.com/api/v1/")
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
The authentication instance to use for API requests.
|
|
79
|
+
|
|
80
|
+
By default, this uses the `cortex_auth` binding from the dependency injection container.
|
|
81
|
+
"""
|
|
82
|
+
authentication = inject.ByName("cortex_auth") # type: ignore[assignment]
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
The requests instance for making HTTP calls.
|
|
86
|
+
"""
|
|
87
|
+
requests = inject.Requests()
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
The casing style used by the Cortex API (camelCase by default).
|
|
91
|
+
"""
|
|
92
|
+
api_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="camelCase")
|
|
93
|
+
|
|
94
|
+
_auth_headers: dict[str, str] = {}
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
A mapping from API response keys to model column names.
|
|
98
|
+
"""
|
|
99
|
+
api_to_model_map = configs.AnyDict(default={})
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
The name of the pagination parameter used in requests.
|
|
103
|
+
"""
|
|
104
|
+
pagination_parameter_name = configs.String(default="page")
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
The name of the limit parameter used in requests.
|
|
108
|
+
"""
|
|
109
|
+
limit_parameter_name = configs.String(default="pageSize")
|
|
110
|
+
|
|
111
|
+
can_count = False
|
|
112
|
+
|
|
113
|
+
@parameters_to_properties
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
base_url: str | None = "https://api.getcortexapp.com/api/v1/",
|
|
117
|
+
authentication: Authentication | None = None,
|
|
118
|
+
model_casing: str = "snake_case",
|
|
119
|
+
api_casing: str = "camelCase",
|
|
120
|
+
api_to_model_map: dict[str, str | list[str]] = {},
|
|
121
|
+
pagination_parameter_name: str = "page",
|
|
122
|
+
pagination_parameter_type: str = "int",
|
|
123
|
+
limit_parameter_name: str = "pageSize",
|
|
124
|
+
):
|
|
125
|
+
self.finalize_and_validate_configuration()
|
|
126
|
+
|
|
127
|
+
def map_records_response(
|
|
128
|
+
self, response_data: Any, query: Query, query_data: dict[str, Any] | None = None
|
|
129
|
+
) -> list[dict[str, Any]]:
|
|
130
|
+
"""
|
|
131
|
+
Map the Cortex API response to model fields.
|
|
132
|
+
|
|
133
|
+
The Cortex API returns responses in a specific format where the actual records are nested
|
|
134
|
+
within a dictionary alongside pagination metadata. This method extracts the records and
|
|
135
|
+
removes the pagination fields before passing to the parent implementation.
|
|
136
|
+
|
|
137
|
+
Example Cortex API response:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"entities": [{"tag": "service-1", "name": "My Service"}, ...],
|
|
142
|
+
"page": 1,
|
|
143
|
+
"totalPages": 5,
|
|
144
|
+
"total": 100
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This method will extract the `entities` list and pass it to the parent for further processing.
|
|
149
|
+
"""
|
|
150
|
+
if isinstance(response_data, dict):
|
|
151
|
+
if "page" in response_data:
|
|
152
|
+
del response_data["page"]
|
|
153
|
+
del response_data["totalPages"]
|
|
154
|
+
del response_data["total"]
|
|
155
|
+
first_item = next(iter(response_data))
|
|
156
|
+
if isinstance(response_data[first_item], list) and all(
|
|
157
|
+
isinstance(item, dict) for item in response_data[first_item]
|
|
158
|
+
):
|
|
159
|
+
return super().map_records_response(response_data[first_item], query, query_data)
|
|
160
|
+
return super().map_records_response(response_data, query, query_data)
|
|
161
|
+
|
|
162
|
+
def get_next_page_data_from_response(
|
|
163
|
+
self,
|
|
164
|
+
query: Query,
|
|
165
|
+
response: "requests.Response", # type: ignore
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""
|
|
168
|
+
Extract pagination data from the Cortex API response.
|
|
169
|
+
|
|
170
|
+
The Cortex API includes pagination information in the response body:
|
|
171
|
+
|
|
172
|
+
- `page`: The current page number
|
|
173
|
+
- `totalPages`: The total number of pages available
|
|
174
|
+
- `total`: The total number of records
|
|
175
|
+
|
|
176
|
+
This method checks if there are more pages available and returns the next page number
|
|
177
|
+
if so. It also extracts total count information for use in RecordsQueryResult.
|
|
178
|
+
The returned dictionary is used by clearskies to fetch subsequent pages and
|
|
179
|
+
populate count metadata.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
A dictionary containing:
|
|
183
|
+
- The next page number if more pages exist
|
|
184
|
+
- total_count: The total number of records (if available)
|
|
185
|
+
- total_pages: The total number of pages (if available)
|
|
186
|
+
"""
|
|
187
|
+
next_page_data: dict[str, Any] = {}
|
|
188
|
+
|
|
189
|
+
response_data = response.json() if response.content else {}
|
|
190
|
+
|
|
191
|
+
if isinstance(response_data, dict):
|
|
192
|
+
# Extract count information from response body
|
|
193
|
+
count_info = self.extract_count_from_response(None, response_data)
|
|
194
|
+
if count_info:
|
|
195
|
+
total_count, total_pages = count_info
|
|
196
|
+
if total_count is not None:
|
|
197
|
+
next_page_data["total_count"] = total_count
|
|
198
|
+
if total_pages is not None:
|
|
199
|
+
next_page_data["total_pages"] = total_pages
|
|
200
|
+
|
|
201
|
+
# Check if there are more pages
|
|
202
|
+
page = response_data.get("page", None)
|
|
203
|
+
total_pages_from_response = response_data.get("totalPages", None)
|
|
204
|
+
if page is not None and total_pages_from_response is not None and page < total_pages_from_response:
|
|
205
|
+
next_page_data[self.pagination_parameter_name] = page + 1
|
|
206
|
+
|
|
207
|
+
return next_page_data
|
|
208
|
+
|
|
209
|
+
def extract_count_from_response(
|
|
210
|
+
self,
|
|
211
|
+
response_headers: dict[str, str] | None = None,
|
|
212
|
+
response_data: Any = None,
|
|
213
|
+
) -> tuple[int | None, int | None]:
|
|
214
|
+
"""
|
|
215
|
+
Extract count information from the Cortex API response body.
|
|
216
|
+
|
|
217
|
+
Unlike many APIs that return count information in headers, the Cortex API includes
|
|
218
|
+
this data in the response body:
|
|
219
|
+
|
|
220
|
+
- `total`: The total number of records matching the query
|
|
221
|
+
- `totalPages`: The total number of pages available
|
|
222
|
+
|
|
223
|
+
This method extracts these values and returns them as a tuple for use in
|
|
224
|
+
`RecordsQueryResult`.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
A tuple of (total_count, total_pages) where either value may be None
|
|
228
|
+
if not present in the response.
|
|
229
|
+
"""
|
|
230
|
+
if not isinstance(response_data, dict):
|
|
231
|
+
return (None, None)
|
|
232
|
+
|
|
233
|
+
total_count = response_data.get("total", None)
|
|
234
|
+
total_pages = response_data.get("totalPages", None)
|
|
235
|
+
|
|
236
|
+
return (total_count, total_pages)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from clearskies.columns import String
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StringList(String):
|
|
7
|
+
"""
|
|
8
|
+
Column type for comma-delimited string lists.
|
|
9
|
+
|
|
10
|
+
This column type extends the String column to handle values that are stored as
|
|
11
|
+
comma-delimited strings but should be represented as Python lists. It automatically
|
|
12
|
+
converts between the two formats when reading from and writing to the backend.
|
|
13
|
+
|
|
14
|
+
Use this column type when the API returns or expects comma-separated values that
|
|
15
|
+
you want to work with as lists in your application code.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from clearskies import Model
|
|
19
|
+
from clearskies_cortex.columns import StringList
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MyModel(Model):
|
|
23
|
+
# Define a column that stores ["tag1", "tag2"] as "tag1,tag2"
|
|
24
|
+
tags = StringList()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# When reading from the backend:
|
|
28
|
+
# API returns: {"tags": "tag1,tag2,tag3"}
|
|
29
|
+
# Model provides: model.tags = ["tag1", "tag2", "tag3"]
|
|
30
|
+
|
|
31
|
+
# When writing to the backend:
|
|
32
|
+
# Model has: model.tags = ["tag1", "tag2", "tag3"]
|
|
33
|
+
# API receives: {"tags": "tag1,tag2,tag3"}
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def from_backend(self, value: str | list[str]) -> list[str]:
|
|
38
|
+
"""
|
|
39
|
+
Convert backend value to a Python list.
|
|
40
|
+
|
|
41
|
+
Handles both string (comma-delimited) and list inputs for flexibility.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
value: Either a comma-delimited string or a list of strings.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A list of strings.
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(value, list):
|
|
50
|
+
return value
|
|
51
|
+
return value.split(",")
|
|
52
|
+
|
|
53
|
+
def to_backend(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
54
|
+
"""
|
|
55
|
+
Convert Python list to comma-delimited string for backend storage.
|
|
56
|
+
|
|
57
|
+
Transforms the list value back into a comma-separated string format
|
|
58
|
+
expected by the backend API.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
data: Dictionary containing the column data.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dictionary with the column value converted to a comma-delimited string.
|
|
65
|
+
"""
|
|
66
|
+
if self.name not in data:
|
|
67
|
+
return data
|
|
68
|
+
|
|
69
|
+
return {**data, self.name: str(",".join(data[self.name]))}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class ServiceEntityHierarchy:
|
|
7
|
+
"""
|
|
8
|
+
Dataclass representing the hierarchy structure of a service entity.
|
|
9
|
+
|
|
10
|
+
This dataclass is used to parse the hierarchy JSON data from Cortex catalog entities.
|
|
11
|
+
It contains both parent and child relationships for navigating the entity hierarchy.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from clearskies_cortex.models import CortexCatalogEntity
|
|
15
|
+
|
|
16
|
+
entity = CortexCatalogEntity().find("tag=my-service")
|
|
17
|
+
hierarchy = entity.parse_hierarchy()
|
|
18
|
+
|
|
19
|
+
# Access parents
|
|
20
|
+
for parent in hierarchy.parents:
|
|
21
|
+
print(f"Parent: {parent.name} ({parent.type})")
|
|
22
|
+
|
|
23
|
+
# Access children
|
|
24
|
+
for child in hierarchy.children:
|
|
25
|
+
print(f"Child: {child.name} ({child.type})")
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
List of parent entities in the hierarchy (from immediate parent to root).
|
|
31
|
+
"""
|
|
32
|
+
parents: list["ServiceEntityHierarchyParent"]
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
List of child entities in the hierarchy.
|
|
36
|
+
"""
|
|
37
|
+
children: list["ServiceEntityHierarchyChild"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ServiceEntityHierarchyParent:
|
|
42
|
+
"""
|
|
43
|
+
Dataclass representing a parent entity in the hierarchy.
|
|
44
|
+
|
|
45
|
+
Contains information about a parent entity including its tag, type, name,
|
|
46
|
+
and recursively its own parents for traversing up the hierarchy tree.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
The unique tag identifier of the parent entity.
|
|
51
|
+
"""
|
|
52
|
+
tag: str
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
The type of the parent entity (e.g., "domain", "team").
|
|
56
|
+
"""
|
|
57
|
+
type: str
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
The human-readable name of the parent entity.
|
|
61
|
+
"""
|
|
62
|
+
name: str
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
Optional description of the parent entity.
|
|
66
|
+
"""
|
|
67
|
+
description: str | None
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
Optional definition data for the parent entity.
|
|
71
|
+
"""
|
|
72
|
+
definition: None | dict[str, Any]
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
List of grandparent entities (recursive parent structure).
|
|
76
|
+
"""
|
|
77
|
+
parents: list["ServiceEntityHierarchyParent"]
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
Optional list of groups the parent entity belongs to.
|
|
81
|
+
"""
|
|
82
|
+
groups: list[str] | None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class ServiceEntityHierarchyChild:
|
|
87
|
+
"""
|
|
88
|
+
Dataclass representing a child entity in the hierarchy.
|
|
89
|
+
|
|
90
|
+
Contains information about a child entity including its tag, type, name,
|
|
91
|
+
and recursively its own children for traversing down the hierarchy tree.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
The unique tag identifier of the child entity.
|
|
96
|
+
"""
|
|
97
|
+
tag: str
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
The type of the child entity (e.g., "service", "domain").
|
|
101
|
+
"""
|
|
102
|
+
type: str
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
The human-readable name of the child entity.
|
|
106
|
+
"""
|
|
107
|
+
name: str
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
Optional description of the child entity.
|
|
111
|
+
"""
|
|
112
|
+
description: str | None
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
Optional definition data for the child entity.
|
|
116
|
+
"""
|
|
117
|
+
definition: None | dict[str, Any]
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
List of grandchild entities (recursive child structure).
|
|
121
|
+
"""
|
|
122
|
+
children: list["ServiceEntityHierarchyChild"]
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
Optional list of groups the child entity belongs to.
|
|
126
|
+
"""
|
|
127
|
+
groups: list[str] | None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class TeamCategory:
|
|
132
|
+
"""
|
|
133
|
+
Dataclass representing a team category in the category tree.
|
|
134
|
+
|
|
135
|
+
Used for organizing teams into hierarchical categories with parent-child relationships.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
The name of the category.
|
|
140
|
+
"""
|
|
141
|
+
name: str
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
The depth level in the category hierarchy (0 for root).
|
|
145
|
+
"""
|
|
146
|
+
level: int
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
The name of the parent category.
|
|
150
|
+
"""
|
|
151
|
+
parent_name: str
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class EntityTeam:
|
|
156
|
+
"""
|
|
157
|
+
Dataclass representing a team associated with a catalog entity.
|
|
158
|
+
|
|
159
|
+
Contains team information including ownership inheritance and provider details.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
Optional description of the team.
|
|
164
|
+
"""
|
|
165
|
+
description: str | None
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
The inheritance type for ownership (e.g., "direct", "inherited").
|
|
169
|
+
"""
|
|
170
|
+
inheritance: str
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
Whether the team is archived.
|
|
174
|
+
"""
|
|
175
|
+
isArchived: bool # noqa: N815
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
The human-readable name of the team.
|
|
179
|
+
"""
|
|
180
|
+
name: str
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
The provider of the team data (e.g., "cortex", "external").
|
|
184
|
+
"""
|
|
185
|
+
provider: str
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
The unique tag identifier of the team.
|
|
189
|
+
"""
|
|
190
|
+
tag: str
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass
|
|
194
|
+
class EntityIndividual:
|
|
195
|
+
"""
|
|
196
|
+
Dataclass representing an individual owner of a catalog entity.
|
|
197
|
+
|
|
198
|
+
Contains information about individual (non-team) owners.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
Optional description of the individual.
|
|
203
|
+
"""
|
|
204
|
+
description: str | None
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
The email address of the individual.
|
|
208
|
+
"""
|
|
209
|
+
email: str
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@dataclass
|
|
213
|
+
class EntityTeamOwner:
|
|
214
|
+
"""
|
|
215
|
+
Dataclass representing the ownership information for a catalog entity.
|
|
216
|
+
|
|
217
|
+
Contains both team and individual owners. Used by `CortexCatalogEntity.parse_owners()`.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from clearskies_cortex.models import CortexCatalogEntity
|
|
221
|
+
|
|
222
|
+
entity = CortexCatalogEntity().find("tag=my-service")
|
|
223
|
+
owners = entity.parse_owners()
|
|
224
|
+
|
|
225
|
+
# Access team owners
|
|
226
|
+
for team in owners.teams:
|
|
227
|
+
print(f"Team owner: {team.name} ({team.tag})")
|
|
228
|
+
|
|
229
|
+
# Access individual owners
|
|
230
|
+
for individual in owners.individuals:
|
|
231
|
+
print(f"Individual owner: {individual.email}")
|
|
232
|
+
```
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
List of teams that own the entity.
|
|
237
|
+
"""
|
|
238
|
+
teams: list[EntityTeam]
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
List of individuals that own the entity.
|
|
242
|
+
"""
|
|
243
|
+
individuals: list[EntityIndividual]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import clearskies
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DefaultCortexAuth(clearskies.di.AdditionalConfigAutoImport):
|
|
5
|
+
"""
|
|
6
|
+
Default authentication provider for Cortex API.
|
|
7
|
+
|
|
8
|
+
This class provides automatic configuration for Cortex API authentication using
|
|
9
|
+
the clearskies dependency injection system. It is auto-imported when the clearskies_cortex
|
|
10
|
+
package is used, making authentication configuration seamless.
|
|
11
|
+
|
|
12
|
+
The authentication can be configured in two ways:
|
|
13
|
+
|
|
14
|
+
1. **Secret Path (recommended for production)**: Set the `CORTEX_AUTH_SECRET_PATH` environment
|
|
15
|
+
variable to point to a secret manager path containing the API key.
|
|
16
|
+
|
|
17
|
+
2. **Direct Environment Key**: Set the `CORTEX_AUTH_KEY` environment variable directly
|
|
18
|
+
with the Cortex API key.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import clearskies
|
|
22
|
+
from clearskies_cortex.models import CortexTeam
|
|
23
|
+
|
|
24
|
+
# The authentication is automatically configured via environment variables
|
|
25
|
+
# Option 1: Using secret path
|
|
26
|
+
# export CORTEX_AUTH_SECRET_PATH=/path/to/secret
|
|
27
|
+
|
|
28
|
+
# Option 2: Using direct key
|
|
29
|
+
# export CORTEX_AUTH_KEY=your-api-key
|
|
30
|
+
|
|
31
|
+
# Then use models normally - auth is handled automatically
|
|
32
|
+
teams = CortexTeam()
|
|
33
|
+
for team in teams:
|
|
34
|
+
print(team.get_name())
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def provide_cortex_auth(self, environment: clearskies.Environment):
|
|
39
|
+
"""
|
|
40
|
+
Provide the Cortex authentication configuration.
|
|
41
|
+
|
|
42
|
+
Checks for `CORTEX_AUTH_SECRET_PATH` first, falling back to `CORTEX_AUTH_KEY`
|
|
43
|
+
if not set. Returns a SecretBearer authentication instance configured for
|
|
44
|
+
the Cortex API.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
environment: The clearskies Environment instance for accessing env variables.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A SecretBearer authentication instance configured for Cortex API.
|
|
51
|
+
"""
|
|
52
|
+
if environment.get("CORTEX_AUTH_SECRET_PATH", True):
|
|
53
|
+
secret_key = environment.get("CORTEX_AUTH_SECRET_PATH")
|
|
54
|
+
return clearskies.authentication.SecretBearer(secret_key=secret_key, header_prefix="Bearer ")
|
|
55
|
+
return clearskies.authentication.SecretBearer(environment_key="CORTEX_AUTH_KEY", header_prefix="Bearer ")
|