agentic-data-contracts 0.2.4__tar.gz → 0.2.5__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.
Files changed (78) hide show
  1. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/CHANGELOG.md +9 -0
  2. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/PKG-INFO +18 -1
  3. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/README.md +17 -0
  4. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/pyproject.toml +1 -1
  5. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/core/contract.py +11 -0
  6. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/semantic/base.py +8 -0
  7. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/semantic/cube.py +8 -1
  8. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/semantic/dbt.py +8 -1
  9. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/semantic/yaml_source.py +16 -1
  10. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/fixtures/semantic_source.yml +21 -0
  11. agentic_data_contracts-0.2.5/tests/test_semantic/test_relationships.py +83 -0
  12. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_semantic/test_yaml_source.py +1 -1
  13. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/.github/dependabot.yml +0 -0
  14. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/.github/workflows/ci.yml +0 -0
  15. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/.gitignore +0 -0
  16. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/.pre-commit-config.yaml +0 -0
  17. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/.python-version +0 -0
  18. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/CLAUDE.md +0 -0
  19. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/LICENSE +0 -0
  20. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/docs/architecture.md +0 -0
  21. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/examples/revenue_agent/agent.py +0 -0
  22. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/examples/revenue_agent/contract.yml +0 -0
  23. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/examples/revenue_agent/semantic.yml +0 -0
  24. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/examples/revenue_agent/setup_db.py +0 -0
  25. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/__init__.py +0 -0
  26. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/adapters/__init__.py +0 -0
  27. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/adapters/base.py +0 -0
  28. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/adapters/duckdb.py +0 -0
  29. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/bridge/__init__.py +0 -0
  30. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/bridge/compiler.py +0 -0
  31. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/core/__init__.py +0 -0
  32. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/core/schema.py +0 -0
  33. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/core/session.py +0 -0
  34. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/py.typed +0 -0
  35. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/semantic/__init__.py +0 -0
  36. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/tools/__init__.py +0 -0
  37. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/tools/factory.py +0 -0
  38. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/tools/middleware.py +0 -0
  39. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/tools/sdk.py +0 -0
  40. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/validation/__init__.py +0 -0
  41. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/validation/checkers.py +0 -0
  42. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/validation/explain.py +0 -0
  43. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/src/agentic_data_contracts/validation/validator.py +0 -0
  44. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/__init__.py +0 -0
  45. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/conftest.py +0 -0
  46. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/fixtures/minimal_contract.yml +0 -0
  47. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/fixtures/sample_cube_schema.yml +0 -0
  48. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/fixtures/sample_dbt_manifest.json +0 -0
  49. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/fixtures/valid_contract.yml +0 -0
  50. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_adapters/__init__.py +0 -0
  51. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_adapters/test_duckdb.py +0 -0
  52. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_bridge/__init__.py +0 -0
  53. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_bridge/test_compiler.py +0 -0
  54. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/__init__.py +0 -0
  55. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_contract.py +0 -0
  56. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_load_semantic_source.py +0 -0
  57. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_schema.py +0 -0
  58. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_sdk_config.py +0 -0
  59. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_session.py +0 -0
  60. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_system_prompt_metrics.py +0 -0
  61. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_core/test_wildcard_tables.py +0 -0
  62. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_public_api.py +0 -0
  63. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_semantic/__init__.py +0 -0
  64. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_semantic/test_cube.py +0 -0
  65. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_semantic/test_dbt.py +0 -0
  66. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_semantic/test_search.py +0 -0
  67. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/__init__.py +0 -0
  68. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/test_auto_load.py +0 -0
  69. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/test_factory.py +0 -0
  70. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/test_middleware.py +0 -0
  71. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/test_sdk.py +0 -0
  72. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/test_semantic_tools.py +0 -0
  73. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_tools/test_wildcard_tools.py +0 -0
  74. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_validation/__init__.py +0 -0
  75. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_validation/test_checkers.py +0 -0
  76. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_validation/test_explain.py +0 -0
  77. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/tests/test_validation/test_validator.py +0 -0
  78. {agentic_data_contracts-0.2.4 → agentic_data_contracts-0.2.5}/uv.lock +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.2.5] - 2026-03-29
