tropek-client 0.1.1a0__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.
- tropek_client-0.1.1a0/.gitignore +186 -0
- tropek_client-0.1.1a0/PKG-INFO +9 -0
- tropek_client-0.1.1a0/README.md +350 -0
- tropek_client-0.1.1a0/docs/architecture.md +137 -0
- tropek_client-0.1.1a0/docs/technical-debt.md +87 -0
- tropek_client-0.1.1a0/pyproject.toml +28 -0
- tropek_client-0.1.1a0/tests/__init__.py +0 -0
- tropek_client-0.1.1a0/tests/conftest.py +44 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/annotation_list.json +80 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/asset_get.json +20 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/asset_group_get.json +33 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/asset_group_tree.json +364 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/asset_list.json +249 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/asset_tag_keys.json +30 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/asset_type_list.json +41 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/configuration_get.json +6 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/configuration_list.json +44 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/datasource_get.json +13 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_detail_baseline.json +113 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_detail_fail.json +185 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_detail_invalidated.json +112 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_detail_override.json +185 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_detail_pass.json +185 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_list.json +3455 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/evaluation_names.json +42 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/heatmap_grouped.json +16641 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/note_category_list.json +42 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/sli_get.json +22 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/sli_versions.json +24 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/slo_assignment_list_for_asset.json +26 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/slo_get.json +79 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/slo_group_get.json +24 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/slo_group_list.json +187 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/slo_versions.json +81 -0
- tropek_client-0.1.1a0/tests/fixtures/api_responses/timeline_get.json +261 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/directory/asset.yaml +6 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/directory/asset_type.yaml +6 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/meta_snapshot.yaml +11 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/missing_api_version.yaml +5 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/multi_document.yaml +13 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/single_document.yaml +6 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/unknown_kind.yaml +9 -0
- tropek_client-0.1.1a0/tests/fixtures/manifests/unsorted_dependencies.yaml +13 -0
- tropek_client-0.1.1a0/tests/test_api_contract_drift.py +165 -0
- tropek_client-0.1.1a0/tests/test_asset_meta_snapshots.py +108 -0
- tropek_client-0.1.1a0/tests/test_assets.py +210 -0
- tropek_client-0.1.1a0/tests/test_cli.py +64 -0
- tropek_client-0.1.1a0/tests/test_configuration.py +76 -0
- tropek_client-0.1.1a0/tests/test_datasources.py +55 -0
- tropek_client-0.1.1a0/tests/test_evaluations.py +311 -0
- tropek_client-0.1.1a0/tests/test_exceptions.py +91 -0
- tropek_client-0.1.1a0/tests/test_health.py +14 -0
- tropek_client-0.1.1a0/tests/test_heatmap.py +37 -0
- tropek_client-0.1.1a0/tests/test_http.py +97 -0
- tropek_client-0.1.1a0/tests/test_models.py +200 -0
- tropek_client-0.1.1a0/tests/test_slis.py +84 -0
- tropek_client-0.1.1a0/tests/test_slos.py +148 -0
- tropek_client-0.1.1a0/tests/test_timeline.py +38 -0
- tropek_client-0.1.1a0/tests/test_yaml_manifest.py +112 -0
- tropek_client-0.1.1a0/tropek_client/__init__.py +23 -0
- tropek_client-0.1.1a0/tropek_client/_http.py +106 -0
- tropek_client-0.1.1a0/tropek_client/cli.py +191 -0
- tropek_client-0.1.1a0/tropek_client/client.py +898 -0
- tropek_client-0.1.1a0/tropek_client/exceptions.py +131 -0
- tropek_client-0.1.1a0/tropek_client/manifest.py +674 -0
- tropek_client-0.1.1a0/tropek_client/manifest_meta.py +44 -0
- tropek_client-0.1.1a0/tropek_client/models/__init__.py +289 -0
- tropek_client-0.1.1a0/tropek_client/models/annotations.py +85 -0
- tropek_client-0.1.1a0/tropek_client/models/asset_groups.py +100 -0
- tropek_client-0.1.1a0/tropek_client/models/asset_types.py +28 -0
- tropek_client-0.1.1a0/tropek_client/models/assets.py +56 -0
- tropek_client-0.1.1a0/tropek_client/models/change_points.py +42 -0
- tropek_client-0.1.1a0/tropek_client/models/common.py +70 -0
- tropek_client-0.1.1a0/tropek_client/models/configuration.py +18 -0
- tropek_client-0.1.1a0/tropek_client/models/datasources.py +43 -0
- tropek_client-0.1.1a0/tropek_client/models/evaluations.py +285 -0
- tropek_client-0.1.1a0/tropek_client/models/heatmap.py +101 -0
- tropek_client-0.1.1a0/tropek_client/models/meta.py +69 -0
- tropek_client-0.1.1a0/tropek_client/models/pagination.py +10 -0
- tropek_client-0.1.1a0/tropek_client/models/slis.py +55 -0
- tropek_client-0.1.1a0/tropek_client/models/slo_assignments.py +37 -0
- tropek_client-0.1.1a0/tropek_client/models/slo_groups.py +101 -0
- tropek_client-0.1.1a0/tropek_client/models/slos.py +220 -0
- tropek_client-0.1.1a0/tropek_client/models/timeline.py +44 -0
- tropek_client-0.1.1a0/tropek_client/models/trend.py +41 -0
- tropek_client-0.1.1a0/tropek_client/py.typed +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Claude Code local settings (personal paths, hooks)
|
|
2
|
+
.claude/settings.local.json
|
|
3
|
+
|
|
4
|
+
# Superpowers brainstorming sessions
|
|
5
|
+
.superpowers/
|
|
6
|
+
|
|
7
|
+
# Personal docs (research, design inspiration, old specs)
|
|
8
|
+
docs/personal/
|
|
9
|
+
|
|
10
|
+
# Git worktrees
|
|
11
|
+
.worktrees/
|
|
12
|
+
|
|
13
|
+
# Dev output (logs, artifacts)
|
|
14
|
+
out/
|
|
15
|
+
|
|
16
|
+
# Byte-compiled / optimized / DLL files
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[codz]
|
|
19
|
+
*$py.class
|
|
20
|
+
|
|
21
|
+
# C extensions
|
|
22
|
+
*.so
|
|
23
|
+
|
|
24
|
+
# Distribution / packaging
|
|
25
|
+
.Python
|
|
26
|
+
build/
|
|
27
|
+
develop-eggs/
|
|
28
|
+
dist/
|
|
29
|
+
downloads/
|
|
30
|
+
eggs/
|
|
31
|
+
.eggs/
|
|
32
|
+
lib/
|
|
33
|
+
lib64/
|
|
34
|
+
!ui/src/lib/
|
|
35
|
+
parts/
|
|
36
|
+
sdist/
|
|
37
|
+
var/
|
|
38
|
+
wheels/
|
|
39
|
+
share/python-wheels/
|
|
40
|
+
*.egg-info/
|
|
41
|
+
.installed.cfg
|
|
42
|
+
*.egg
|
|
43
|
+
MANIFEST
|
|
44
|
+
|
|
45
|
+
# PyInstaller
|
|
46
|
+
*.manifest
|
|
47
|
+
*.spec
|
|
48
|
+
|
|
49
|
+
# Installer logs
|
|
50
|
+
pip-log.txt
|
|
51
|
+
pip-delete-this-directory.txt
|
|
52
|
+
|
|
53
|
+
# Unit test / coverage reports
|
|
54
|
+
htmlcov/
|
|
55
|
+
.tox/
|
|
56
|
+
.nox/
|
|
57
|
+
.coverage
|
|
58
|
+
.coverage.*
|
|
59
|
+
.cache
|
|
60
|
+
nosetests.xml
|
|
61
|
+
coverage.xml
|
|
62
|
+
*.cover
|
|
63
|
+
*.py.cover
|
|
64
|
+
.hypothesis/
|
|
65
|
+
.pytest_cache/
|
|
66
|
+
cover/
|
|
67
|
+
|
|
68
|
+
# Translations
|
|
69
|
+
*.mo
|
|
70
|
+
*.pot
|
|
71
|
+
|
|
72
|
+
# Django stuff:
|
|
73
|
+
*.log
|
|
74
|
+
local_settings.py
|
|
75
|
+
db.sqlite3
|
|
76
|
+
db.sqlite3-journal
|
|
77
|
+
|
|
78
|
+
# Flask stuff:
|
|
79
|
+
instance/
|
|
80
|
+
.webassets-cache
|
|
81
|
+
|
|
82
|
+
# Scrapy stuff:
|
|
83
|
+
.scrapy
|
|
84
|
+
|
|
85
|
+
# Sphinx documentation
|
|
86
|
+
docs/_build/
|
|
87
|
+
|
|
88
|
+
# PyBuilder
|
|
89
|
+
.pybuilder/
|
|
90
|
+
target/
|
|
91
|
+
|
|
92
|
+
# Jupyter Notebook
|
|
93
|
+
.ipynb_checkpoints
|
|
94
|
+
|
|
95
|
+
# IPython
|
|
96
|
+
profile_default/
|
|
97
|
+
ipython_config.py
|
|
98
|
+
|
|
99
|
+
# pyenv
|
|
100
|
+
# .python-version
|
|
101
|
+
|
|
102
|
+
# UV
|
|
103
|
+
#uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
#poetry.lock
|
|
107
|
+
#poetry.toml
|
|
108
|
+
|
|
109
|
+
# pdm
|
|
110
|
+
#pdm.lock
|
|
111
|
+
#pdm.toml
|
|
112
|
+
.pdm-python
|
|
113
|
+
.pdm-build/
|
|
114
|
+
|
|
115
|
+
# pixi
|
|
116
|
+
#pixi.lock
|
|
117
|
+
.pixi
|
|
118
|
+
|
|
119
|
+
# PEP 582
|
|
120
|
+
__pypackages__/
|
|
121
|
+
|
|
122
|
+
# Celery stuff
|
|
123
|
+
celerybeat-schedule
|
|
124
|
+
celerybeat.pid
|
|
125
|
+
|
|
126
|
+
# SageMath parsed files
|
|
127
|
+
*.sage.py
|
|
128
|
+
|
|
129
|
+
# Environments
|
|
130
|
+
.env
|
|
131
|
+
.envrc
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# Ruff stuff:
|
|
164
|
+
.ruff_cache/
|
|
165
|
+
|
|
166
|
+
# PyPI configuration file
|
|
167
|
+
.pypirc
|
|
168
|
+
|
|
169
|
+
# IDEs
|
|
170
|
+
.idea/
|
|
171
|
+
.vscode/
|
|
172
|
+
*.iml
|
|
173
|
+
|
|
174
|
+
# Cursor
|
|
175
|
+
.cursorignore
|
|
176
|
+
.cursorindexingignore
|
|
177
|
+
|
|
178
|
+
# Generated mock adapter data
|
|
179
|
+
adapters/mock/data/*/metrics.csv
|
|
180
|
+
|
|
181
|
+
# Node / React UI
|
|
182
|
+
node_modules/
|
|
183
|
+
ui/dist/
|
|
184
|
+
ui/.vite/
|
|
185
|
+
|
|
186
|
+
.tmp
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tropek-client
|
|
3
|
+
Version: 0.1.1a0
|
|
4
|
+
Summary: Typed Python client for the TROPEK quality gate API
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Requires-Dist: click>=8.1
|
|
7
|
+
Requires-Dist: httpx>=0.27
|
|
8
|
+
Requires-Dist: pydantic>=2.7
|
|
9
|
+
Requires-Dist: pyyaml>=6.0
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# TROPEK Python Client
|
|
2
|
+
|
|
3
|
+
Typed Python client and CLI for the TROPEK quality gate API. Provides a programmatic
|
|
4
|
+
interface to all TROPEK resources and a declarative YAML manifest system for
|
|
5
|
+
infrastructure-as-code workflows.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Requires Python 3.13+. Install from the local package:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd clients/python
|
|
13
|
+
uv pip install -e .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Dependencies: `httpx`, `pydantic`, `pyyaml`, `click`.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from tropek_client import TropekClient
|
|
22
|
+
|
|
23
|
+
with TropekClient('http://localhost:8080', api_key='my-key') as client:
|
|
24
|
+
# Check connectivity
|
|
25
|
+
client.health()
|
|
26
|
+
|
|
27
|
+
# Create an asset and trigger an evaluation
|
|
28
|
+
client.assets.create('web-api', type_name='service', tags={'env': 'prod'})
|
|
29
|
+
result = client.evaluations.evaluate(
|
|
30
|
+
asset_name='web-api',
|
|
31
|
+
eval_name='release-42',
|
|
32
|
+
period_start='2024-01-01T00:00:00Z',
|
|
33
|
+
period_end='2024-01-01T01:00:00Z',
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
| Parameter | Default | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `base_url` | (required) | TROPEK API base URL |
|
|
42
|
+
| `api_key` | `None` | Bearer token for authentication |
|
|
43
|
+
|
|
44
|
+
The client uses a 30-second timeout. The underlying `httpx.Client` is closed
|
|
45
|
+
automatically when using the context manager.
|
|
46
|
+
|
|
47
|
+
## Client API Reference
|
|
48
|
+
|
|
49
|
+
The client exposes resource-specific namespaces as attributes on `TropekClient`.
|
|
50
|
+
|
|
51
|
+
### `client.health() -> dict`
|
|
52
|
+
|
|
53
|
+
Returns API health status.
|
|
54
|
+
|
|
55
|
+
### Asset Types — `client.asset_types`
|
|
56
|
+
|
|
57
|
+
| Method | Returns | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `list()` | `list[AssetType]` | List all asset types |
|
|
60
|
+
| `create(name, *, is_default=False)` | `AssetType` | Create an asset type |
|
|
61
|
+
| `set_default(name)` | `AssetType` | Set a type as default |
|
|
62
|
+
| `rename(name, new_name)` | `AssetType` | Rename a type |
|
|
63
|
+
| `delete(name)` | `None` | Delete a type |
|
|
64
|
+
|
|
65
|
+
### Assets — `client.assets`
|
|
66
|
+
|
|
67
|
+
| Method | Returns | Description |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `list(*, type_name=, tag_key=, tag_val=)` | `PagedResponse[Asset]` | List with optional filters |
|
|
70
|
+
| `create(name, type_name='vm', *, display_name=, tags=, variables=)` | `Asset` | Create an asset |
|
|
71
|
+
| `get(name)` | `Asset` | Get by name |
|
|
72
|
+
| `update(name, **kwargs)` | `Asset` | Partial update |
|
|
73
|
+
| `delete(name)` | `None` | Delete an asset |
|
|
74
|
+
| `tag_keys()` | `list[dict]` | Distinct tag keys with counts |
|
|
75
|
+
| `tag_values(key)` | `list[dict]` | Values for a tag key |
|
|
76
|
+
|
|
77
|
+
### Asset Groups — `client.asset_groups`
|
|
78
|
+
|
|
79
|
+
| Method | Returns | Description |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `list()` | `PagedResponse[AssetGroup]` | List all groups |
|
|
82
|
+
| `tree()` | `AssetGroupTree` | Get hierarchical tree |
|
|
83
|
+
| `create(name, *, display_name=, members=, subgroups=)` | `AssetGroup` | Create a group |
|
|
84
|
+
| `get(name)` | `AssetGroup` | Get by name |
|
|
85
|
+
| `add_member(group_name, asset_id, weight=1.0)` | `AssetGroup` | Add asset to group |
|
|
86
|
+
| `remove_member(group_name, asset_id)` | `None` | Remove asset from group |
|
|
87
|
+
| `add_subgroup(group_name, child_group_id, weight=1.0)` | `AssetGroup` | Nest a group |
|
|
88
|
+
| `remove_subgroup(group_name, child_group_id)` | `None` | Remove nested group |
|
|
89
|
+
|
|
90
|
+
### Data Sources — `client.datasources`
|
|
91
|
+
|
|
92
|
+
| Method | Returns | Description |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `list(*, adapter_type=)` | `PagedResponse[DataSource]` | List with optional filter |
|
|
95
|
+
| `create(name, adapter_type, adapter_url, **kwargs)` | `DataSource` | Register a datasource |
|
|
96
|
+
| `get(name)` | `DataSource` | Get by name |
|
|
97
|
+
| `update(name, **kwargs)` | `DataSource` | Partial update |
|
|
98
|
+
| `delete(name)` | `None` | Delete a datasource |
|
|
99
|
+
| `tag_keys()` | `list[dict]` | Distinct tag keys |
|
|
100
|
+
| `tag_values(key)` | `list[dict]` | Values for a tag key |
|
|
101
|
+
|
|
102
|
+
### SLI Definitions — `client.slis`
|
|
103
|
+
|
|
104
|
+
| Method | Returns | Description |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `list()` | `PagedResponse[SLIDefinition]` | List all SLIs |
|
|
107
|
+
| `create(body)` | `SLIDefinition` | Create (or new version) |
|
|
108
|
+
| `get(name)` | `SLIDefinition` | Get latest version |
|
|
109
|
+
| `versions(name)` | `list[SLIDefinition]` | All versions |
|
|
110
|
+
| `delete(name)` | `None` | Delete an SLI |
|
|
111
|
+
| `new_version(name, **overrides)` | `SLIDefinition` | Create new version with overrides |
|
|
112
|
+
| `tag_keys()` | `list[dict]` | Distinct tag keys |
|
|
113
|
+
| `tag_values(key)` | `list[dict]` | Values for a tag key |
|
|
114
|
+
|
|
115
|
+
### SLO Definitions — `client.slos`
|
|
116
|
+
|
|
117
|
+
| Method | Returns | Description |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `list()` | `PagedResponse[SLODefinition]` | List all SLOs |
|
|
120
|
+
| `create(body)` | `SLODefinition` | Create (or new version) |
|
|
121
|
+
| `get(name)` | `SLODefinition` | Get latest version |
|
|
122
|
+
| `versions(name)` | `list[SLODefinition]` | All versions |
|
|
123
|
+
| `delete(name)` | `None` | Delete an SLO |
|
|
124
|
+
| `new_version(name, **overrides)` | `SLODefinition` | Create new version with overrides |
|
|
125
|
+
| `validate(body)` | `SLOValidationResult` | Validate without saving |
|
|
126
|
+
| `test(body)` | `SLOTestResult` | Dry-run evaluation |
|
|
127
|
+
| `tag_keys()` | `list[dict]` | Distinct tag keys |
|
|
128
|
+
| `tag_values(key)` | `list[dict]` | Values for a tag key |
|
|
129
|
+
|
|
130
|
+
### SLO Assignments — `client.slo_assignments`
|
|
131
|
+
|
|
132
|
+
Pins an asset or group to a specific SLO definition version + datasource.
|
|
133
|
+
|
|
134
|
+
| Method | Returns | Description |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `create_for_asset(asset_name, slo_definition_id, data_source_name, *, comparison_rules=)` | `SLOAssignment` | Upsert for asset |
|
|
137
|
+
| `create_for_group(group_name, slo_definition_id, data_source_name, *, comparison_rules=)` | `SLOAssignment` | Upsert for group |
|
|
138
|
+
| `list_for_asset(asset_name)` | `list[SLOAssignment]` | List for asset |
|
|
139
|
+
| `list_for_group(group_name)` | `list[SLOAssignment]` | List for group |
|
|
140
|
+
| `delete_for_asset(asset_name, slo_definition_id)` | `None` | Delete for asset |
|
|
141
|
+
| `delete_for_group(group_name, slo_definition_id)` | `None` | Delete for group |
|
|
142
|
+
|
|
143
|
+
### SLO Groups — `client.slo_groups`
|
|
144
|
+
|
|
145
|
+
Template-based SLO generation with variable expansion.
|
|
146
|
+
|
|
147
|
+
| Method | Returns | Description |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `create(name, template_slo_name, template_slo_version, gen_variables, *, display_name=, tags=, author=)` | `SLOGroup` | Create a group |
|
|
150
|
+
| `get(name)` | `SLOGroup` | Get by name |
|
|
151
|
+
| `list(*, tag_key=, tag_val=)` | `list[SLOGroup]` | List with optional filters |
|
|
152
|
+
| `update(name, *, template_slo_name=, template_slo_version=, gen_variables=, ...)` | `SLOGroup` | Update (triggers regeneration) |
|
|
153
|
+
| `delete(name)` | `None` | Deactivate a group |
|
|
154
|
+
| `extract(group_name, slo_name, new_name)` | `None` | Extract generated SLO to standalone |
|
|
155
|
+
|
|
156
|
+
### SLO Group Assignments — `client.slo_group_assignments`
|
|
157
|
+
|
|
158
|
+
Asset/group to SLO group with always-latest semantics.
|
|
159
|
+
|
|
160
|
+
| Method | Returns | Description |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `create_for_asset(asset_name, slo_group_name, data_source_name)` | `SLOGroupAssignment` | Upsert for asset |
|
|
163
|
+
| `create_for_group(group_name, slo_group_name, data_source_name)` | `SLOGroupAssignment` | Upsert for group |
|
|
164
|
+
| `list_for_asset(asset_name)` | `list[SLOGroupAssignment]` | List for asset |
|
|
165
|
+
| `list_for_group(group_name)` | `list[SLOGroupAssignment]` | List for group |
|
|
166
|
+
| `delete_for_asset(asset_name, slo_group_name)` | `None` | Delete for asset |
|
|
167
|
+
| `delete_for_group(group_name, slo_group_name)` | `None` | Delete for group |
|
|
168
|
+
|
|
169
|
+
### Evaluations — `client.evaluations`
|
|
170
|
+
|
|
171
|
+
| Method | Returns | Description |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| `list(*, asset_name=, slo_name=, result=, date=, group_name=, from_=, to=, limit=50, offset=0)` | `PagedResponse[EvaluationSummary]` | List with filters |
|
|
174
|
+
| `get(eval_id)` | `EvaluationDetail` | Full evaluation detail |
|
|
175
|
+
| `evaluate(asset_name, eval_name, period_start, period_end, *, variables=)` | `dict` | Trigger evaluation |
|
|
176
|
+
| `evaluate_batch(mode, eval_name, *, asset_name=, periods=, asset_names=, period_start=, period_end=, variables=)` | `dict` | Batch trigger (`by_date` or `by_asset`) |
|
|
177
|
+
| `invalidate(eval_id, note)` | `EvaluationSummary` | Invalidate an evaluation |
|
|
178
|
+
| `restore(eval_id)` | `EvaluationSummary` | Restore invalidated eval |
|
|
179
|
+
| `pin_baseline(eval_id, reason, author)` | `EvaluationDetail` | Pin as baseline |
|
|
180
|
+
| `unpin_baseline(eval_id)` | `EvaluationDetail` | Remove baseline pin |
|
|
181
|
+
| `override_status(eval_id, new_result, reason, author)` | `EvaluationDetail` | Override result |
|
|
182
|
+
| `restore_override(eval_id)` | `EvaluationDetail` | Restore original result |
|
|
183
|
+
| `re_evaluate_from_date(scope, from_date, *, selector=, slo_version=, dry_run=, pin_strategy=)` | `dict` | Re-evaluate from date |
|
|
184
|
+
| `re_evaluate_from_baseline(scope, *, selector=, slo_version=, dry_run=, pin_strategy=)` | `dict` | Re-evaluate from pinned baseline |
|
|
185
|
+
| `re_evaluate_from_evaluation(evaluation_id, scope, *, selector=, slo_version=, dry_run=, pin_strategy=)` | `dict` | Re-evaluate from reference eval |
|
|
186
|
+
|
|
187
|
+
Re-evaluation `scope` is `{'kind': 'asset', 'asset_name': '...'}` or
|
|
188
|
+
`{'kind': 'group', 'group_name': '...'}`. `pin_strategy` can be `'skip_to_pin'` or
|
|
189
|
+
`'ignore_pin'` to resolve baseline-pin conflicts.
|
|
190
|
+
|
|
191
|
+
### Annotations — `client.annotations`
|
|
192
|
+
|
|
193
|
+
| Method | Returns | Description |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `list(eval_id)` | `list[Annotation]` | List for an evaluation |
|
|
196
|
+
| `create(eval_id, content, **kwargs)` | `Annotation` | Create SLO-level annotation |
|
|
197
|
+
| `create_for_run(run_id, content, **kwargs)` | `Annotation` | Create run-level annotation |
|
|
198
|
+
| `update(eval_id, ann_id, **kwargs)` | `Annotation` | Update an annotation |
|
|
199
|
+
| `hide(eval_id, ann_id, reason, author=None)` | `Annotation` | Soft-delete |
|
|
200
|
+
|
|
201
|
+
### Trend — `client.trend`
|
|
202
|
+
|
|
203
|
+
| Method | Returns | Description |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `by_eval(eval_id, metric, from_, *, to=)` | `list[TrendPoint]` | Trend by evaluation ID |
|
|
206
|
+
| `by_asset(asset_name, slo_name, metric, from_, *, to=)` | `list[TrendPoint]` | Trend by asset + SLO |
|
|
207
|
+
|
|
208
|
+
## Manifest System
|
|
209
|
+
|
|
210
|
+
The manifest system enables declarative, infrastructure-as-code management of TROPEK
|
|
211
|
+
resources using YAML files.
|
|
212
|
+
|
|
213
|
+
### Manifest Format
|
|
214
|
+
|
|
215
|
+
Each manifest document has four fields:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
api_version: tropek/v1
|
|
219
|
+
kind: Asset
|
|
220
|
+
metadata:
|
|
221
|
+
name: web-api
|
|
222
|
+
display_name: Web API
|
|
223
|
+
tags:
|
|
224
|
+
env: prod
|
|
225
|
+
spec:
|
|
226
|
+
type_name: service
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Supported kinds (processed in dependency order):
|
|
230
|
+
`AssetType`, `DataSource`, `Asset`, `SLI`, `SLO`, `AssetGroup`, `SLOGroup`,
|
|
231
|
+
`SLOAssignment`, `SLOGroupAssignment`.
|
|
232
|
+
|
|
233
|
+
A single file can contain multiple documents separated by `---`. You can also point
|
|
234
|
+
at a directory — all `*.yaml` and `*.yml` files are loaded.
|
|
235
|
+
|
|
236
|
+
### SLO Assignment Manifest
|
|
237
|
+
|
|
238
|
+
```yaml
|
|
239
|
+
api_version: tropek/v1
|
|
240
|
+
kind: SLOAssignment
|
|
241
|
+
metadata:
|
|
242
|
+
name: web-api-latency-assignment
|
|
243
|
+
spec:
|
|
244
|
+
target_type: asset # or asset_group
|
|
245
|
+
target_name: web-api
|
|
246
|
+
slo_name: latency-slo
|
|
247
|
+
data_source_name: prometheus
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Reconciliation
|
|
251
|
+
|
|
252
|
+
The manifest system uses desired-state reconciliation:
|
|
253
|
+
1. Load and parse all YAML documents
|
|
254
|
+
2. Topologically sort by kind dependency
|
|
255
|
+
3. For each document, look up the existing resource via the API
|
|
256
|
+
4. Compare fields to detect drift (CREATE / UPDATE / SKIP)
|
|
257
|
+
5. Apply changes, blocking dependent kinds on failure
|
|
258
|
+
|
|
259
|
+
### Programmatic Usage
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from tropek_client import TropekClient
|
|
263
|
+
from tropek_client.manifest import load_manifests, apply, dry_run
|
|
264
|
+
|
|
265
|
+
docs = load_manifests('manifests/')
|
|
266
|
+
|
|
267
|
+
with TropekClient('http://localhost:8080') as client:
|
|
268
|
+
# Preview changes
|
|
269
|
+
plan = dry_run(client, docs)
|
|
270
|
+
for action in plan.actions:
|
|
271
|
+
print(f'{action.operation} {action.kind}/{action.name}')
|
|
272
|
+
|
|
273
|
+
# Apply
|
|
274
|
+
result = apply(client, docs)
|
|
275
|
+
print(f'{result.created} created, {result.updated} updated')
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## CLI Usage
|
|
279
|
+
|
|
280
|
+
The CLI is installed as `tropek` (entry point defined in pyproject.toml).
|
|
281
|
+
|
|
282
|
+
### Validate manifests
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
tropek validate -f manifests/
|
|
286
|
+
tropek validate -f my-slo.yaml
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Checks syntax and cross-references without contacting the API.
|
|
290
|
+
|
|
291
|
+
### Apply manifests
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Dry run — show what would change
|
|
295
|
+
tropek apply --dry-run -f manifests/ --base-url http://localhost:8080
|
|
296
|
+
|
|
297
|
+
# Apply changes
|
|
298
|
+
tropek apply -f manifests/ --base-url http://localhost:8080 --api-key my-key
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Export current state
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# To stdout
|
|
305
|
+
tropek export --base-url http://localhost:8080
|
|
306
|
+
|
|
307
|
+
# To file
|
|
308
|
+
tropek export -f backup.yaml --base-url http://localhost:8080 --api-key my-key
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Exports all resources (asset types, datasources, assets, SLIs, SLOs, groups,
|
|
312
|
+
assignments) as a multi-document YAML manifest.
|
|
313
|
+
|
|
314
|
+
## Known Limitations
|
|
315
|
+
|
|
316
|
+
- **Asset names are globally unique.** There is no per-group scoping — two groups
|
|
317
|
+
cannot each contain an asset named `load_test`. Assets are identified by name
|
|
318
|
+
across the entire TROPEK instance.
|
|
319
|
+
|
|
320
|
+
- **Display groups (`client.display_groups`) are half-baked.** The API exposes
|
|
321
|
+
`/slo-display-groups` as a standalone CRUD resource for organizing generated SLOs
|
|
322
|
+
into named visual buckets, but the feature is incomplete: no FK linking display
|
|
323
|
+
groups to the SLO group they belong to, no validation that member SLO names
|
|
324
|
+
exist, and no UI consumer. The client wraps the routes but they are untested
|
|
325
|
+
against real workflows. This will likely be redesigned when SLO group navigation
|
|
326
|
+
is revisited.
|
|
327
|
+
|
|
328
|
+
## Error Handling
|
|
329
|
+
|
|
330
|
+
All API errors raise typed exceptions:
|
|
331
|
+
|
|
332
|
+
| Exception | HTTP Status | When |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| `TropekNotFoundError` | 404 | Resource not found |
|
|
335
|
+
| `TropekConflictError` | 409 | Resource conflict (duplicate name) |
|
|
336
|
+
| `TropekValidationError` | 422 | Request validation failed |
|
|
337
|
+
| `TropekAPIError` | any non-2xx | Base class for all errors |
|
|
338
|
+
|
|
339
|
+
Each exception has `status_code` and `detail` attributes:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from tropek_client import TropekClient, TropekNotFoundError
|
|
343
|
+
|
|
344
|
+
with TropekClient('http://localhost:8080') as client:
|
|
345
|
+
try:
|
|
346
|
+
client.assets.get('nonexistent')
|
|
347
|
+
except TropekNotFoundError as e:
|
|
348
|
+
print(e.status_code) # 404
|
|
349
|
+
print(e.detail) # "asset 'nonexistent' not found"
|
|
350
|
+
```
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Python Client — Architecture
|
|
2
|
+
|
|
3
|
+
Contributor guide for `clients/python/tropek_client/`.
|
|
4
|
+
|
|
5
|
+
## Module Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
tropek_client/
|
|
9
|
+
├── __init__.py # Public API: TropekClient + exception classes
|
|
10
|
+
├── client.py # HTTP client with resource-namespaced methods
|
|
11
|
+
├── models.py # Pydantic response models
|
|
12
|
+
├── manifest.py # YAML manifest loader and reconciler
|
|
13
|
+
├── cli.py # Click CLI interface
|
|
14
|
+
└── exceptions.py # Typed exception hierarchy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Client Design (`client.py`)
|
|
18
|
+
|
|
19
|
+
The client uses **namespace classes** to group API methods by resource type. Each
|
|
20
|
+
namespace (e.g., `_Assets`, `_SLODefinitions`) is a private class that receives a
|
|
21
|
+
shared `httpx.Client` instance. `TropekClient.__init__` instantiates all namespaces
|
|
22
|
+
and exposes them as public attributes.
|
|
23
|
+
|
|
24
|
+
HTTP pattern for every method:
|
|
25
|
+
1. Build params/body dict, omitting `None` values
|
|
26
|
+
2. Call `self._http.{verb}(path, ...)`
|
|
27
|
+
3. Pass response through `_raise_for_status()` (maps status codes to typed exceptions)
|
|
28
|
+
4. Deserialize via `Model.model_validate(resp.json())`
|
|
29
|
+
|
|
30
|
+
Paginated endpoints return `PagedResponse[T]` (items + total). Non-paginated
|
|
31
|
+
endpoints return the model directly or a raw `dict` for polymorphic responses
|
|
32
|
+
(evaluations trigger, batch, re-evaluate).
|
|
33
|
+
|
|
34
|
+
`TropekClient` implements the context manager protocol for automatic cleanup.
|
|
35
|
+
|
|
36
|
+
## Error Handling (`exceptions.py`)
|
|
37
|
+
|
|
38
|
+
Four-class hierarchy:
|
|
39
|
+
- `TropekAPIError(status_code, detail)` — base, catches any non-2xx
|
|
40
|
+
- `TropekNotFoundError(detail)` — 404
|
|
41
|
+
- `TropekConflictError(detail)` — 409
|
|
42
|
+
- `TropekValidationError(detail)` — 422
|
|
43
|
+
|
|
44
|
+
`_raise_for_status()` uses structural pattern matching on `resp.status_code`.
|
|
45
|
+
The response body's `detail` field is extracted when available; raw text is the fallback.
|
|
46
|
+
|
|
47
|
+
## Models (`models.py`)
|
|
48
|
+
|
|
49
|
+
Pydantic v2 models mirroring API response schemas. Key patterns:
|
|
50
|
+
- `PagedResponse[T]` — generic paginated wrapper using Python 3.12+ type parameter syntax
|
|
51
|
+
- `ConfigDict(from_attributes=True)` on ORM-mapped models for compatibility
|
|
52
|
+
- `EvaluationDetail` extends `EvaluationSummary` (adds annotations, indicator results)
|
|
53
|
+
- Request models (`SLOTestRequest`, `BaselineConfig`) for structured input
|
|
54
|
+
- String IDs on assignment models (`SLOAssignment`, `SLOGroupAssignment`) — the API
|
|
55
|
+
returns UUIDs as strings in some endpoints
|
|
56
|
+
|
|
57
|
+
## Manifest System (`manifest.py`)
|
|
58
|
+
|
|
59
|
+
### Processing Pipeline
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
load_manifests(path)
|
|
63
|
+
├── _load_file() # YAML multi-doc parsing
|
|
64
|
+
├── _parse_document() # Validate required fields, known kinds
|
|
65
|
+
└── _topological_sort() # Sort by _KIND_ORDER dependency
|
|
66
|
+
|
|
67
|
+
validate_manifests(path)
|
|
68
|
+
├── load_manifests()
|
|
69
|
+
└── _validate_doc_refs() # Cross-reference warnings
|
|
70
|
+
|
|
71
|
+
dry_run(client, manifests)
|
|
72
|
+
└── for each doc:
|
|
73
|
+
├── _lookup() # Fetch existing resource from API
|
|
74
|
+
├── _has_diff() # Field-level comparison
|
|
75
|
+
└── → PlanAction(CREATE | UPDATE | SKIP)
|
|
76
|
+
|
|
77
|
+
apply(client, manifests)
|
|
78
|
+
├── dry_run() # Build plan first
|
|
79
|
+
└── for each action:
|
|
80
|
+
├── _create() # Kind-specific create dispatch
|
|
81
|
+
├── _update() # Kind-specific update dispatch
|
|
82
|
+
└── _dependents_of() # Block downstream kinds on failure
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Dependency Graph
|
|
86
|
+
|
|
87
|
+
`_KIND_ORDER` defines processing order: `AssetType → DataSource → Asset → SLI → SLO
|
|
88
|
+
→ AssetGroup → SLOGroup → SLOAssignment → SLOGroupAssignment`.
|
|
89
|
+
|
|
90
|
+
`_KIND_DEPS` maps each kind to its dependents for error propagation — if an `Asset`
|
|
91
|
+
creation fails, `AssetGroup`, `SLOAssignment`, and `SLOGroupAssignment` are blocked.
|
|
92
|
+
|
|
93
|
+
### Update Semantics
|
|
94
|
+
|
|
95
|
+
- **Versioned resources** (SLI, SLO): updates create a new version via the same
|
|
96
|
+
`create` endpoint rather than patching in place.
|
|
97
|
+
- **Immutable resources** (SLOAssignment, SLOGroupAssignment): `_has_diff` always
|
|
98
|
+
returns `False` — requires manual delete + recreate.
|
|
99
|
+
- **AssetGroup**: member/subgroup sync not yet implemented; updates are skipped.
|
|
100
|
+
|
|
101
|
+
## CLI (`cli.py`)
|
|
102
|
+
|
|
103
|
+
Click command tree with three commands:
|
|
104
|
+
|
|
105
|
+
- `tropek validate -f <path>` — offline YAML validation
|
|
106
|
+
- `tropek apply -f <path> [--dry-run] [--base-url] [--api-key]` — reconcile manifests
|
|
107
|
+
- `tropek export [-f <path>] [--base-url] [--api-key]` — dump current state as YAML
|
|
108
|
+
|
|
109
|
+
`export` uses `_collect_documents()` which iterates all resource types and builds
|
|
110
|
+
manifest dicts. It also walks per-asset and per-group SLO assignments.
|
|
111
|
+
|
|
112
|
+
## Test Structure
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
tests/
|
|
116
|
+
├── test_client.py # HTTP mocking via pytest-httpx; covers CRUD + error mapping
|
|
117
|
+
├── test_manifest.py # Manifest loading, sorting, dry_run, apply with MagicMock client
|
|
118
|
+
├── test_cli.py # Click CliRunner tests for validate + apply --dry-run
|
|
119
|
+
└── test_models.py # Pydantic model_validate round-trips
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Dev dependencies: `pytest`, `pytest-httpx` (for `HTTPXMock` fixture).
|
|
123
|
+
|
|
124
|
+
Tests use `MagicMock` for the client in manifest/CLI tests and `HTTPXMock` for
|
|
125
|
+
HTTP-level client tests. No real API calls in any test.
|
|
126
|
+
|
|
127
|
+
## Known Limitations
|
|
128
|
+
|
|
129
|
+
See [`technical-debt.md`](technical-debt.md) for detailed analysis and recommended
|
|
130
|
+
fixes. Summary:
|
|
131
|
+
|
|
132
|
+
- No contract validation against the backend OpenAPI spec — client can silently drift
|
|
133
|
+
- `AssetGroup` update via manifests is a no-op (member sync not implemented)
|
|
134
|
+
- Assignment updates require manual delete + recreate
|
|
135
|
+
- No async client variant
|
|
136
|
+
- `export` does not include SLO group assignments
|
|
137
|
+
- Some endpoints return raw `dict` instead of typed models (evaluation triggers)
|