data-flow-control 0.1.0__tar.gz → 0.1.3__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 (177) hide show
  1. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/Cargo.lock +3 -3
  2. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/Cargo.toml +1 -1
  3. data_flow_control-0.1.3/LICENSE +21 -0
  4. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/PKG-INFO +7 -3
  5. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/catalog.rs +84 -0
  6. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_store/compiled.rs +2 -0
  7. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewrite_stats.rs +1 -1
  8. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/dimensions.rs +21 -11
  9. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/columns.rs +65 -2
  10. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/mod.rs +1 -1
  11. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/dimensions.rs +37 -0
  12. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/pyproject.toml +15 -5
  13. data_flow_control-0.1.3/python/data_flow_control/__init__.py +31 -0
  14. data_flow_control-0.1.3/python/data_flow_control/langchain/__init__.py +8 -0
  15. data_flow_control-0.1.3/python/data_flow_control/langchain/agent.py +114 -0
  16. data_flow_control-0.1.3/python/data_flow_control/langchain/call_tool_with_dataflow.py +160 -0
  17. data_flow_control-0.1.3/python/data_flow_control/langchain/database.py +466 -0
  18. data_flow_control-0.1.3/python/data_flow_control/langchain/middleware.py +127 -0
  19. data_flow_control-0.1.3/python/data_flow_control/langchain/schema.py +174 -0
  20. data_flow_control-0.1.3/python/data_flow_control/langchain/sql_validation.py +123 -0
  21. data_flow_control-0.1.3/python/data_flow_control/langchain/tool_spec.py +164 -0
  22. data_flow_control-0.1.3/python/data_flow_control/langchain/wrapper.py +128 -0
  23. data_flow_control-0.1.0/python/data_flow_control/__init__.py +0 -17
  24. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/Cargo.toml +0 -0
  25. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/benches/rewrite_perf.rs +0 -0
  26. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/aggregate_registry.rs +0 -0
  27. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/diagnostics.rs +0 -0
  28. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/explain.rs +0 -0
  29. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/full_push.rs +0 -0
  30. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/identifiers.rs +0 -0
  31. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/intern.rs +0 -0
  32. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/ir.rs +0 -0
  33. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/lib.rs +0 -0
  34. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/optimizer.rs +0 -0
  35. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/parser.rs +0 -0
  36. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/partial_push.rs +0 -0
  37. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/planner.rs +0 -0
  38. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy.rs +0 -0
  39. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_compile.rs +0 -0
  40. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_index.rs +0 -0
  41. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_parser.rs +0 -0
  42. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_pgn.pest +0 -0
  43. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_store/branch.rs +0 -0
  44. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_store/delete.rs +0 -0
  45. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_store/indexes.rs +0 -0
  46. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_store/memory.rs +0 -0
  47. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/policy_store/mod.rs +0 -0
  48. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/query_analysis.rs +0 -0
  49. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewrite_strategy.rs +0 -0
  50. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/aggregates.rs +0 -0
  51. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/columns.rs +0 -0
  52. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/constraint_preprocess.rs +0 -0
  53. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/derived_policy.rs +0 -0
  54. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/exists.rs +0 -0
  55. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/expr.rs +0 -0
  56. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/helpers.rs +0 -0
  57. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/limit.rs +0 -0
  58. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/mod.rs +0 -0
  59. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/plan/actions.rs +0 -0
  60. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/plan/applicability.rs +0 -0
  61. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/plan/dominance.rs +0 -0
  62. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/plan/mod.rs +0 -0
  63. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/plan/scope_plan.rs +0 -0
  64. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/plan/write_plan.rs +0 -0
  65. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/policy_expr.rs +0 -0
  66. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/projection.rs +0 -0
  67. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/resolution.rs +0 -0
  68. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/scope.rs +0 -0
  69. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/select.rs +0 -0
  70. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/types.rs +0 -0
  71. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/rewriter/write_path.rs +0 -0
  72. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/semiring.rs +0 -0
  73. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_set_index.rs +0 -0
  74. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_sets/analysis.rs +0 -0
  75. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_sets/annotations.rs +0 -0
  76. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_sets/branch.rs +0 -0
  77. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_sets/mod.rs +0 -0
  78. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_sets/rewrites.rs +0 -0
  79. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/source_sets/split.rs +0 -0
  80. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/ast_stats.rs +0 -0
  81. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/builders.rs +0 -0
  82. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/dialect.rs +0 -0
  83. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/expr.rs +0 -0
  84. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/expr_key.rs +0 -0
  85. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/parse.rs +0 -0
  86. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/sql/render.rs +0 -0
  87. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/statement_tables.rs +0 -0
  88. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/src/threshold.rs +0 -0
  89. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/common/duckdb.rs +0 -0
  90. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/common/mod.rs +0 -0
  91. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/conformance.rs +0 -0
  92. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/main.rs +0 -0
  93. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/partial_push.rs +0 -0
  94. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/pgn.rs +0 -0
  95. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/pgn_unique.rs +0 -0
  96. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/policy_storage.rs +0 -0
  97. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/rewrite_api_parity.rs +0 -0
  98. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/semiring.rs +0 -0
  99. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/source_sets.rs +0 -0
  100. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/symmetric_self_join.rs +0 -0
  101. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/threshold.rs +0 -0
  102. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/completion/unique_not_unique.rs +0 -0
  103. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/dialect.rs +0 -0
  104. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/execution/insert.rs +0 -0
  105. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/execution/kill.rs +0 -0
  106. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/execution/main.rs +0 -0
  107. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/execution/oracle.rs +0 -0
  108. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/execution/remove.rs +0 -0
  109. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/execution/update.rs +0 -0
  110. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/expected/duckdb/remove_scan.sql +0 -0
  111. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/expected/postgres/remove_scan.sql +0 -0
  112. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/expected/sqlite/remove_scan.sql +0 -0
  113. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/explain_strategy.rs +0 -0
  114. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/column_propagation.rs +0 -0
  115. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/exists_join.rs +0 -0
  116. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/having_where.rs +0 -0
  117. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/identifiers.rs +0 -0
  118. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/insert_shapes.rs +0 -0
  119. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/main.rs +0 -0
  120. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/multi_source.rs +0 -0
  121. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/operators.rs +0 -0
  122. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/query_shapes.rs +0 -0
  123. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/gaps/scalar_subquery.rs +0 -0
  124. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/indexed_candidate_differential.rs +0 -0
  125. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/insert_values_body.rs +0 -0
  126. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/paper_examples.rs +0 -0
  127. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/parser.rs +0 -0
  128. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/planner.rs +0 -0
  129. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/policy.rs +0 -0
  130. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/policy_dialect.rs +0 -0
  131. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/policy_store_memory.rs +0 -0
  132. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/derived_policy.rs +0 -0
  133. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/in_semijoin.rs +0 -0
  134. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/insert.rs +0 -0
  135. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/joins.rs +0 -0
  136. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/main.rs +0 -0
  137. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/output.rs +0 -0
  138. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/recursive.rs +0 -0
  139. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/scan.rs +0 -0
  140. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite/update.rs +0 -0
  141. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite_candidate_regression.rs +0 -0
  142. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite_fast_paths.rs +0 -0
  143. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite_large_registry.rs +0 -0
  144. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/rewrite_stats_timings.rs +0 -0
  145. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/scan_ready_expr.rs +0 -0
  146. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/source_set_split_no_parse.rs +0 -0
  147. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/sql_builders.rs +0 -0
  148. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/sql_columns.rs +0 -0
  149. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-core/tests/ui_resolution.rs +0 -0
  150. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/Cargo.toml +0 -0
  151. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/bindings/catalog.rs +0 -0
  152. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/bindings/errors.rs +0 -0
  153. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/bindings/mod.rs +0 -0
  154. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/bindings/planner.rs +0 -0
  155. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/bindings/policy.rs +0 -0
  156. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/bindings/stats.rs +0 -0
  157. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/passant-py/src/lib.rs +0 -0
  158. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/_rust.py +0 -0
  159. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/__init__.py +0 -0
  160. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/base.py +0 -0
  161. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/clickhouse.py +0 -0
  162. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/datafusion.py +0 -0
  163. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/duckdb.py +0 -0
  164. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/kill.py +0 -0
  165. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/pg_catalog.py +0 -0
  166. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/postgres.py +0 -0
  167. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/registry.py +0 -0
  168. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/sqlite.py +0 -0
  169. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/adapters/umbra.py +0 -0
  170. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/aggregate_introspection.py +0 -0
  171. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/catalog.py +0 -0
  172. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/connection.py +0 -0
  173. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/dialect.py +0 -0
  174. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/options.py +0 -0
  175. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/planner.py +0 -0
  176. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/policy.py +0 -0
  177. {data_flow_control-0.1.0 → data_flow_control-0.1.3}/python/data_flow_control/ui.py +0 -0