6
+
7
+ ### Added
8
+
9
+ - **Table relationship metadata**: `Relationship` dataclass and `get_relationships()` on `SemanticSource` protocol for declaring join paths between tables (from/to column + relationship type)
10
+ - **Relationships in system prompt**: `to_system_prompt()` includes join paths so the agent knows how to combine tables correctly
11
+ - **YamlSource relationships**: Parsed from `relationships` section in semantic YAML files
12
+ - DbtSource and CubeSource return empty relationships (ready for future parsing of native join metadata)
13
+
5
14
  ## [0.2.4] - 2026-03-29
6
15
 
7
16
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-data-contracts
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: YAML-first data contract governance for AI agents
5
5
  Project-URL: Homepage, https://github.com/flyersworder/agentic-data-contracts
6
6
  Project-URL: Repository, https://github.com/flyersworder/agentic-data-contracts
@@ -277,6 +277,23 @@ semantic:
277
277
  path: "./cube/schema.yml"
278
278
  ```
279
279
 
280
+ ## Table Relationships
281
+
282
+ Define join paths so the agent knows how to combine tables correctly:
283
+
284
+ ```yaml
285
+ # semantic.yml
286
+ relationships:
287
+ - from: analytics.orders.customer_id
288
+ to: analytics.customers.id
289
+ type: many_to_one
290
+ - from: analytics.orders.product_id
291
+ to: analytics.products.id
292
+ type: many_to_one
293
+ ```
294
+
295
+ The agent sees these in its system prompt and uses them to write correct JOINs instead of guessing from column names.
296
+
280
297
  ## Scalable Metric Discovery
281
298
 
282
299
  For large data lakes with hundreds of KPIs, group metrics by domain and let the agent discover them efficiently:
@@ -224,6 +224,23 @@ semantic:
224
224
  path: "./cube/schema.yml"
225
225
  ```
226
226
 
227
+ ## Table Relationships
228
+
229
+ Define join paths so the agent knows how to combine tables correctly:
230
+
231
+ ```yaml
232
+ # semantic.yml
233
+ relationships:
234
+ - from: analytics.orders.customer_id
235
+ to: analytics.customers.id
236
+ type: many_to_one
237
+ - from: analytics.orders.product_id
238
+ to: analytics.products.id
239
+ type: many_to_one
240
+ ```
241
+
242
+ The agent sees these in its system prompt and uses them to write correct JOINs instead of guessing from column names.
243
+
227
244
  ## Scalable Metric Discovery
228
245
 
229
246
  For large data lakes with hundreds of KPIs, group metrics by domain and let the agent discover them efficiently:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentic-data-contracts"
3
- version = "0.2.4"
3
+ version = "0.2.5"
4
4
  description = "YAML-first data contract governance for AI agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -171,6 +171,17 @@ class DataContract:
171
171
  )
172
172
  sections.append(line)
173
173
 
174
+ # Table relationships
175
+ if semantic_source is not None:
176
+ rels = semantic_source.get_relationships()
177
+ if rels:
178
+ sections.append(
179
+ "\n### Table Relationships\n"
180
+ "Use these join paths when combining tables:"
181
+ )
182
+ for r in rels:
183
+ sections.append(f"- {r.from_} \u2192 {r.to} ({r.type})")
184
+
174
185
  # Resource limits
175
186
  res = self.schema.resources
176
187
  if res:
@@ -20,12 +20,20 @@ class MetricDefinition:
20
20
  filters: list[str] = field(default_factory=list)
21
21
 
22
22
 
23
+ @dataclass
24
+ class Relationship:
25
+ from_: str # "schema.table.column"
26
+ to: str # "schema.table.column"
27
+ type: str = "many_to_one" # many_to_one | one_to_one | many_to_many
28
+
29
+
23
30
  @runtime_checkable
24
31
  class SemanticSource(Protocol):
25
32
  def get_metrics(self) -> list[MetricDefinition]: ...
26
33
  def get_metric(self, name: str) -> MetricDefinition | None: ...
27
34
  def get_table_schema(self, schema: str, table: str) -> TableSchema | None: ...
28
35
  def search_metrics(self, query: str) -> list[MetricDefinition]: ...
36
+ def get_relationships(self) -> list[Relationship]: ...
29
37
 
