dataceen-client 0.0.1__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.
- dataceen_client-0.0.1/.env.example +17 -0
- dataceen_client-0.0.1/CHANGELOG.md +58 -0
- dataceen_client-0.0.1/LICENSE +16 -0
- dataceen_client-0.0.1/MANIFEST.in +14 -0
- dataceen_client-0.0.1/PKG-INFO +160 -0
- dataceen_client-0.0.1/README.md +124 -0
- dataceen_client-0.0.1/docs/decisions.md +26 -0
- dataceen_client-0.0.1/docs/dsl-comparison.md +125 -0
- dataceen_client-0.0.1/docs/plan.md +143 -0
- dataceen_client-0.0.1/docs/porting-guide.md +119 -0
- dataceen_client-0.0.1/docs/progress.md +359 -0
- dataceen_client-0.0.1/docs/release.md +130 -0
- dataceen_client-0.0.1/protos/dataceenevent.proto +85 -0
- dataceen_client-0.0.1/pyproject.toml +67 -0
- dataceen_client-0.0.1/setup.cfg +4 -0
- dataceen_client-0.0.1/src/dataceen_client/__init__.py +48 -0
- dataceen_client-0.0.1/src/dataceen_client/config.py +105 -0
- dataceen_client-0.0.1/src/dataceen_client/dataceen_client.py +230 -0
- dataceen_client-0.0.1/src/dataceen_client/errors.py +36 -0
- dataceen_client-0.0.1/src/dataceen_client/http_transport.py +137 -0
- dataceen_client-0.0.1/src/dataceen_client/py.typed +0 -0
- dataceen_client-0.0.1/src/dataceen_client/runtime/__init__.py +48 -0
- dataceen_client-0.0.1/src/dataceen_client/runtime/aggregations.py +54 -0
- dataceen_client-0.0.1/src/dataceen_client/runtime/fields.py +78 -0
- dataceen_client-0.0.1/src/dataceen_client/runtime/filter.py +130 -0
- dataceen_client-0.0.1/src/dataceen_client/runtime/operations.py +199 -0
- dataceen_client-0.0.1/src/dataceen_client/runtime/order_by.py +29 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/__init__.py +36 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/_pb/__init__.py +15 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/_pb/dataceenevent_pb2.py +52 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/_pb/dataceenevent_pb2.pyi +130 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/_pb/dataceenevent_pb2_grpc.py +100 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/grpc_client.py +75 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/subscription.py +75 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/subscription_manager.py +367 -0
- dataceen_client-0.0.1/src/dataceen_client/subscriptions/types.py +103 -0
- dataceen_client-0.0.1/src/dataceen_client/token_provider.py +68 -0
- dataceen_client-0.0.1/src/dataceen_client/types.py +62 -0
- dataceen_client-0.0.1/src/dataceen_client.egg-info/PKG-INFO +160 -0
- dataceen_client-0.0.1/src/dataceen_client.egg-info/SOURCES.txt +71 -0
- dataceen_client-0.0.1/src/dataceen_client.egg-info/dependency_links.txt +1 -0
- dataceen_client-0.0.1/src/dataceen_client.egg-info/requires.txt +12 -0
- dataceen_client-0.0.1/src/dataceen_client.egg-info/top_level.txt +1 -0
- dataceen_client-0.0.1/tests/__init__.py +0 -0
- dataceen_client-0.0.1/tests/test_aggregations.py +18 -0
- dataceen_client-0.0.1/tests/test_codegen_aggregation.py +146 -0
- dataceen_client-0.0.1/tests/test_codegen_emit.py +155 -0
- dataceen_client-0.0.1/tests/test_codegen_parse.py +299 -0
- dataceen_client-0.0.1/tests/test_dataceen_client.py +133 -0
- dataceen_client-0.0.1/tests/test_examples_smoke.py +58 -0
- dataceen_client-0.0.1/tests/test_fields.py +52 -0
- dataceen_client-0.0.1/tests/test_filter.py +72 -0
- dataceen_client-0.0.1/tests/test_http_transport.py +148 -0
- dataceen_client-0.0.1/tests/test_operations.py +163 -0
- dataceen_client-0.0.1/tests/test_order_by.py +27 -0
- dataceen_client-0.0.1/tests/test_subscriptions.py +368 -0
- dataceen_client-0.0.1/tools/__init__.py +0 -0
- dataceen_client-0.0.1/tools/codegen/__init__.py +0 -0
- dataceen_client-0.0.1/tools/codegen/src/__init__.py +0 -0
- dataceen_client-0.0.1/tools/codegen/src/cli.py +107 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_aggregation.py +143 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_all_client.py +38 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_client.py +184 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_create.py +84 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_fields.py +57 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_filter.py +91 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_model.py +80 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_plan.py +142 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_update.py +115 -0
- dataceen_client-0.0.1/tools/codegen/src/emit_util.py +146 -0
- dataceen_client-0.0.1/tools/codegen/src/fetch_schema.py +147 -0
- dataceen_client-0.0.1/tools/codegen/src/introspection_query.py +82 -0
- dataceen_client-0.0.1/tools/codegen/src/parse_schema.py +402 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Copy to .env and fill in real values.
|
|
2
|
+
# Mirrors the C# example's appsettings.json for CandyShopModel.
|
|
3
|
+
|
|
4
|
+
DATACEEN_TENANT_ID=97dd913c-931e-44cf-96fd-a36270f7a465
|
|
5
|
+
DATACEEN_CLIENT_ID=62e0cda5-a254-4498-b660-e00b98e83225
|
|
6
|
+
DATACEEN_CLIENT_SECRET=replace-me
|
|
7
|
+
|
|
8
|
+
DATACEEN_DOMAIN=Thomas
|
|
9
|
+
DATACEEN_MODEL=CandyShopModel
|
|
10
|
+
DATACEEN_SCOPE=All
|
|
11
|
+
|
|
12
|
+
DATACEEN_API_URL=https://api.dataceen.com
|
|
13
|
+
DATACEEN_API_PATH=/graphql
|
|
14
|
+
DATACEEN_API_SCOPE=api://413b24f0-e191-4cf2-839d-3c9c75465ba2/.default
|
|
15
|
+
|
|
16
|
+
DATACEEN_SUBSCRIPTION_URL=https://subscription.dataceen.com
|
|
17
|
+
DATACEEN_SUBSCRIPTION_SCOPE=api://6982ac36-9f3e-4b60-9f17-6b078fd65ead/.default
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `dataceen-client` (Python) are documented here. Format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
|
+
project loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.0.1] — 2026-05-01
|
|
10
|
+
|
|
11
|
+
Initial alpha — feature-parity with C# `DataceenClient` and TypeScript
|
|
12
|
+
`@dataceen/client`, verified against the same model fixtures.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Core runtime** (`dataceen_client.*`)
|
|
17
|
+
- `DataceenConfig`, `DataceenClientOptions`, `load_config_from_env()`
|
|
18
|
+
- `DataceenError` — single error type for HTTP, GraphQL, parse, and server-side `ResponseCode != 0`
|
|
19
|
+
- `DataceenClient.execute_query()` (default retry on transient) and `execute_mutation()` (no retry by default)
|
|
20
|
+
- `TokenProvider` — Azure AD client-credentials via `msal`, with `force_refresh` simulated by `remove_tokens_for_client()`
|
|
21
|
+
- `HttpTransport` — `httpx.AsyncClient` POST with 401-refresh + transient retry (408/429/5xx, exponential backoff)
|
|
22
|
+
- Result `TypedDict`s: `FindResult[T]`, `SearchResult[T]`, `MutationResult`, …
|
|
23
|
+
|
|
24
|
+
- **Runtime DSL helpers** (`dataceen_client.runtime.*`)
|
|
25
|
+
- Pure-dict DSL — `filter_to_graphql`, `order_by_to_graphql`, `collect_fields`
|
|
26
|
+
- `StringOps`, `NumberOps`, `BooleanOps`, `DateOps` — typed comparison-operator dicts
|
|
27
|
+
- `aggregation_fragment(depth)` — recursive Aggregations result expansion
|
|
28
|
+
- `do_find` / `do_search` / `do_create` / `do_create_bulk` / `do_update` / `do_update_bulk` / `do_delete` / `do_create_relation` / `do_create_relation_bulk`
|
|
29
|
+
|
|
30
|
+
- **Codegen** (`tools/codegen/`)
|
|
31
|
+
- GraphQL introspection fetch + cache (`fetch_schema.py`, `parse_schema.py`)
|
|
32
|
+
- Per-entity emitters: read TypedDict, Create TypedDict, Update class with `__setattr__` change-tracking, Filter / SearchFilter, Fields, per-entity Client, AllClient
|
|
33
|
+
- Aggregation emitter — typed `Search<Entity>_aggregation` + `<Entity>AggregationDetails`
|
|
34
|
+
- CLI: `python -m tools.codegen.src.cli {fetch,emit ROOT,emit-all}` (also installable as `dataceen-codegen`)
|
|
35
|
+
|
|
36
|
+
- **gRPC subscriptions** (`dataceen_client.subscriptions.*`)
|
|
37
|
+
- `Subscription` class with typed + low-level handlers, `asyncio.Event`-based cancel
|
|
38
|
+
- `grpc.aio` async stream open + Bearer-metadata auth
|
|
39
|
+
- Subscription manager with baseload semantics, exponential reconnect (max 10), synthetic CONNECTION_LOST/RECONNECTED events, slow-handler timing, central exception catch
|
|
40
|
+
- Pre-compiled proto stubs in `_pb/` (regenerable via `scripts/regenerate_proto.py`)
|
|
41
|
+
|
|
42
|
+
- **Example app** — `examples/candy_shop/` ports the TS port's `examples/candy-shop/`. Read-only flows always run; create/update/delete gated by `RUN_DESTRUCTIVE=1`.
|
|
43
|
+
|
|
44
|
+
- **Docs** — `docs/{plan,decisions,progress,dsl-comparison,porting-guide,release}.md`.
|
|
45
|
+
|
|
46
|
+
### Test coverage
|
|
47
|
+
|
|
48
|
+
88 unit tests, hermetic (~1.7s). Live-backend integration is opt-in via
|
|
49
|
+
`RUN_INTEGRATION=1` once `.env` is configured.
|
|
50
|
+
|
|
51
|
+
### Known gaps for v0.1
|
|
52
|
+
|
|
53
|
+
- No live-backend smoke run yet (needs valid `.env`).
|
|
54
|
+
- No published wheel — `python -m build` produces it locally.
|
|
55
|
+
- Sphinx/MkDocs site not built; docstrings + `docs/*.md` are the source of truth.
|
|
56
|
+
|
|
57
|
+
[Unreleased]: https://github.com/dataceen/dataceen-client-python/compare/v0.0.1...HEAD
|
|
58
|
+
[0.0.1]: https://github.com/dataceen/dataceen-client-python/releases/tag/v0.0.1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Copyright (c) 2026 Dataceen AB.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary and confidential. Unauthorized copying,
|
|
6
|
+
modification, distribution, or use of this software, via any medium, is
|
|
7
|
+
strictly prohibited without the express written permission of the copyright
|
|
8
|
+
holder.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
11
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
12
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
13
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
14
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING
|
|
15
|
+
FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
16
|
+
DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include CHANGELOG.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
include .env.example
|
|
5
|
+
recursive-include docs *.md
|
|
6
|
+
recursive-include protos *.proto
|
|
7
|
+
recursive-include src *.py *.pyi py.typed
|
|
8
|
+
recursive-include tests *.py
|
|
9
|
+
recursive-include tools *.py
|
|
10
|
+
prune .venv
|
|
11
|
+
prune dist
|
|
12
|
+
prune build
|
|
13
|
+
global-exclude __pycache__
|
|
14
|
+
global-exclude *.py[cod]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataceen-client
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python client for Dataceen GraphQL + gRPC subscription APIs
|
|
5
|
+
Author: Dataceen
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Source, https://github.com/dataceen/dataceen-client-python
|
|
8
|
+
Project-URL: Issues, https://github.com/dataceen/dataceen-client-python/issues
|
|
9
|
+
Keywords: dataceen,graphql,grpc,client,azure-ad
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: Other/Proprietary License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: msal>=1.28
|
|
26
|
+
Requires-Dist: grpcio>=1.60
|
|
27
|
+
Requires-Dist: protobuf>=4.25
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
31
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
32
|
+
Requires-Dist: python-dotenv>=1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: grpcio-tools>=1.60; extra == "dev"
|
|
34
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# dataceen-client (Python)
|
|
38
|
+
|
|
39
|
+
Python client for the Dataceen GraphQL + gRPC subscription APIs. Parallel to
|
|
40
|
+
the C# (`DataceenClient/`) and TypeScript (`DataceenClientTs/`) reference
|
|
41
|
+
clients — feature-parity verified against the same `Thomas/CandyShopModel/All`
|
|
42
|
+
fixtures.
|
|
43
|
+
|
|
44
|
+
> **Status:** 0.0.1 alpha. 88 unit tests green. Not yet on PyPI.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
python -m venv .venv
|
|
50
|
+
.venv\Scripts\activate # PowerShell
|
|
51
|
+
pip install -e ".[dev]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Requires Python 3.11+ (`typing.NotRequired`, generic `TypedDict`).
|
|
55
|
+
|
|
56
|
+
## Configure
|
|
57
|
+
|
|
58
|
+
Copy `.env.example` to `.env` at the repo root and fill in real Azure AD
|
|
59
|
+
credentials. The example matches the C# `appsettings.json`'s `AllClient`
|
|
60
|
+
section.
|
|
61
|
+
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
from dataceen_client import DataceenClient, load_config_from_env
|
|
67
|
+
from dataceen_client.runtime import do_find
|
|
68
|
+
|
|
69
|
+
async def main():
|
|
70
|
+
cfg = load_config_from_env()
|
|
71
|
+
async with DataceenClient(cfg) as client:
|
|
72
|
+
result = await do_find(
|
|
73
|
+
client,
|
|
74
|
+
"Customer",
|
|
75
|
+
size=5,
|
|
76
|
+
filter={"CustomerId": {"like": "cst"}},
|
|
77
|
+
fields={
|
|
78
|
+
"_id": True,
|
|
79
|
+
"CustomerId": True,
|
|
80
|
+
"CustomerPlacedOrder": {"Order": {"OrderId": True}},
|
|
81
|
+
},
|
|
82
|
+
order_by=[{"CustomerId": "Ascending"}],
|
|
83
|
+
)
|
|
84
|
+
for item in result["Items"]:
|
|
85
|
+
print(item)
|
|
86
|
+
|
|
87
|
+
asyncio.run(main())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
After running codegen, replace `do_find(client, "Customer", ...)` with the
|
|
91
|
+
typed `AllClient(client).Customer.find(...)`.
|
|
92
|
+
|
|
93
|
+
## Subscriptions
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
async with DataceenClient(cfg) as client:
|
|
97
|
+
sub = client.create_subscription(
|
|
98
|
+
topics=["Customer"],
|
|
99
|
+
baseload_topics=["Customer"],
|
|
100
|
+
start_mode="POSITION_BEGINNING",
|
|
101
|
+
include_complete=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
async def on_customer(evt):
|
|
105
|
+
if evt.event_type == "BASELOAD_EVENT":
|
|
106
|
+
... # evt.complete is parsed JSON
|
|
107
|
+
elif evt.event_type == "SUBSCRIPTION_EVENT":
|
|
108
|
+
... # live event
|
|
109
|
+
|
|
110
|
+
sub.on("Customer", on_customer)
|
|
111
|
+
await client.subscribe(sub) # blocks until sub.cancel()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Codegen
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
python -m tools.codegen.src.cli fetch # cache schema.json
|
|
118
|
+
python -m tools.codegen.src.cli emit Customer # Customer + everything it references
|
|
119
|
+
python -m tools.codegen.src.cli emit-all # whole model
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Output → `src/dataceen_client/generated/` (gitignored). Per entity you get
|
|
123
|
+
typed `TypedDict`s for read/create/filter/fields, a change-tracked `Update`
|
|
124
|
+
class, and an `<Entity>Client` wrapping the runtime helpers; `AllClient`
|
|
125
|
+
aggregates the top-level entities.
|
|
126
|
+
|
|
127
|
+
Codegen runs from a source checkout — it is intentionally not part of the
|
|
128
|
+
published wheel (only the runtime `dataceen_client` package is shipped).
|
|
129
|
+
|
|
130
|
+
## Tests
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
pytest # 88 unit tests, ~1.7s
|
|
134
|
+
RUN_INTEGRATION=1 pytest # also run live-backend integration tests (needs .env)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Example app
|
|
138
|
+
|
|
139
|
+
End-to-end example against the live backend:
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
python -m examples.candy_shop.find
|
|
143
|
+
python -m examples.candy_shop.search_and_aggregate
|
|
144
|
+
python -m examples.candy_shop.subscriber
|
|
145
|
+
python -m examples.candy_shop.program # full read-only orchestrator
|
|
146
|
+
RUN_DESTRUCTIVE=1 python -m examples.candy_shop.program # also create/update/delete
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Docs
|
|
150
|
+
|
|
151
|
+
- `docs/plan.md` — phase plan
|
|
152
|
+
- `docs/decisions.md` — decisions and motivation
|
|
153
|
+
- `docs/progress.md` — live status
|
|
154
|
+
- `docs/dsl-comparison.md` — why pure-dict DSL for Python
|
|
155
|
+
- `docs/porting-guide.md` — Python-specific amendments to the canonical guide
|
|
156
|
+
- `docs/release.md` — version-bump and publish runbook
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
Proprietary — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# dataceen-client (Python)
|
|
2
|
+
|
|
3
|
+
Python client for the Dataceen GraphQL + gRPC subscription APIs. Parallel to
|
|
4
|
+
the C# (`DataceenClient/`) and TypeScript (`DataceenClientTs/`) reference
|
|
5
|
+
clients — feature-parity verified against the same `Thomas/CandyShopModel/All`
|
|
6
|
+
fixtures.
|
|
7
|
+
|
|
8
|
+
> **Status:** 0.0.1 alpha. 88 unit tests green. Not yet on PyPI.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
.venv\Scripts\activate # PowerShell
|
|
15
|
+
pip install -e ".[dev]"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Python 3.11+ (`typing.NotRequired`, generic `TypedDict`).
|
|
19
|
+
|
|
20
|
+
## Configure
|
|
21
|
+
|
|
22
|
+
Copy `.env.example` to `.env` at the repo root and fill in real Azure AD
|
|
23
|
+
credentials. The example matches the C# `appsettings.json`'s `AllClient`
|
|
24
|
+
section.
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
from dataceen_client import DataceenClient, load_config_from_env
|
|
31
|
+
from dataceen_client.runtime import do_find
|
|
32
|
+
|
|
33
|
+
async def main():
|
|
34
|
+
cfg = load_config_from_env()
|
|
35
|
+
async with DataceenClient(cfg) as client:
|
|
36
|
+
result = await do_find(
|
|
37
|
+
client,
|
|
38
|
+
"Customer",
|
|
39
|
+
size=5,
|
|
40
|
+
filter={"CustomerId": {"like": "cst"}},
|
|
41
|
+
fields={
|
|
42
|
+
"_id": True,
|
|
43
|
+
"CustomerId": True,
|
|
44
|
+
"CustomerPlacedOrder": {"Order": {"OrderId": True}},
|
|
45
|
+
},
|
|
46
|
+
order_by=[{"CustomerId": "Ascending"}],
|
|
47
|
+
)
|
|
48
|
+
for item in result["Items"]:
|
|
49
|
+
print(item)
|
|
50
|
+
|
|
51
|
+
asyncio.run(main())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
After running codegen, replace `do_find(client, "Customer", ...)` with the
|
|
55
|
+
typed `AllClient(client).Customer.find(...)`.
|
|
56
|
+
|
|
57
|
+
## Subscriptions
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
async with DataceenClient(cfg) as client:
|
|
61
|
+
sub = client.create_subscription(
|
|
62
|
+
topics=["Customer"],
|
|
63
|
+
baseload_topics=["Customer"],
|
|
64
|
+
start_mode="POSITION_BEGINNING",
|
|
65
|
+
include_complete=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def on_customer(evt):
|
|
69
|
+
if evt.event_type == "BASELOAD_EVENT":
|
|
70
|
+
... # evt.complete is parsed JSON
|
|
71
|
+
elif evt.event_type == "SUBSCRIPTION_EVENT":
|
|
72
|
+
... # live event
|
|
73
|
+
|
|
74
|
+
sub.on("Customer", on_customer)
|
|
75
|
+
await client.subscribe(sub) # blocks until sub.cancel()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Codegen
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
python -m tools.codegen.src.cli fetch # cache schema.json
|
|
82
|
+
python -m tools.codegen.src.cli emit Customer # Customer + everything it references
|
|
83
|
+
python -m tools.codegen.src.cli emit-all # whole model
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Output → `src/dataceen_client/generated/` (gitignored). Per entity you get
|
|
87
|
+
typed `TypedDict`s for read/create/filter/fields, a change-tracked `Update`
|
|
88
|
+
class, and an `<Entity>Client` wrapping the runtime helpers; `AllClient`
|
|
89
|
+
aggregates the top-level entities.
|
|
90
|
+
|
|
91
|
+
Codegen runs from a source checkout — it is intentionally not part of the
|
|
92
|
+
published wheel (only the runtime `dataceen_client` package is shipped).
|
|
93
|
+
|
|
94
|
+
## Tests
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
pytest # 88 unit tests, ~1.7s
|
|
98
|
+
RUN_INTEGRATION=1 pytest # also run live-backend integration tests (needs .env)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Example app
|
|
102
|
+
|
|
103
|
+
End-to-end example against the live backend:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
python -m examples.candy_shop.find
|
|
107
|
+
python -m examples.candy_shop.search_and_aggregate
|
|
108
|
+
python -m examples.candy_shop.subscriber
|
|
109
|
+
python -m examples.candy_shop.program # full read-only orchestrator
|
|
110
|
+
RUN_DESTRUCTIVE=1 python -m examples.candy_shop.program # also create/update/delete
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Docs
|
|
114
|
+
|
|
115
|
+
- `docs/plan.md` — phase plan
|
|
116
|
+
- `docs/decisions.md` — decisions and motivation
|
|
117
|
+
- `docs/progress.md` — live status
|
|
118
|
+
- `docs/dsl-comparison.md` — why pure-dict DSL for Python
|
|
119
|
+
- `docs/porting-guide.md` — Python-specific amendments to the canonical guide
|
|
120
|
+
- `docs/release.md` — version-bump and publish runbook
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
Proprietary — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Beslutade defaults — Python-port
|
|
2
|
+
|
|
3
|
+
Logg av Python-specifika beslut. Server-imposed beslut (operatorernas casing,
|
|
4
|
+
PascalCase-svar, `like`-semantik etc.) ärvs från
|
|
5
|
+
`DataceenClientTs/docs/porting-guide.md` #19–47 och repeteras inte här.
|
|
6
|
+
|
|
7
|
+
## 2026-05-01 — Projektstart
|
|
8
|
+
|
|
9
|
+
| # | Beslut | Motivering |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| 1 | **Python 3.10+ som minimum** | `X | Y` union-syntax, `dict[str, T]`-generics, `match` om vi behöver. 3.10 är välsupporterat överallt. |
|
|
12
|
+
| 2 | **Async-first API** (`httpx.AsyncClient`, `async def`) | Subscriptions är inneboende async (gRPC stream). Att blanda sync HTTP + async gRPC ger två kodvägar. Sync-wrapper kan adderas senare med `asyncio.run`. |
|
|
13
|
+
| 3 | **`httpx` istället för `requests` + `aiohttp`** | Modern, sync+async i samma paket, native HTTP/2. Default-timeout, AbortController-motsvarighet via `httpx.Timeout`. |
|
|
14
|
+
| 4 | **`msal` för Azure AD** | Officiell Microsoft-SDK, samma autentiseringsflöde som C# och TS. Har inbyggd token-cache. |
|
|
15
|
+
| 5 | **DSL-strategi A (pure dict)** | Se `docs/dsl-comparison.md`. Python's `__getattr__`-proxies ger ingen typsäkerhet och försämrar IDE-stöd; idiomatisk Python är `TypedDict` + `dict`. |
|
|
16
|
+
| 6 | **PascalCase i wire-typer** (`FindResult.Items`, `Customer._id`) | Matchar server-format 1:1. Att konvertera till snake_case skapar friktion vid paritet med C#/TS-dokumentation och GraphQL-strängar. Python-konsumenter får acceptera PascalCase-fält. |
|
|
17
|
+
| 7 | **`pytest` + `pytest-asyncio` + `respx`** | De facto-standard för Python-testning. `respx` för HTTP-mockning matchar Vitest/MSW-rollen i TS-porten. |
|
|
18
|
+
| 8 | **src-layout (`src/dataceen_client/`)** | Standardpraxis för moderna Python-paket; tvingar editable-installs att gå via paket-import istället för repo-rot. |
|
|
19
|
+
| 9 | **`force_refresh` via `app.remove_tokens_for_client()` före re-acquire** | MSAL Python's `acquire_token_for_client` har ingen `skip_cache`-parameter. Cleanest workaround: rensa app-only-cachen före nytt anrop på 401. |
|
|
20
|
+
| 10 | **Result-typer som `TypedDict` (total=False)** | PascalCase-nycklar bevarade. Pydantic kan komma som opt-in senare; `TypedDict` är dependency-free och räcker för read-modeller. |
|
|
21
|
+
|
|
22
|
+
## Öppna frågor
|
|
23
|
+
|
|
24
|
+
- Sync-wrapper-API (`DataceenClientSync`)? Beslut tas i fas 6 om exempelappen visar att det behövs.
|
|
25
|
+
- `dataclass` vs `TypedDict` vs `pydantic.BaseModel` för genererade entitet-typer? Tas i fas 3.
|
|
26
|
+
- gRPC-stub-generering: `grpc_tools.protoc` mot `dataceenevent.proto` i build-steg eller checkat in? Tas i fas 4.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# DSL-jämförelse — Python-port
|
|
2
|
+
|
|
3
|
+
**Datum:** 2026-05-01
|
|
4
|
+
**Beslut:** Strategi A (pure dict / objekt-literal) — för **alla tre** delar
|
|
5
|
+
(filter, fields, orderBy).
|
|
6
|
+
|
|
7
|
+
Detta avviker från TS-porten som valde C (hybrid: dict-filter, proxy-fields).
|
|
8
|
+
Motiveringen följer.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Use-case
|
|
13
|
+
|
|
14
|
+
Hämta 10 kunder vars `CustomerId` matchar `cst` OCH vars status är antingen
|
|
15
|
+
`Active` eller `Premium`. Returnera `_id`, `CustomerId`, `ExternalReference`
|
|
16
|
+
plus nästlad relation `CustomerPlacedOrder -> Order -> OrderId`. Sortera på
|
|
17
|
+
`CustomerId Ascending, ExternalReference Descending`.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
await api.Customer.find(
|
|
21
|
+
size=10,
|
|
22
|
+
filter={
|
|
23
|
+
"and": [
|
|
24
|
+
{"CustomerId": {"like": "cst"}},
|
|
25
|
+
{
|
|
26
|
+
"or": [
|
|
27
|
+
{"Status": {"eq": "Active"}},
|
|
28
|
+
{"Status": {"eq": "Premium"}},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
fields={
|
|
34
|
+
"_id": True,
|
|
35
|
+
"CustomerId": True,
|
|
36
|
+
"ExternalReference": True,
|
|
37
|
+
"CustomerPlacedOrder": {
|
|
38
|
+
"Order": {"OrderId": True},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
order_by=[
|
|
42
|
+
{"CustomerId": "Ascending"},
|
|
43
|
+
{"ExternalReference": "Descending"},
|
|
44
|
+
],
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Varför inte proxy-lambda för fields (som TS)?
|
|
51
|
+
|
|
52
|
+
Två separata problem på Python-sidan.
|
|
53
|
+
|
|
54
|
+
### 1. Typchecker-stöd är sämre
|
|
55
|
+
|
|
56
|
+
`__getattr__` returnerar `Any` som default. mypy/pyright kan inte härleda
|
|
57
|
+
strukturen `f.CustomerPlacedOrder(lambda r: r.Order(...))` utan att vi skriver
|
|
58
|
+
overloadade `__getattr__`-stubbar per fält per entitet — vilket gör att hela
|
|
59
|
+
"proxy-elegansen" är beroende av codegen som ändå producerar lika mycket TS-typ-kod.
|
|
60
|
+
|
|
61
|
+
I TS funkar `FieldsProxy<T>` mapped types med konditionella inferens som faktiskt
|
|
62
|
+
ger autocomplete. Pythons typsystem har inga conditional types av motsvarande styrka.
|
|
63
|
+
|
|
64
|
+
### 2. Statement-as-expression-mönstret är inte idiomatiskt
|
|
65
|
+
|
|
66
|
+
TS-porten skriver `f._id; f.CustomerId;` som hängande identifiers. Python tillåter
|
|
67
|
+
det syntaktiskt men är ovanligt; rinter och kollegor kommer att fråga "är det
|
|
68
|
+
en bug?". Lambdor på flera rader är dessutom inte tillåtet utan att gå via en
|
|
69
|
+
named function — vilket eliminerar lambda-syntaxens läsbarhetsfördel.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Varför inte builders med method-chaining?
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# Avfärdat
|
|
77
|
+
filter = (FilterBuilder[Customer]()
|
|
78
|
+
.where("CustomerId").like("cst")
|
|
79
|
+
.and_(FilterBuilder().where("Status").eq("Active")
|
|
80
|
+
.or_(FilterBuilder().where("Status").eq("Premium"))))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- Verbose i Python (måste returnera `self` på varje metod).
|
|
84
|
+
- Ger inte bättre typstöd än dict + `TypedDict`.
|
|
85
|
+
- Lägger till en abstraktion som måste serialiseras tillbaka till samma struktur
|
|
86
|
+
som dict-input ändå producerar.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Vad pure dict ger
|
|
91
|
+
|
|
92
|
+
- **Typsäkert via `TypedDict`** (genereras i fas 3): `CustomerFilter` är en
|
|
93
|
+
`TypedDict` per fält → IDE flaggar tryckfel direkt.
|
|
94
|
+
- **Triviell serialisering:** `filter_to_graphql(d)` är ren rekursion över strukturen.
|
|
95
|
+
- **Trivial codegen:** generatorn emit:ar bara typaliasar — inga runtime-byggare per entitet.
|
|
96
|
+
- **Lätt att kopiera mellan språk:** samma struktur fungerar i JSON, vilket är
|
|
97
|
+
praktiskt vid debug.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Korrelationer mot porting-guide
|
|
102
|
+
|
|
103
|
+
`DataceenClientTs/docs/porting-guide.md` rekommenderar för Python:
|
|
104
|
+
> Object-literal (dict) for everything. `__getattr__`-proxies are ergonomic
|
|
105
|
+
> but trip static type checkers.
|
|
106
|
+
|
|
107
|
+
Detta beslut följer rekommendationen.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Konsekvenser för fas 3 (generator)
|
|
112
|
+
|
|
113
|
+
Per entitet emit:ar generatorn bara *typer* (`TypedDict`):
|
|
114
|
+
|
|
115
|
+
1. `Customer` (read-model)
|
|
116
|
+
2. `CustomerCreate` (create-input)
|
|
117
|
+
3. `CustomerUpdate` (klass med `__setattr__`-tracking)
|
|
118
|
+
4. `CustomerFilter` (filter-input)
|
|
119
|
+
5. `CustomerSearchFilter` (separat — inkluderar relationer)
|
|
120
|
+
6. `CustomerOrderBy = list[dict[Literal[...field names...], OrderByDirection]]`
|
|
121
|
+
7. `CustomerFields = TypedDict` (rekursivt)
|
|
122
|
+
|
|
123
|
+
Runtime-helpers (`filter_to_graphql`, `order_by_to_graphql`, `collect_fields`,
|
|
124
|
+
`do_find`, `do_search`, `do_create`, …) delas mellan alla entiteter — skrivs
|
|
125
|
+
en gång i `dataceen_client/runtime/`.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Plan: portera Dataceen-klienten till Python
|
|
2
|
+
|
|
3
|
+
Parallell-klient-strategi: Python-klienten samexisterar med C#- och TS-klienterna,
|
|
4
|
+
ersätter dem inte. Tredje officiella språkporten efter `DataceenClient/` (C#) och
|
|
5
|
+
`DataceenClientTs/` (TypeScript).
|
|
6
|
+
|
|
7
|
+
## Förutsättningar (beslutade defaults)
|
|
8
|
+
|
|
9
|
+
| Fråga | Beslut |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Målversion | Python 3.10+ (typ-syntax `X | Y`, `dict[...]`) |
|
|
12
|
+
| Beroenden | `httpx` (HTTP), `msal` (Azure AD), `grpcio` + `protobuf` (subscriptions, optional) |
|
|
13
|
+
| Async-modell | `asyncio` — primär API är `async def` |
|
|
14
|
+
| Test-ramverk | `pytest` + `pytest-asyncio` + `respx` (HTTP mocking) |
|
|
15
|
+
| Repo-struktur | Eget paket under `C:\Source\Dataceen\DataceenClientPython\` |
|
|
16
|
+
| PyPI-namn | `dataceen-client` (tentativt) |
|
|
17
|
+
| Källlayout | `src/dataceen_client/` (src-layout) |
|
|
18
|
+
| Auth | Client-credentials via `msal.ConfidentialClientApplication` |
|
|
19
|
+
| DSL-strategi | **A — pure dict (objekt-literal)**. Se `docs/dsl-comparison.md`. |
|
|
20
|
+
| Typing | `TypedDict` + Pydantic v2 valbart i framtida fas; v1 levererar `dict`-baserade signaturer |
|
|
21
|
+
|
|
22
|
+
## Komponentmappning C#/TS → Python
|
|
23
|
+
|
|
24
|
+
| C# / TS-komponent | Python-motsvarighet | Svårighet |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `DataceenClient` (GraphQL över HTTP) | `httpx.AsyncClient` | Lätt |
|
|
27
|
+
| `DataceenClient` subscriptions (gRPC) | `grpcio` (async stub) | Medel |
|
|
28
|
+
| `TokenProvider` (MSAL) | `msal.ConfidentialClientApplication` | Lätt |
|
|
29
|
+
| `FilterBuilder<T>` / object-literal-filter | `dict` med typade `TypedDict`-aliaser | Lätt |
|
|
30
|
+
| `OrderByBuilder<T>` / array | `list[dict[str, Literal["Ascending","Descending"]]]` | Trivialt |
|
|
31
|
+
| `FieldsBuilder<T>` / proxy-lambda | Nested `dict`: `{"_id": True, "Order": {"OrderId": True}}` | Lätt |
|
|
32
|
+
| `CustomerUpdate` med ändringsspårning | Klass med `__setattr__`-hook + `_changed: set[str]` | Medel |
|
|
33
|
+
| `FindResult<T>`, `SearchResult<T>`, `MutationResult` | `TypedDict` (PascalCase för wire-paritet) | Trivialt |
|
|
34
|
+
| Genererad `AllClient` | Codegen i fas 3+ — emit:ar `.py`-moduler från GraphQL-introspection | Stort men rakframt |
|
|
35
|
+
| `EventHandlerList` + typade delegates | `dict[type, list[Callable]]` | Lätt |
|
|
36
|
+
| `PostGraphQLAsync` (retry/auth) | Custom retry-loop runt `httpx` | Lätt |
|
|
37
|
+
|
|
38
|
+
## Fasplan
|
|
39
|
+
|
|
40
|
+
Följer samma faser som TS-porten enligt `DataceenClientTs/docs/porting-guide.md`.
|
|
41
|
+
|
|
42
|
+
### Fas 0 — Spike (1–2 dagar)
|
|
43
|
+
- Token via `msal.ConfidentialClientApplication.acquire_token_for_client`
|
|
44
|
+
- GraphQL POST mot `apiUrl`
|
|
45
|
+
- gRPC subscription-stream mot `subscriptionUrl`
|
|
46
|
+
- Introspection POST → cacha `tools/codegen/cache/schema.json`
|
|
47
|
+
|
|
48
|
+
### Fas 1 — Kärn-runtime (3–5 dagar) — **denna sessionen**
|
|
49
|
+
Generisk `DataceenClient`-kärna utan typgenerering.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
src/dataceen_client/
|
|
53
|
+
__init__.py
|
|
54
|
+
config.py # DataceenConfig dataclass + load_config_from_env()
|
|
55
|
+
errors.py # DataceenError
|
|
56
|
+
types.py # FindResult, SearchResult, MutationResult (TypedDict)
|
|
57
|
+
token_provider.py # TokenProvider (MSAL)
|
|
58
|
+
http_transport.py # HttpTransport.post_graphql() — 401-refresh + retry
|
|
59
|
+
dataceen_client.py # DataceenClient.execute_query/execute_mutation
|
|
60
|
+
runtime/
|
|
61
|
+
filter.py # filter_to_graphql()
|
|
62
|
+
order_by.py # order_by_to_graphql()
|
|
63
|
+
fields.py # collect_fields()
|
|
64
|
+
aggregations.py # aggregation_fragment(depth)
|
|
65
|
+
operations.py # do_find / do_search / do_create / do_update / do_delete
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Fas 2 — DSL-prototyper + beslut (1 dag)
|
|
69
|
+
**Beslut redan taget:** strategi A (pure dict). Python's typcheckare har svårt med
|
|
70
|
+
proxy-attribute-access utan att tappa typsäkerhet. Dict + `TypedDict` är det
|
|
71
|
+
idiomatiska valet. Prototyparbete behövs alltså inte — kort dokumentation i
|
|
72
|
+
`docs/dsl-comparison.md`.
|
|
73
|
+
|
|
74
|
+
### Fas 3 — Kodgenerator v1 (1–2 veckor)
|
|
75
|
+
Generera typade `.py`-filer per entitet från GraphQL-introspection.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
tools/codegen/
|
|
79
|
+
src/
|
|
80
|
+
fetch_schema.py
|
|
81
|
+
parse_schema.py
|
|
82
|
+
emit_model.py
|
|
83
|
+
emit_filter.py
|
|
84
|
+
emit_create.py
|
|
85
|
+
emit_update.py
|
|
86
|
+
emit_client.py
|
|
87
|
+
cli.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Output i `src/dataceen_client/generated/`.
|
|
91
|
+
|
|
92
|
+
### Fas 4 — Subscriptions (3–5 dagar)
|
|
93
|
+
gRPC-baserad subscription-klient med baseload-semantik.
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
src/dataceen_client/subscriptions/
|
|
97
|
+
__init__.py
|
|
98
|
+
types.py
|
|
99
|
+
subscription.py
|
|
100
|
+
subscription_manager.py
|
|
101
|
+
grpc_client.py
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Fas 5 — Full modell (3–5 dagar)
|
|
105
|
+
Alla 9 noder + 18 relationer + ~12 sub-entiteter för CandyShopModel. Compound
|
|
106
|
+
mutations (`Create{From}{Rel}{To}`) + aggregations.
|
|
107
|
+
|
|
108
|
+
### Fas 6 — Exempel-app (3–5 dagar)
|
|
109
|
+
Port av `examples/candy-shop/` från TS:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
examples/candy_shop/
|
|
113
|
+
config.py
|
|
114
|
+
program.py
|
|
115
|
+
find.py
|
|
116
|
+
search_and_aggregate.py
|
|
117
|
+
subscriber.py
|
|
118
|
+
create.py / update.py / delete.py
|
|
119
|
+
handlers/
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Fas 7 — Paketering & release (2–3 dagar)
|
|
123
|
+
- `dataceen-client` på PyPI
|
|
124
|
+
- Sphinx-dokumentation (eller MkDocs)
|
|
125
|
+
- CI: GitHub Actions
|
|
126
|
+
|
|
127
|
+
## Kritiska beslutpunkter
|
|
128
|
+
|
|
129
|
+
| # | Beslut | Status |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| 1 | Parallell eller ersättande klient? | Parallell ✅ |
|
|
132
|
+
| 2 | Sync- eller async-API? | Async-first (`httpx.AsyncClient`); sync-wrapper kan läggas till senare |
|
|
133
|
+
| 3 | DSL-strategi | A (pure dict) ✅ — Python type-checkers fungerar bäst med strukturerad data |
|
|
134
|
+
| 4 | Schemakälla för generator | GraphQL introspection (samma som TS) |
|
|
135
|
+
| 5 | Validering av inputs | Lazy: `dict` skickas direkt; runtime-fel kommer från servern. Pydantic-overlay valbart senare. |
|
|
136
|
+
| 6 | Pydantic-modeller? | Optional opt-in i fas 3 — kan emit:as parallellt med `TypedDict` |
|
|
137
|
+
|
|
138
|
+
## Referensfiler
|
|
139
|
+
|
|
140
|
+
- `C:\Source\Dataceen\DataceenClient\` — C#-klienten (kanonisk)
|
|
141
|
+
- `C:\Source\Dataceen\DataceenClientTs\` — TS-klienten (verifierad mot live)
|
|
142
|
+
- `C:\Source\Dataceen\DataceenClientTs\docs\porting-guide.md` — kanonisk porting-guide
|
|
143
|
+
- `C:\Source\Dataceen\DataceenEvent\Protos\dataceenevent.proto` — gRPC-kontrakt
|