@@ -1594,7 +1594,7 @@ dependencies = [
1594
1594
 
1595
1595
  [[package]]
1596
1596
  name = "passant-cli"
1597
- version = "0.1.0"
1597
+ version = "0.1.3"
1598
1598
  dependencies = [
1599
1599
  "anyhow",
1600
1600
  "assert_cmd",
@@ -1606,7 +1606,7 @@ dependencies = [
1606
1606
 
1607
1607
  [[package]]
1608
1608
  name = "passant-core"
1609
- version = "0.1.0"
1609
+ version = "0.1.3"
1610
1610
  dependencies = [
1611
1611
  "anyhow",
1612
1612
  "criterion",
@@ -1624,7 +1624,7 @@ dependencies = [
1624
1624
 
1625
1625
  [[package]]
1626
1626
  name = "passant-py"
1627
- version = "0.1.0"
1627
+ version = "0.1.3"
1628
1628
  dependencies = [
1629
1629
  "passant-core",
1630
1630
  "pyo3",
@@ -3,7 +3,7 @@ members = ["passant-core", "passant-py"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.1.0"
6
+ version = "0.1.3"
7
7
  edition = "2024"
8
8
  license = "MIT"
9
9
  authors = ["Charlie Summers"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dataflowcontrol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,12 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: data-flow-control
3
- Version: 0.1.0
3
+ Version: 0.1.3
4
4
  Requires-Dist: boto3>=1.26.0
5
5
  Requires-Dist: duckdb>=1.3.0
6
+ Requires-Dist: langchain>=1.3,<2
7
+ Requires-Dist: langchain-core>=1.3,<2
6
8
  Requires-Dist: numpy>=1.24.4
7
9
  Requires-Dist: sqlglot==28.3.0
8
10
  Requires-Dist: clickhouse-connect>=0.7.0 ; extra == 'clickhouse'
9
- Requires-Dist: datafusion>=53.0.0 ; python_full_version >= '3.10' and extra == 'datafusion'
11
+ Requires-Dist: datafusion>=53.0.0 ; extra == 'datafusion'
10
12
  Requires-Dist: pyarrow>=14.0.0 ; extra == 'datafusion'
11
13
  Requires-Dist: maturin>=1.7.0 ; extra == 'dev'
12
14
  Requires-Dist: pytest>=8.4.2 ; extra == 'dev'
@@ -16,5 +18,7 @@ Provides-Extra: clickhouse
16
18
  Provides-Extra: datafusion
17
19
  Provides-Extra: dev
18
20
  Provides-Extra: postgres
21
+ License-File: LICENSE
19
22
  Summary: Rust-backed Data Flow Control rewrite engine
20
- Requires-Python: >=3.9
23
+ License-Expression: MIT
24
+ Requires-Python: >=3.10
@@ -16,6 +16,7 @@ use crate::policy::{PolicyIr, Resolution};
16
16
  use crate::policy_compile::{
17
17
  ParsedPolicyConstraint, parse_policy_constraint, parse_policy_constraint_with_registry,
18
18
  };
19
+ use crate::sql::collect_query_projection_column_names;
19
20
 
20
21
  /// Catalog facts supplied at policy registration time (from any adapter snapshot).
21
22
  #[derive(Debug, Default, Clone)]
@@ -389,6 +390,24 @@ fn validate_pgn_policy_parsed(
389
390
  dimension_columns.insert(TableKey::new(alias), columns.clone());
390
391
  dimension_columns.insert(TableKey::new(base), columns);
391
392
  }
393
+ for (alias, query_sql) in dimension_queries {
394
+ let columns = collect_query_projection_column_names(query_sql)
395
+ .map_err(|err| {
396
+ RewriteError::catalog_with_context(
397
+ ErrorKind::UnknownColumn,
398
+ format!(
399
+ "Dimension subquery '{alias}' has invalid projection for catalog validation: {err}"
400
+ ),
401
+ Some(alias.clone()),
402
+ None,
403
+ Some(constraint.to_string()),
404
+ Some("catalog_validation"),
405
+ )
406
+ })?
407
+ .into_iter()
408
+ .collect::<HashSet<_>>();
409
+ dimension_columns.insert(TableKey::new(alias), columns);
410
+ }
392
411
 
393
412
  let source_names = sources
394
413
  .iter()
@@ -495,6 +514,7 @@ fn validate_pgn_policy_parsed(
495
514
  &table_key,
496
515
  dimension_tables,
497
516
  dimension_aliases,
517
+ dimension_queries,
498
518
  &dimension_columns,
499
519
  ) {
500
520
  if !columns.contains(&column_key) {
@@ -544,6 +564,7 @@ fn resolve_dimension_columns<'a>(
544
564
  table_key: &TableKey,
545
565
  dimension_tables: &[String],
546
566
  dimension_aliases: &HashMap<String, String>,
567
+ dimension_queries: &HashMap<String, String>,
547
568
  dimension_columns: &'a HashMap<TableKey, HashSet<String>>,
548
569
  ) -> Option<&'a HashSet<String>> {
549
570
  if dimension_tables
@@ -552,7 +573,13 @@ fn resolve_dimension_columns<'a>(
552
573
  {
553
574
  return dimension_columns.get(table_key);
554
575
  }
576
+ if dimension_queries.contains_key(table_key.as_str()) {
577
+ return dimension_columns.get(table_key);
578
+ }
555
579
  if let Some(base) = dimension_aliases.get(table_key.as_str()) {
580
+ if dimension_queries.contains_key(base) {
581
+ return dimension_columns.get(table_key);
582
+ }
556
583
  return dimension_columns.get(&TableKey::new(base));
557
584
  }
558
585
  dimension_columns.get(table_key)
@@ -1054,6 +1081,63 @@ mod tests {
1054
1081
  .expect("dimension alias column should validate");
1055
1082
  }
1056
1083
 
1084
+ #[test]
1085
+ fn validates_constraint_columns_through_dimension_subquery_alias() {
1086
+ let catalog = sample_catalog();
1087
+ let mut dimension_aliases = HashMap::new();
1088
+ dimension_aliases.insert("u".to_string(), "u".to_string());
1089
+ let mut dimension_queries = HashMap::new();
1090
+ dimension_queries.insert(
1091
+ "u".to_string(),
1092
+ "(SELECT user_id FROM session_user)".to_string(),
1093
+ );
1094
+ let policy = PolicyIr::Pgn {
1095
+ sources: vec!["foo".into()],
1096
+ required_sources: Vec::new(),
1097
+ dimension_tables: Vec::new(),
1098
+ dimension_aliases,
1099
+ dimension_queries,
1100
+ sink: None,
1101
+ sink_alias: None,
1102
+ source_aliases: HashMap::new(),
1103
+ constraint: "max(foo.id) > 1 AND u.user_id = 1".into(),
1104
+ on_fail: Resolution::Remove,
1105
+ description: None,
1106
+ };
1107
+ catalog
1108
+ .validate_policy(&policy, &sample_registry())
1109
+ .expect("dimension subquery alias column should validate");
1110
+ }
1111
+
1112
+ #[test]
1113
+ fn rejects_unknown_dimension_subquery_column() {
1114
+ let catalog = sample_catalog();
1115
+ let mut dimension_aliases = HashMap::new();
1116
+ dimension_aliases.insert("u".to_string(), "u".to_string());
1117
+ let mut dimension_queries = HashMap::new();
1118
+ dimension_queries.insert(
1119
+ "u".to_string(),
1120
+ "(SELECT user_id FROM session_user)".to_string(),
1121
+ );
1122
+ let policy = PolicyIr::Pgn {
1123
+ sources: vec!["foo".into()],
1124
+ required_sources: Vec::new(),
1125
+ dimension_tables: Vec::new(),
1126
+ dimension_aliases,
1127
+ dimension_queries,
1128
+ sink: None,
1129
+ sink_alias: None,
1130
+ source_aliases: HashMap::new(),
1131
+ constraint: "max(foo.id) > 1 AND u.missing = 1".into(),
1132
+ on_fail: Resolution::Remove,
1133
+ description: None,
1134
+ };
1135
+ let err = catalog
1136
+ .validate_policy(&policy, &sample_registry())
1137
+ .unwrap_err();
1138
+ assert_eq!(err.kind(), ErrorKind::UnknownColumn);
1139
+ }
1140
+
1057
1141
  #[test]
1058
1142
  fn rejects_unknown_dimension_column() {
1059
1143
  let mut catalog = sample_catalog();
@@ -140,6 +140,7 @@ impl PolicyStore {
140
140
  &compiled.ast,
141
141
  policy.dimension_tables(),
142
142
  policy.dimension_aliases(),
143
+ policy.dimension_queries(),
143
144
  &source_key_set,
144
145
  )
145
146
  });
@@ -226,6 +227,7 @@ pub(crate) fn compile_branch_policy(
226
227
  &constraint_ast,
227
228
  policy.dimension_tables(),
228
229
  policy.dimension_aliases(),
230
+ policy.dimension_queries(),
229
231
  &source_key_set,
230
232
  )
231
233
  };
@@ -36,7 +36,7 @@ impl From<RewriteStats> for RewriteStatsExport {
36
36
  }
37
37
  }
38
38
 
39
- /// Optional rewrite instrumentation counters (see `passant/docs/performance.md`).
39
+ /// Optional rewrite instrumentation counters (see `developer-docs/performance.md`).
40
40
  #[derive(Debug, Default, Clone)]
41
41
  pub struct RewriteStats {
42
42
  pub total_policies: usize,
@@ -56,6 +56,7 @@ pub(crate) fn inject_policy_dimensions(
56
56
  constraint,
57
57
  dimension_tables,
58
58
  dimension_aliases,
59
+ dimension_queries,
59
60
  &source_keys,
60
61
  )?
61
62
  };
@@ -175,11 +176,9 @@ pub(crate) fn inject_policy_dimensions(
175
176
  append_dimension_join(select, factor, join.on)?;
176
177
  joined_keys.insert(table_key);
177
178
  } else {
178
- skipped.push(alias.clone());
179
- warnings.push(format!(
180
- "Dimension subquery '{alias}' was not joined: no equality link to the query or \
181
- another dimension in the policy constraint and catalog row count is not 1"
182
- ));
179
+ warnings.push(expansion_warning(alias));
180
+ append_dimension_join(select, factor, None)?;
181
+ joined_keys.insert(table_key);
183
182
  }
184
183
  }
185
184
 
@@ -405,9 +404,10 @@ pub(crate) fn compile_dimension_join_plan(
405
404
  constraint_ast: &Expr,
406
405
  dimension_tables: &[String],
407
406
  dimension_aliases: &HashMap<String, String>,
407
+ dimension_queries: &HashMap<String, String>,
408
408
  source_keys: &HashSet<TableKey>,
409
409
  ) -> Option<DimensionJoinPlan> {
410
- if dimension_tables.is_empty() {
410
+ if dimension_tables.is_empty() && dimension_queries.is_empty() {
411
411
  return None;
412
412
  }
413
413
  let mut dimension_keys = HashSet::new();
@@ -419,6 +419,9 @@ pub(crate) fn compile_dimension_join_plan(
419
419
  }
420
420
  }
421
421
  }
422
+ for alias in dimension_queries.keys() {
423
+ dimension_keys.insert(TableKey::new(alias));
424
+ }
422
425
  let mut source_conditions = HashMap::new();
423
426
  let mut dimension_edges = Vec::new();
424
427
  collect_dimension_join_graph(
@@ -454,17 +457,24 @@ fn dimension_join_graph(
454
457
  constraint: &str,
455
458
  dimension_tables: &[String],
456
459
  dimension_aliases: &HashMap<String, String>,
460
+ dimension_queries: &HashMap<String, String>,
457
461
  source_keys: &HashSet<TableKey>,
458
462
  ) -> Result<(HashMap<TableKey, Expr>, Vec<DimensionJoinEdge>), RewriteError> {
459
463
  let constraint = preprocess_policy_constraint(constraint);
460
464
  let expr = parse_projection_expr(&constraint).map_err(|err| {
461
465
  RewriteError::unsupported_statement(format!("dimension join analysis: {err}"))
462
466
  })?;
463
- let plan = compile_dimension_join_plan(&expr, dimension_tables, dimension_aliases, source_keys)
464
- .unwrap_or(DimensionJoinPlan {
465
- source_conditions: HashMap::new(),
466
- dimension_edges: Vec::new(),
467
- });
467
+ let plan = compile_dimension_join_plan(
468
+ &expr,
469
+ dimension_tables,
470
+ dimension_aliases,
471
+ dimension_queries,
472
+ source_keys,
473
+ )
474
+ .unwrap_or(DimensionJoinPlan {
475
+ source_conditions: HashMap::new(),
476
+ dimension_edges: Vec::new(),
477
+ });
468
478
  Ok((plan.source_conditions, plan.dimension_edges))
469
479
  }
470
480
 
@@ -1,10 +1,13 @@
1
1
  //! Qualified-column extraction from constraint ASTs.
2
2
 
3
3
  use sqlparser::ast::{
4
- Array, Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, Map, Subscript,
4
+ Array, Expr, Function, FunctionArg, FunctionArgExpr, FunctionArguments, Map, SelectItem,
5
+ SetExpr, Statement, Subscript,
5
6
  };
6
7
 
7
- use crate::identifiers::QualifiedColumn;
8
+ use crate::diagnostics::RewriteError;
9
+ use crate::identifiers::{ColumnName, QualifiedColumn};
10
+ use crate::parser::parse_query;
8
11
 
9
12
  pub fn collect_qualified_columns_from_expr(expr: &Expr) -> Vec<QualifiedColumn> {
10
13
  let mut found = Vec::new();
@@ -12,6 +15,66 @@ pub fn collect_qualified_columns_from_expr(expr: &Expr) -> Vec<QualifiedColumn>
12
15
  found
13
16
  }
14
17
 
18
+ /// Output column names from a dimension subquery SELECT list.
19
+ pub fn collect_query_projection_column_names(query_sql: &str) -> Result<Vec<String>, RewriteError> {
20
+ let select = dimension_subquery_select(query_sql)?;
21
+ let mut names = Vec::new();
22
+ for item in &select.projection {
23
+ match item {
24
+ SelectItem::UnnamedExpr(expr) => {
25
+ let Some(name) = projection_column_name(expr) else {
26
+ return Err(RewriteError::unsupported_statement(format!(
27
+ "dimension subquery projection must name columns explicitly: {item}"
28
+ )));
29
+ };
30
+ names.push(ColumnName::new(name).key());
31
+ }
32
+ SelectItem::ExprWithAlias { alias, .. } => {
33
+ names.push(ColumnName::new(alias.value.as_str()).key());
34
+ }
35
+ SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _) => {
36
+ return Err(RewriteError::unsupported_statement(
37
+ "dimension subquery projections with * are not supported for catalog validation",
38
+ ));
39
+ }
40
+ }
41
+ }
42
+ if names.is_empty() {
43
+ return Err(RewriteError::unsupported_statement(
44
+ "dimension subquery must project at least one column",
45
+ ));
46
+ }
47
+ Ok(names)
48
+ }
49
+
50
+ fn dimension_subquery_select(query_sql: &str) -> Result<sqlparser::ast::Select, RewriteError> {
51
+ let statement = parse_query(query_sql)?;
52
+ let Statement::Query(query) = statement else {
53
+ return Err(RewriteError::unsupported_statement(
54
+ "dimension subquery must be a query",
55
+ ));
56
+ };
57
+ select_from_set_expr(*query.body)
58
+ }
59
+
60
+ fn select_from_set_expr(body: SetExpr) -> Result<sqlparser::ast::Select, RewriteError> {
61
+ match body {
62
+ SetExpr::Select(select) => Ok(*select),
63
+ SetExpr::Query(query) => select_from_set_expr(*query.body),
64
+ other => Err(RewriteError::unsupported_statement(format!(
65
+ "dimension subquery must be a SELECT, got {other}"
66
+ ))),
67
+ }
68
+ }
69
+
70
+ fn projection_column_name(expr: &Expr) -> Option<String> {
71
+ match expr {
72
+ Expr::Identifier(ident) => Some(ident.value.clone()),
73
+ Expr::CompoundIdentifier(parts) => parts.last().map(|ident| ident.value.clone()),
74
+ _ => None,
75
+ }
76
+ }
77
+
15
78
  fn visit_expr(expr: &Expr, found: &mut Vec<QualifiedColumn>) {
16
79
  if let Some(column) = QualifiedColumn::from_expr(expr) {
17
80
  found.push(column);
@@ -25,5 +25,5 @@ pub use builders::{
25
25
  query_from_select, sanitize_projection_alias, scalar_subquery, statement_from_query,
26
26
  string_concat, string_literal, table_alias, table_factor, with_ctes, wrap_table_with_filter,
27
27
  };
28
- pub use columns::collect_qualified_columns_from_expr;
28
+ pub use columns::{collect_qualified_columns_from_expr, collect_query_projection_column_names};
29
29
  pub use expr::{rename_table_refs, replace_expr_subtrees, unqualify_table_refs};
@@ -132,6 +132,43 @@ fn dimension_alias_qualifiers_rewrite_after_injection() {
132
132
  );
133
133
  }
134
134
 
135
+ #[test]
136
+ fn dimension_subquery_alias_in_constraint_rewrites() {
137
+ let mut catalog = TableCatalog::new();
138
+ catalog.register_table("foo", vec!["id".to_string()]);
139
+ catalog.register_table("session_user", vec!["user_id".to_string()]);
140
+ catalog.register_table_row_count("session_user", 1);
141
+ let mut dimension_aliases = HashMap::new();
142
+ dimension_aliases.insert("u".to_string(), "u".to_string());
143
+ let mut dimension_queries = HashMap::new();
144
+ dimension_queries.insert(
145
+ "u".to_string(),
146
+ "(SELECT user_id FROM session_user)".to_string(),
147
+ );
148
+ let policy = PolicyIr::Pgn {
149
+ sources: vec!["foo".to_string()],
150
+ required_sources: Vec::new(),
151
+ dimension_tables: Vec::new(),
152
+ dimension_aliases,
153
+ dimension_queries,
154
+ sink: None,
155
+ sink_alias: None,
156
+ source_aliases: HashMap::new(),
157
+ constraint: "max(foo.id) > 0 AND u.user_id = 1".to_string(),
158
+ on_fail: Resolution::Remove,
159
+ description: None,
160
+ };
161
+ let sql = rewrite_with_catalog("SELECT foo.id FROM foo", &[policy], catalog);
162
+ assert!(
163
+ sql.contains("u.user_id = 1"),
164
+ "expected dimension subquery alias in rewritten SQL: {sql}"
165
+ );
166
+ assert!(
167
+ sql.contains("SELECT user_id FROM session_user") || sql.contains("session_user"),
168
+ "expected dimension subquery injected: {sql}"
169
+ );
170
+ }
171
+
135
172
  #[test]
136
173
  fn catalog_rejects_unknown_dimension_table() {
137
174
  let mut catalog = TableCatalog::new();
@@ -1,11 +1,15 @@
1
1
  [project]
2
2
  name = "data-flow-control"
3
- version = "0.1.0"
3
+ version = "0.1.3"
4
4
  description = "Rust-backed Data Flow Control rewrite engine"
5
- requires-python = ">=3.9"
5
+ license = "MIT"
6
+ license-files = ["LICENSE"]
7
+ requires-python = ">=3.10"
6
8
  dependencies = [
7
9
  "boto3>=1.26.0",
8
10
  "duckdb>=1.3.0",
11
+ "langchain>=1.3,<2",
12
+ "langchain-core>=1.3,<2",
9
13
  "numpy>=1.24.4",
10
14
  "sqlglot==28.3.0",
11
15
  ]
@@ -23,7 +27,7 @@ clickhouse = [
23
27
  "clickhouse-connect>=0.7.0",
24
28
  ]
25
29
  datafusion = [
26
- "datafusion>=53.0.0; python_version >= '3.10'",
30
+ "datafusion>=53.0.0",
27
31
  "pyarrow>=14.0.0",
28
32
  ]
29
33
 
@@ -45,10 +49,16 @@ markers = [
45
49
  "postgres: requires psycopg and Passant docker postgres service",
46
50
  "clickhouse: requires clickhouse-connect and Passant docker clickhouse service",
47
51
  "umbra: requires psycopg and Passant docker umbra service",
48
- "flock: optional Flock DuckDB extension tests (see passant/scripts/setup_flock.sh)",
52
+ "flock: optional Flock DuckDB extension tests (see scripts/setup_flock.sh)",
49
53
  "ui_extension: optional extended_duckdb UI stream union tests",
54
+ "langchain: LangChain tool-use integration tests",
50
55
  ]
51
56
 
57
+ [tool.ruff.lint.per-file-ignores]
58
+ "python/tests/test_langchain*.py" = ["E402"]
59
+ "python/tests/test_call_tool_with_dataflow.py" = ["E402"]
60
+
52
61
  [tool.ruff]
53
62
  line-length = 100
54
- target-version = "py39"
63
+ target-version = "py310"
64
+ exclude = ["extended_duckdb"]
@@ -0,0 +1,31 @@
1
+ from ._passant import PassantRewriteError
2
+ from .connection import dfc
3
+ from .dialect import Dialect
4
+ from .options import RewriteOptions, UiUpdateMode
5
+ from .policy import Policy, Resolution
6
+ from .ui import UiViolationEvent
7
+
8
+ __all__ = [
9
+ "create_agent",
10
+ "dfc",
11
+ "Dialect",
12
+ "langchain_dfc",
13
+ "Policy",
14
+ "Resolution",
15
+ "RewriteOptions",
16
+ "UiUpdateMode",
17
+ "PassantRewriteError",
18
+ "UiViolationEvent",
19
+ ]
20
+
21
+
22
+ def __getattr__(name: str):
23
+ if name == "create_agent":
24
+ from .langchain import create_agent as _create_agent
25
+
26
+ return _create_agent
27
+ if name == "langchain_dfc":
28
+ from .langchain import langchain_dfc as _langchain_dfc
29
+
30
+ return _langchain_dfc
31
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,8 @@
1
+ """LangChain integration for Passant data flow control."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .agent import create_agent, store_langchain_agent_config
6
+ from .wrapper import LangChainDFC, langchain_dfc
7
+
8
+ __all__ = ["LangChainDFC", "create_agent", "langchain_dfc", "store_langchain_agent_config"]
@@ -0,0 +1,114 @@
1
+ """LangChain agent configuration for Passant wrapping."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ from langchain.agents import create_agent as _langchain_create_agent
9
+
10
+ from .middleware import CALL_TOOL_WITH_DATAFLOW_NAME
11
+ from .tool_spec import ToolSpec, tools_to_specs
12
+
13
+ PASSANT_CONFIG_ATTR = "_passant_langchain_config"
14
+
15
+
16
+ class UnsupportedAgentError(ValueError):
17
+ """Raised when an agent cannot be wrapped through public LangChain hooks."""
18
+
19
+
20
+ @dataclass
21
+ class AgentConfig:
22
+ model: Any
23
+ tools: list[Any]
24
+ middleware: list[Any] = field(default_factory=list)
25
+ system_prompt: str | None = None
26
+ create_agent_kwargs: dict[str, Any] = field(default_factory=dict)
27
+
28
+
29
+ def create_agent(
30
+ model: Any,
31
+ tools: list[Any],
32
+ *,
33
+ middleware: list[Any] | None = None,
34
+ system_prompt: str | None = None,
35
+ **create_agent_kwargs: Any,
36
+ ) -> Any:
37
+ """Create a LangChain agent and attach Passant wrap configuration."""
38
+ agent = _langchain_create_agent(
39
+ model=model,
40
+ tools=tools,
41
+ middleware=tuple(middleware or ()),
42
+ system_prompt=system_prompt,
43
+ **create_agent_kwargs,
44
+ )
45
+ return store_langchain_agent_config(
46
+ agent,
47
+ model=model,
48
+ tools=tools,
49
+ middleware=middleware,
50
+ system_prompt=system_prompt,
51
+ **create_agent_kwargs,
52
+ )
53
+
54
+
55
+ def store_langchain_agent_config(
56
+ agent: Any,
57
+ *,
58
+ model: Any,
59
+ tools: list[Any],
60
+ middleware: list[Any] | None = None,
61
+ system_prompt: str | None = None,
62
+ **create_agent_kwargs: Any,
63
+ ) -> Any:
64
+ """Attach Passant agent configuration using only public create_agent inputs."""
65
+ config = AgentConfig(
66
+ model=model,
67
+ tools=list(tools),
68
+ middleware=list(middleware or []),
69
+ system_prompt=system_prompt,
70
+ create_agent_kwargs=dict(create_agent_kwargs),
71
+ )
72
+ setattr(agent, PASSANT_CONFIG_ATTR, config)
73
+ return agent
74
+
75
+
76
+ def resolve_agent_config(agent: Any) -> AgentConfig:
77
+ stored = getattr(agent, PASSANT_CONFIG_ATTR, None)
78
+ if stored is not None:
79
+ return stored
80
+
81
+ raise UnsupportedAgentError(
82
+ "Could not resolve LangChain agent configuration for the provided agent. "
83
+ "Create the agent with data_flow_control.langchain.create_agent(...), or call "
84
+ "store_langchain_agent_config(agent, model=..., tools=...) after create_agent(...). "
85
+ "Passant does not introspect private LangGraph state."
86
+ )
87
+
88
+
89
+ def build_wrapped_agent(
90
+ config: AgentConfig,
91
+ *,
92
+ visible_tools: list[Any],
93
+ middleware: list[Any],
94
+ ) -> Any:
95
+ wrapped = _langchain_create_agent(
96
+ model=config.model,
97
+ tools=visible_tools,
98
+ system_prompt=config.system_prompt,
99
+ middleware=[*config.middleware, *middleware],
100
+ **config.create_agent_kwargs,
101
+ )
102
+ setattr(wrapped, PASSANT_CONFIG_ATTR, config)
103
+ return wrapped
104
+
105
+
106
+ def specs_from_agent_tools(
107
+ tools: list[Any],
108
+ *,
109
+ output_schemas: dict[str, Any] | None,
110
+ ) -> list[ToolSpec]:
111
+ filtered = [
112
+ tool for tool in tools if getattr(tool, "name", None) != CALL_TOOL_WITH_DATAFLOW_NAME
113
+ ]
114
+ return tools_to_specs(filtered, output_schemas=output_schemas)