30
38
 
31
39
  def fuzzy_search_metrics(
@@ -7,7 +7,11 @@ from pathlib import Path
7
7
  import yaml
8
8
 
9
9
  from agentic_data_contracts.adapters.base import Column, TableSchema
10
- from agentic_data_contracts.semantic.base import MetricDefinition, fuzzy_search_metrics
10
+ from agentic_data_contracts.semantic.base import (
11
+ MetricDefinition,
12
+ Relationship,
13
+ fuzzy_search_metrics,
14
+ )
11
15
 
12
16
 
13
17
  class CubeSource:
@@ -54,5 +58,8 @@ class CubeSource:
54
58
  def search_metrics(self, query: str) -> list[MetricDefinition]:
55
59
  return fuzzy_search_metrics(self._metrics, self.get_metric, query)
56
60
 
61
+ def get_relationships(self) -> list[Relationship]:
62
+ return [] # TODO: parse from Cube joins config
63
+
57
64
  def get_table_schema(self, schema: str, table: str) -> TableSchema | None:
58
65
  return self._tables.get(f"{schema}.{table}")
@@ -7,7 +7,11 @@ from pathlib import Path
7
7
  from typing import Any
8
8
 
9
9
  from agentic_data_contracts.adapters.base import Column, TableSchema
10
- from agentic_data_contracts.semantic.base import MetricDefinition, fuzzy_search_metrics
10
+ from agentic_data_contracts.semantic.base import (
11
+ MetricDefinition,
12
+ Relationship,
13
+ fuzzy_search_metrics,
14
+ )
11
15
 
12
16
 
13
17
  class DbtSource:
@@ -77,5 +81,8 @@ class DbtSource:
77
81
  def search_metrics(self, query: str) -> list[MetricDefinition]:
78
82
  return fuzzy_search_metrics(self._metrics, self.get_metric, query)
79
83
 
84
+ def get_relationships(self) -> list[Relationship]:
85
+ return [] # TODO: parse from dbt manifest relationships/refs
86
+
80
87
  def get_table_schema(self, schema: str, table: str) -> TableSchema | None:
81
88
  return self._tables.get(f"{schema}.{table}")
@@ -7,7 +7,11 @@ from pathlib import Path
7
7
  import yaml
8
8
 
9
9
  from agentic_data_contracts.adapters.base import Column, TableSchema
10
- from agentic_data_contracts.semantic.base import MetricDefinition, fuzzy_search_metrics
10
+ from agentic_data_contracts.semantic.base import (
11
+ MetricDefinition,
12
+ Relationship,
13
+ fuzzy_search_metrics,
14
+ )
11
15
 
12
16
 
13
17
  class YamlSource:
@@ -38,6 +42,14 @@ class YamlSource:
38
42
  for c in t.get("columns", [])
39
43
  ]
40
44
  )
45
+ self._relationships = [
46
+ Relationship(
47
+ from_=r["from"],
48
+ to=r["to"],
49
+ type=r.get("type", "many_to_one"),
50
+ )
51
+ for r in raw.get("relationships", [])
52
+ ]
41
53
 
42
54
  def get_metrics(self) -> list[MetricDefinition]:
43
55
  return list(self._metrics)
@@ -51,5 +63,8 @@ class YamlSource:
51
63
  def search_metrics(self, query: str) -> list[MetricDefinition]:
52
64
  return fuzzy_search_metrics(self._metrics, self.get_metric, query)
53
65
 
66
+ def get_relationships(self) -> list[Relationship]:
67
+ return list(self._relationships)
68
+
54
69
  def get_table_schema(self, schema: str, table: str) -> TableSchema | None:
55
70
  return self._tables.get(f"{schema}.{table}")
@@ -29,3 +29,24 @@ tables:
29
29
  - name: status
30
30
  type: VARCHAR
31
31
  description: "Order status: pending, completed, cancelled"
