Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The DataHub REST API this SDK targets is a separate Spring Boot project; the HTT

This crate is a thin async HTTP SDK around a DataHub-style REST API. Entry point is `create_api_service()` in `src/lib.rs`, which returns an `Arc<ApiService>` built with `Arc::new_cyclic` so each subservice holds a `Weak<ApiService>` back-reference. Subservices are fields on `ApiService`:

- `time_series` (`src/timeseries/`) — `TimeSeries` + datapoint ingestion/retrieval. Neither `TimeSeries` nor `TimeSeriesUpdate` has **`securityCategories`**: it was stored, writable and returned, but nothing ever read it — no part in access control (dataset grants are Keycloak organization groups), no query filtering on it, and the backend silently dropped any id that did not already exist, so the field never round-tripped. It has been removed server-side along with its join table, and the api reads request bodies strictly, so sending it is now a 400. Files keep their own `securityCategories` (`INode` in `src/generic.rs`) — separate entity, separate question. `ListFieldU64` went with it: it was the only field of that type, so the Python wrapper class is gone too (`ListFieldStr` and `ListFieldIdCollection` remain). **`tableEngine` is stale in the same way but still present**: the api marks it `@JsonIgnore`, so no response carries it, flat or graph — it is still accepted on create, so the SDK keeps the field as write-only rather than removing it, and it always reads back `None`.
- `time_series` (`src/timeseries/`) — `TimeSeries` + datapoint ingestion/retrieval. Neither `TimeSeries` nor `TimeSeriesUpdate` has **`securityCategories`**: it was stored, writable and returned, but nothing ever read it — no part in access control (dataset grants are Keycloak organization groups), no query filtering on it, and the backend silently dropped any id that did not already exist, so the field never round-tripped. It has been removed server-side along with its join table, and the api reads request bodies strictly, so sending it is now a 400. Files keep their own `securityCategories` (`INode` in `src/generic.rs`) — separate entity, separate question. `ListFieldU64` went with it: it was the only field of that type, so the Python wrapper class is gone too (`ListFieldStr` and `ListFieldIdCollection` remain). **`tableEngine`** went the same way. No read had returned it since the api marked it `@JsonIgnore` — which ClickHouse engine backs a series is an internal storage decision — and it has now been removed from the api entirely, so the write side that made it worth keeping as a write-only field is gone too.
- `units` (`src/unit/`)
- `events` (`src/events/`) — event CRUD, filter/search, plus the vocabulary endpoints (`list_types`/`search_types` and the same pair for sub-types, statuses and sources, over `EventDimension`). Those answer "what values does this tenant actually use" for the four categorical fields and back filter dropdowns; they read small server-side dimension tables rather than scanning events, so they are cheap but *eventually consistent* with the events. Note the route asymmetry the SDK hides: `/events/list/{plural}` but `/events/search/{singular}`. `EventUpdate` has **no `event_time`**: an event's time is immutable after creation — the events table is partitioned by it, so ClickHouse refuses the mutation outright, and the api used to validate the field, echo the new value back with a 200 and then fail to apply it. It has been dropped from the update form, so sending it is now a 400. Record a corrected time as a new event.
- `resources` (`src/resources/`) — the generic node service. Its reads span **every** node type and answer with [`Node`](#the-polymorphic-node-type) rather than one flat shape; relationship edges live in `src/relations/` (`EdgeProxy`, `RelForm`, `RelatedNode`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,6 @@ class TimeSeries:
@labels.setter
def labels(self, value: list[str] | None) -> None: ...
@property
def table_engine(self) -> str | None:
"""The ClickHouse table engine. On a series reached through `neighbors()` this is the
API's default rather than data — re-read the series by id for the real value."""
@property
def id(self) -> int | None: ...
@property
def external_id(self) -> str: ...
Expand Down
1 change: 0 additions & 1 deletion datahub_python_bindings/src/timeseries/construction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ impl PyTimeSeries {
.map(|r| r.into_iter().map(RelatedNode::from).collect())
.unwrap_or_default(),
labels: None,
table_engine: None,
};
Ok(PyTimeSeries {
inner,
Expand Down
8 changes: 0 additions & 8 deletions datahub_python_bindings/src/timeseries/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,6 @@ impl PyTimeSeries {
pub fn set_labels(&mut self, value: Option<Vec<String>>) {
self.inner.labels = value;
}
/// The ClickHouse table engine backing this series. Server-assigned.
///
/// On a series reached through `neighbors()` this is the api's DTO default rather than data —
/// the graph does not store the column. Re-read the series by id for the real value.
#[getter]
pub fn table_engine(&self) -> Option<&str> {
self.inner.table_engine.as_deref()
}
/// Always `"timeseries"`. Present on every node class so data-driven code can dispatch
/// without an `isinstance` ladder.
#[getter]
Expand Down
3 changes: 0 additions & 3 deletions python_tests/test_polymorphic_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ def test_a_timeseries_read_through_resources_carries_its_timeseries_fields(
assert isinstance(node, TimeSeries)
assert node.unit == "bar"
assert node.value_type == "float"
# No table_engine: the api marks it @JsonIgnore, so no read returns it.
finally:
try:
sync_client.timeseries.delete([ext])
Expand Down Expand Up @@ -209,8 +208,6 @@ def reached():
assert isinstance(graph_ts, TimeSeries)
assert graph_ts.unit == "bar", "the graph carries the unit column"
assert graph_ts.value_type == "float"
# table_engine is the one field no read returns: the api marks it @JsonIgnore.
assert graph_ts.table_engine is None
finally:
try:
sync_client.timeseries.delete([ts_ext])
Expand Down
9 changes: 1 addition & 8 deletions src/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,8 +819,6 @@ mod tests {
assert_eq!(ts.unit.as_deref(), Some("deg C"));
assert_eq!(ts.unit_external_id.as_deref(), Some("deg_c"));
assert_eq!(ts.value_type.as_deref(), Some("float"));
// No `table_engine`: the api marks it @JsonIgnore, so no read carries it.
assert_eq!(ts.table_engine, None);
assert_eq!(ts.data_set_id, Some(21));
assert_eq!(ts.labels.as_deref(), Some(&["TIMESERIES".to_string()][..]));
}
Expand Down Expand Up @@ -864,7 +862,6 @@ mod tests {
let ts = node.into_time_series().expect("timeseries");
assert_eq!(ts.value_type, None, "not told, rather than a wrong default");
assert_eq!(ts.unit, None);
assert_eq!(ts.table_engine, None);
}

#[test]
Expand Down Expand Up @@ -913,7 +910,6 @@ mod tests {
let foreign = [
"unit",
"unitExternalId",
"tableEngine",
"valueType",
"policies",
"connectedDataSets",
Expand All @@ -926,10 +922,7 @@ mod tests {
];
let own: HashMap<&str, &[&str]> = HashMap::from([
("ASSET", &["geoLocation", "isRoot"][..]),
(
"TIMESERIES",
&["unit", "unitExternalId", "tableEngine", "valueType"][..],
),
("TIMESERIES", &["unit", "unitExternalId", "valueType"][..]),
("FUNCTION", &[][..]),
("DATASET", &["policies", "connectedDataSets"][..]),
("POLICY", &["type", "value", "deactivated", "templateId"][..]),
Expand Down
10 changes: 0 additions & 10 deletions src/timeseries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,14 +619,6 @@ pub struct TimeSeries {
/// heterogeneous `/resources` result — see [`crate::nodes::Node`].
#[serde(default, skip_serializing_if = "Option::is_none")]
pub labels: Option<Vec<String>>,
/// ClickHouse table engine backing this series (`MERGETREE` by default).
///
/// Stale: no read returns this. Which ClickHouse engine backs a series is an internal
/// storage decision, so the api marks it `@JsonIgnore` and omits it from every response —
/// flat and graph alike. It is still *accepted* on create, and the field is kept rather
/// than removed, so it is write-only in practice and always `None` on the way back.
#[serde(rename = "tableEngine", default, skip_serializing_if = "Option::is_none")]
pub table_engine: Option<String>,
}

impl TimeSeries {
Expand All @@ -646,7 +638,6 @@ impl TimeSeries {
last_updated_time: None,
related_resources: vec![],
labels: None,
table_engine: None,
}
}
pub fn from_dict(dict: HashMap<String, String>) -> Self {
Expand All @@ -667,7 +658,6 @@ impl TimeSeries {
last_updated_time: None,
related_resources: vec![],
labels: None,
table_engine: None,
}
}

Expand Down
Loading