clear-skies-cortex 2.0.2__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.2 → clear_skies_cortex-2.0.4}/.copier-answers.yml +1 -1
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.github/workflows/create-version.yaml +1 -1
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.github/workflows/docs.yaml +2 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.github/workflows/run-tests.yml +2 -1
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.github/workflows/tests-matrix.yaml +6 -19
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.pre-commit-config.yaml +1 -7
- clear_skies_cortex-2.0.4/.vscode/settings.json +21 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/CHANGELOG.md +19 -0
- clear_skies_cortex-2.0.4/LATEST_CHANGELOG.md +9 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/PKG-INFO +2 -2
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/pyproject.toml +5 -16
- clear_skies_cortex-2.0.4/src/clearskies_cortex/backends/cortex_backend.py +236 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/backends/cortex_team_relationship_backend.py +20 -13
- 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.2 → 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.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/cortex_catalog_entity_service.py +35 -1
- {clear_skies_cortex-2.0.2 → 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.2 → 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.4/uv.lock +999 -0
- clear_skies_cortex-2.0.2/LATEST_CHANGELOG.md +0 -11
- clear_skies_cortex-2.0.2/src/clearskies_cortex/backends/cortex_backend.py +0 -100
- clear_skies_cortex-2.0.2/src/clearskies_cortex/columns/string_list.py +0 -25
- clear_skies_cortex-2.0.2/src/clearskies_cortex/dataclasses.py +0 -73
- clear_skies_cortex-2.0.2/src/clearskies_cortex/defaults/default_cortex_auth.py +0 -9
- clear_skies_cortex-2.0.2/src/clearskies_cortex/defaults/default_cortex_url.py +0 -7
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_catalog_entity.py +0 -90
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_catalog_entity_group.py +0 -22
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_catalog_entity_scorecard.py +0 -33
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_catalog_entity_types.py +0 -25
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_entity_relationships.py +0 -25
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_model.py +0 -9
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_scorecard.py +0 -27
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_team_category_tree.py +0 -27
- clear_skies_cortex-2.0.2/src/clearskies_cortex/models/cortex_team_department.py +0 -25
- clear_skies_cortex-2.0.2/uv.lock +0 -1024
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.editorconfig +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.github/workflows/tests.yaml +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.gitignore +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/.python-version +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/LICENSE +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/README.md +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/cliff.toml +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/ruff.toml +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/__init__.py +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/backends/__init__.py +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/columns/__init__.py +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/defaults/__init__.py +0 -0
- {clear_skies_cortex-2.0.2 → clear_skies_cortex-2.0.4}/src/clearskies_cortex/models/__init__.py +0 -0
|
@@ -79,7 +79,7 @@ jobs:
|
|
|
79
79
|
# STEP 2: Commit all file changes together
|
|
80
80
|
- name: Commit All Changes
|
|
81
81
|
run: |
|
|
82
|
-
git add pyproject.toml CHANGELOG.md
|
|
82
|
+
git add pyproject.toml CHANGELOG.md uv.lock
|
|
83
83
|
git commit -m "chore(release): bump version to ${{ steps.bump.outputs.tag }}"
|
|
84
84
|
|
|
85
85
|
# STEP 3: Create the tag for the final commit
|
|
@@ -67,7 +67,7 @@ jobs:
|
|
|
67
67
|
python-version: ${{ matrix.python-version }}
|
|
68
68
|
run-mypy: ${{ needs.changes.outputs.tests == 'true' }}
|
|
69
69
|
run-pytest: ${{ needs.changes.outputs.tests == 'true' }}
|
|
70
|
-
run-
|
|
70
|
+
run-ruff-format: ${{ needs.changes.outputs.tests == 'true' }}
|
|
71
71
|
run-ruff-check: ${{ needs.changes.outputs.tests == 'true' }}
|
|
72
72
|
secrets: inherit
|
|
73
73
|
strategy:
|
|
@@ -76,6 +76,7 @@ jobs:
|
|
|
76
76
|
- '3.11'
|
|
77
77
|
- '3.12'
|
|
78
78
|
- '3.13'
|
|
79
|
+
- '3.14'
|
|
79
80
|
fail-fast: false
|
|
80
81
|
|
|
81
82
|
status:
|
|
@@ -14,7 +14,7 @@ on:
|
|
|
14
14
|
run-pytest:
|
|
15
15
|
required: true
|
|
16
16
|
type: boolean
|
|
17
|
-
run-
|
|
17
|
+
run-ruff-format:
|
|
18
18
|
required: true
|
|
19
19
|
type: boolean
|
|
20
20
|
run-ruff-check:
|
|
@@ -66,27 +66,14 @@ jobs:
|
|
|
66
66
|
- run: uv run pytest -v
|
|
67
67
|
- run: git diff --exit-code --stat HEAD
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
name:
|
|
69
|
+
ruff-format:
|
|
70
|
+
name: ruff-format
|
|
71
71
|
runs-on: ${{ inputs.runner }}
|
|
72
|
-
if: inputs.run-
|
|
72
|
+
if: inputs.run-ruff-format
|
|
73
73
|
steps:
|
|
74
|
-
- uses:
|
|
75
|
-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
76
|
-
with:
|
|
77
|
-
persist-credentials: false
|
|
78
|
-
|
|
79
|
-
- name: Install uv and set the python version
|
|
80
|
-
uses: astral-sh/setup-uv@v6
|
|
81
|
-
with:
|
|
82
|
-
python-version: ${{ inputs.python-version }}
|
|
83
|
-
enable-cache: true
|
|
84
|
-
- name: Install dependencies
|
|
85
|
-
run: uv sync --locked --all-extras --dev
|
|
86
|
-
- uses: psf/black@stable
|
|
74
|
+
- uses: astral-sh/ruff-action@v3
|
|
87
75
|
with:
|
|
88
|
-
|
|
89
|
-
src: "./src"
|
|
76
|
+
args: "format --diff"
|
|
90
77
|
|
|
91
78
|
ruff-check:
|
|
92
79
|
name: ruff-check
|
|
@@ -38,12 +38,6 @@ repos:
|
|
|
38
38
|
hooks:
|
|
39
39
|
- id: uv-lock
|
|
40
40
|
|
|
41
|
-
- repo: https://github.com/psf/black
|
|
42
|
-
rev: 25.1.0
|
|
43
|
-
hooks:
|
|
44
|
-
- id: black
|
|
45
|
-
files: \.py$
|
|
46
|
-
|
|
47
41
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
48
42
|
rev: v0.12.1
|
|
49
43
|
hooks:
|
|
@@ -55,7 +49,7 @@ repos:
|
|
|
55
49
|
rev: v1.16.1
|
|
56
50
|
hooks:
|
|
57
51
|
- id: mypy
|
|
58
|
-
additional_dependencies: [types-requests
|
|
52
|
+
additional_dependencies: [types-requests]
|
|
59
53
|
files: \.py$
|
|
60
54
|
|
|
61
55
|
- repo: https://github.com/adrienverge/yamllint
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python.testing.pytestArgs": ["tests"],
|
|
3
|
+
"python.testing.unittestEnabled": false,
|
|
4
|
+
"python.testing.pytestEnabled": true,
|
|
5
|
+
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
|
6
|
+
"ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"],
|
|
7
|
+
"ruff.path": ["${workspaceFolder}/.venv/bin/ruff"],
|
|
8
|
+
"ruff.configuration": "${workspaceFolder}/ruff.toml",
|
|
9
|
+
"ruff.nativeServer": "on",
|
|
10
|
+
"[python]": {
|
|
11
|
+
"editor.formatOnSave": true,
|
|
12
|
+
"editor.codeActionsOnSave": {
|
|
13
|
+
"source.fixAll": "explicit",
|
|
14
|
+
"source.organizeImports": "explicit"
|
|
15
|
+
},
|
|
16
|
+
"editor.defaultFormatter": "charliermarsh.ruff"
|
|
17
|
+
},
|
|
18
|
+
"mypy-type-checker.args": [
|
|
19
|
+
"--config-file=${workspaceFolder}/pyproject.toml"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -5,12 +5,29 @@ 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
|
+
|
|
17
|
+
## [2.0.3] - 2026-01-27
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Bump version to v2.0.3 by @github-actions[bot]
|
|
21
|
+
- Udpate to QueryResult
|
|
22
|
+
- Update to latest copier version
|
|
23
|
+
|
|
8
24
|
## [2.0.2] - 2026-01-06
|
|
9
25
|
|
|
10
26
|
### Added
|
|
11
27
|
- Add teams
|
|
12
28
|
|
|
13
29
|
### Changed
|
|
30
|
+
- Bump version to v2.0.2 by @github-actions[bot]
|
|
14
31
|
- Make it all work with the api
|
|
15
32
|
|
|
16
33
|
### Fixed
|
|
@@ -34,6 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
34
51
|
* @tnijboer made their first contribution in [#1](https://github.com/clearskies-py/cortex/pull/1)
|
|
35
52
|
* @cmancone made their first contribution
|
|
36
53
|
* @ made their first contribution
|
|
54
|
+
[2.0.4]: https://github.com/clearskies-py/cortex/compare/v2.0.3..v2.0.4
|
|
55
|
+
[2.0.3]: https://github.com/clearskies-py/cortex/compare/v2.0.2..v2.0.3
|
|
37
56
|
[2.0.2]: https://github.com/clearskies-py/cortex/compare/v2.0.1..v2.0.2
|
|
38
57
|
|
|
39
58
|
<!-- generated by git-cliff -->
|
|
@@ -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
|
|
@@ -14,7 +14,7 @@ Classifier: Intended Audience :: Developers
|
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Requires-Python: <4.0,>=3.11
|
|
17
|
-
Requires-Dist: clear-skies<3.0.0,>=2.0.
|
|
17
|
+
Requires-Dist: clear-skies<3.0.0,>=2.0.37
|
|
18
18
|
Requires-Dist: dacite>=1.9.2
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: types-requests>=2.32.4; extra == 'dev'
|
|
@@ -1,13 +1,13 @@
|
|
|
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"}]
|
|
8
8
|
requires-python = ">=3.11,<4.0"
|
|
9
9
|
dependencies = [
|
|
10
|
-
"clear-skies>=2.0.
|
|
10
|
+
"clear-skies>=2.0.37,<3.0.0",
|
|
11
11
|
"dacite>=1.9.2",
|
|
12
12
|
]
|
|
13
13
|
classifiers = [
|
|
@@ -44,19 +44,6 @@ exclude = [
|
|
|
44
44
|
"tests/**"
|
|
45
45
|
]
|
|
46
46
|
|
|
47
|
-
[tool.black]
|
|
48
|
-
line-length = 120
|
|
49
|
-
skip-magic-trailing-comma = false
|
|
50
|
-
preview = false
|
|
51
|
-
|
|
52
|
-
[tool.mypy]
|
|
53
|
-
python_version = "3.11"
|
|
54
|
-
|
|
55
|
-
exclude = [
|
|
56
|
-
".*_test\\.py$",
|
|
57
|
-
"docs/.*"
|
|
58
|
-
]
|
|
59
|
-
|
|
60
47
|
[tool.pytest.ini_options]
|
|
61
48
|
minversion = "6.0"
|
|
62
49
|
addopts = "-ra -q"
|
|
@@ -72,10 +59,12 @@ pythonpath = [
|
|
|
72
59
|
[dependency-groups]
|
|
73
60
|
dev = [
|
|
74
61
|
"akeyless>=5.0.8",
|
|
75
|
-
"black>=25.1.0",
|
|
76
62
|
"mypy>=1.18.2",
|
|
77
63
|
"pre-commit>=4.3.0",
|
|
78
64
|
"pytest>=8.4.1",
|
|
79
65
|
"pytest-cov>=6.2.1",
|
|
80
66
|
"ruff>=0.12.10",
|
|
81
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)
|
|
@@ -9,6 +9,7 @@ from clearskies.backends.memory_backend import MemoryBackend, MemoryTable
|
|
|
9
9
|
from clearskies.columns import String, Uuid
|
|
10
10
|
from clearskies.di import inject
|
|
11
11
|
from clearskies.query import Condition, Query
|
|
12
|
+
from clearskies.query.result import RecordsQueryResult
|
|
12
13
|
|
|
13
14
|
from clearskies_cortex.backends import cortex_backend as rest_backend
|
|
14
15
|
|
|
@@ -34,7 +35,7 @@ class CortexTeamRelationshipBackend(MemoryBackend, Configurable):
|
|
|
34
35
|
# or we need to let the di system build the CortexBackend. This change does both:
|
|
35
36
|
self.cortex_backend = cortex_backend
|
|
36
37
|
|
|
37
|
-
def records(self, query: Query
|
|
38
|
+
def records(self, query: Query) -> RecordsQueryResult:
|
|
38
39
|
"""Accept either a model or a model class and creates a "table" for it."""
|
|
39
40
|
table_name = query.model_class.destination_name()
|
|
40
41
|
if table_name not in self._tables:
|
|
@@ -45,7 +46,7 @@ class CortexTeamRelationshipBackend(MemoryBackend, Configurable):
|
|
|
45
46
|
# we don't need since we built the data ourselves. In short, it will be a lot slower, so I cheat.
|
|
46
47
|
self._tables[table_name]._rows = records # type: ignore[assignment]
|
|
47
48
|
self._tables[table_name]._id_index = id_index # type: ignore[assignment]
|
|
48
|
-
return super().records(query
|
|
49
|
+
return super().records(query)
|
|
49
50
|
|
|
50
51
|
def _fetch_and_map_relationship_data(self, table_name: str) -> tuple[list[dict[str, str | int]], dict[str, int]]:
|
|
51
52
|
class RelationshipModel(Model):
|
|
@@ -75,11 +76,14 @@ class CortexTeamRelationshipBackend(MemoryBackend, Configurable):
|
|
|
75
76
|
root_categories: dict[str, str] = {}
|
|
76
77
|
known_children: dict[str, str] = {}
|
|
77
78
|
relationships: dict[str, set[str]] = {}
|
|
78
|
-
for relationship in
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
for relationship in (
|
|
80
|
+
self._get_cortex_backend()
|
|
81
|
+
.records(
|
|
82
|
+
Query(
|
|
83
|
+
model_class=RelationshipModel,
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
.records
|
|
83
87
|
):
|
|
84
88
|
child_category = relationship["child_team_tag"]
|
|
85
89
|
parent_category = relationship["parent_team_tag"]
|
|
@@ -153,12 +157,15 @@ class CortexTeamRelationshipBackend(MemoryBackend, Configurable):
|
|
|
153
157
|
from clearskies_cortex.models.cortex_team import CortexTeam
|
|
154
158
|
|
|
155
159
|
teams: dict[str, dict[str, Any]] = {}
|
|
156
|
-
for team in
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
for team in (
|
|
161
|
+
self._get_cortex_backend()
|
|
162
|
+
.records(
|
|
163
|
+
Query(
|
|
164
|
+
model_class=CortexTeam,
|
|
165
|
+
conditions=[Condition("include_teams_without_members=true")],
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
.records
|
|
162
169
|
):
|
|
163
170
|
teams[team["team_tag"]] = team
|
|
164
171
|
self._cached_teams = teams
|
|
@@ -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]))}
|