32
+ - name: customer_id
33
+ type: INTEGER
34
+ description: "FK to customers"
35
+
36
+ - schema: analytics
37
+ table: customers
38
+ columns:
39
+ - name: id
40
+ type: INTEGER
41
+ description: "Primary key"
42
+ - name: name
43
+ type: VARCHAR
44
+ description: "Customer name"
45
+ - name: region
46
+ type: VARCHAR
47
+ description: "Geographic region"
48
+
49
+ relationships:
50
+ - from: analytics.orders.customer_id
51
+ to: analytics.customers.id
52
+ type: many_to_one
@@ -0,0 +1,83 @@
1
+ """Tests for table relationship metadata."""
2
+
3
+ from pathlib import Path
4
+
5
+ from agentic_data_contracts.core.contract import DataContract
6
+ from agentic_data_contracts.core.schema import (
7
+ AllowedTable,
8
+ DataContractSchema,
9
+ SemanticConfig,
10
+ )
11
+ from agentic_data_contracts.semantic.cube import CubeSource
12
+ from agentic_data_contracts.semantic.dbt import DbtSource
13
+ from agentic_data_contracts.semantic.yaml_source import YamlSource
14
+
15
+
16
+ def test_yaml_source_loads_relationships(fixtures_dir: Path) -> None:
17
+ source = YamlSource(fixtures_dir / "semantic_source.yml")
18
+ rels = source.get_relationships()
19
+ assert len(rels) == 1
20
+ assert rels[0].from_ == "analytics.orders.customer_id"
21
+ assert rels[0].to == "analytics.customers.id"
22
+ assert rels[0].type == "many_to_one"
23
+
24
+
25
+ def test_yaml_source_no_relationships(tmp_path: Path) -> None:
26
+ (tmp_path / "empty.yml").write_text("metrics: []")
27
+ source = YamlSource(tmp_path / "empty.yml")
28
+ assert source.get_relationships() == []
29
+
30
+
31
+ def test_dbt_source_returns_empty_relationships(
32
+ fixtures_dir: Path,
33
+ ) -> None:
34
+ source = DbtSource(fixtures_dir / "sample_dbt_manifest.json")
35
+ assert source.get_relationships() == []
36
+
37
+
38
+ def test_cube_source_returns_empty_relationships(
39
+ fixtures_dir: Path,
40
+ ) -> None:
41
+ source = CubeSource(fixtures_dir / "sample_cube_schema.yml")
42
+ assert source.get_relationships() == []
43
+
44
+
45
+ def test_system_prompt_includes_relationships(
46
+ fixtures_dir: Path,
47
+ ) -> None:
48
+ source = YamlSource(fixtures_dir / "semantic_source.yml")
49
+ schema = DataContractSchema(
50
+ name="test",
51
+ semantic=SemanticConfig(
52
+ allowed_tables=[
53
+ AllowedTable.model_validate(
54
+ {"schema": "analytics", "tables": ["orders", "customers"]}
55
+ ),
56
+ ],
57
+ ),
58
+ )
59
+ dc = DataContract(schema)
60
+ prompt = dc.to_system_prompt(semantic_source=source)
61
+ assert "Table Relationships" in prompt
62
+ assert "analytics.orders.customer_id" in prompt
63
+ assert "analytics.customers.id" in prompt
64
+ assert "many_to_one" in prompt
65
+
66
+
67
+ def test_system_prompt_no_relationships_when_empty(
68
+ fixtures_dir: Path,
69
+ ) -> None:
70
+ source = DbtSource(fixtures_dir / "sample_dbt_manifest.json")
71
+ schema = DataContractSchema(
72
+ name="test",
73
+ semantic=SemanticConfig(
74
+ allowed_tables=[
75
+ AllowedTable.model_validate(
76
+ {"schema": "analytics", "tables": ["orders"]}
77
+ ),
78
+ ],
79
+ ),
80
+ )
81
+ dc = DataContract(schema)
82
+ prompt = dc.to_system_prompt(semantic_source=source)
83
+ assert "Table Relationships" not in prompt
@@ -39,7 +39,7 @@ def test_get_metric_not_found(source: YamlSource) -> None:
39
39
  def test_get_table_schema(source: YamlSource) -> None:
40
40
  schema = source.get_table_schema("analytics", "orders")
41
41
  assert schema is not None
42
- assert len(schema.columns) == 4
42
+ assert len(schema.columns) == 5
43
43
  col_names = [c.name for c in schema.columns]
44
44
  assert "id" in col_names
45
45
  assert "amount" in col_names