dataface 0.1.6.dev62__py3-none-any.whl → 0.1.6.dev76__py3-none-any.whl

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.
@@ -65,7 +65,7 @@ class FaceRenderContext:
65
65
  """
66
66
 
67
67
  face_file: Path
68
- scoped_path: Path | None
68
+ scoped_path: Path
69
69
  scoped_base: Path
70
70
  project_root: Path
71
71
  output_dir: Path
@@ -74,37 +74,34 @@ class FaceRenderContext:
74
74
 
75
75
  def build_face_render_context(
76
76
  face_path: Path,
77
- project_dir: Path | None = None,
77
+ project_dir: Path,
78
78
  ) -> FaceRenderContext:
79
79
  """Resolve a face path and walk for dbt context.
80
80
 
81
- ``project_dir=None`` means "walk freely from the face's parent" — used by
82
- the CLI when ``--project-dir`` is omitted so a face under a dbt sub-project
83
- still anchors on that sub-project's root. A given ``project_dir`` is
84
- authoritative; the walk only contributes the dbt project path.
81
+ ``project_dir`` is authoritative; the walk only contributes the dbt project
82
+ path. Callers must resolve their project dir first (e.g. via
83
+ ``resolve_project_dir(raw_dir)`` at the CLI boundary).
85
84
  """
86
85
  if face_path.is_absolute():
87
86
  face_file = face_path.resolve()
88
87
  elif ".." in face_path.parts:
89
88
  face_file = (Path.cwd() / face_path).resolve()
90
89
  else:
91
- anchor = (
92
- project_dir.resolve()
93
- if project_dir is not None
94
- else resolve_project_dir(None)
95
- )
96
- face_file = (anchor / face_path).resolve()
97
-
98
- walk_root, dbt_project_path = discover_render_context(
90
+ face_file = (project_dir / face_path).resolve()
91
+
92
+ _, dbt_project_path = discover_render_context(
99
93
  face_file.parent,
100
94
  discovery_boundary_for_face(face_file.parent, project_dir),
101
95
  )
102
- project_root = project_dir.resolve() if project_dir is not None else walk_root
96
+ project_root = project_dir
103
97
 
104
98
  try:
105
- scoped_path: Path | None = face_file.relative_to(project_root)
99
+ scoped_path: Path = face_file.relative_to(project_root)
106
100
  except ValueError:
107
- scoped_path = face_file
101
+ raise ValueError(
102
+ f"Face file {face_file} is outside project_dir {project_root}. "
103
+ f"Pass an explicit --project-dir that contains the face file."
104
+ ) from None
108
105
 
109
106
  return FaceRenderContext(
110
107
  face_file=face_file,
@@ -129,18 +126,15 @@ class YamlRenderContext:
129
126
 
130
127
 
131
128
  def build_yaml_render_context(
132
- project_dir: Path | None = None,
129
+ project_dir: Path,
133
130
  ) -> YamlRenderContext:
134
131
  """Walk for dbt context anchored at the given project root.
135
132
 
136
- ``project_dir=None`` walks from cwd to discover the project root; a given
137
- ``project_dir`` is authoritative.
133
+ ``project_dir`` is authoritative. Callers must resolve their project dir
134
+ first (e.g. via ``resolve_project_dir(raw_dir)`` at the CLI boundary).
138
135
  """
139
- anchor = (
140
- project_dir.resolve() if project_dir is not None else resolve_project_dir(None)
141
- )
142
- walk_root, dbt_project_path = discover_render_context(anchor, None)
143
- project_root = anchor if project_dir is not None else walk_root
136
+ project_root = project_dir.resolve()
137
+ _, dbt_project_path = discover_render_context(project_root, None)
144
138
  return YamlRenderContext(
145
139
  project_root=project_root,
146
140
  output_dir=project_root,
dataface/ai/llm.py CHANGED
@@ -50,7 +50,8 @@ def _resolve_model(
50
50
  def _normalize_openai_tools(
51
51
  tools: list[dict[str, Any]] | None,
52
52
  ) -> list[dict[str, Any]]:
53
- source_tools = tools or ALL_TOOLS
53
+ # None means "use default agent tools"; [] means "no tools, text-only completion".
54
+ source_tools = ALL_TOOLS if tools is None else tools
54
55
  if not source_tools:
55
56
  return []
56
57
  if "input_schema" in source_tools[0]:
@@ -69,7 +70,8 @@ def _normalize_openai_tools(
69
70
  def _normalize_anthropic_tools(
70
71
  tools: list[dict[str, Any]] | None,
71
72
  ) -> list[dict[str, Any]]:
72
- source_tools = tools or ALL_TOOLS
73
+ # None means "use default agent tools"; [] means "no tools, text-only completion".
74
+ source_tools = ALL_TOOLS if tools is None else tools
73
75
  if not source_tools:
74
76
  return []
75
77
  if "input_schema" in source_tools[0]:
@@ -120,8 +122,14 @@ def _is_anthropic_api_error(exc: Exception) -> bool:
120
122
  # -- Provider stream adapter ------------------------------------------------
121
123
 
122
124
 
123
- def _iter_anthropic_stream(stream_ctx: Any) -> Iterator[StreamEvent]:
124
- """Parse Anthropic Messages API stream context manager into typed events."""
125
+ def _iter_anthropic_stream(
126
+ stream_ctx: Any, usage_sink: Any = None
127
+ ) -> Iterator[StreamEvent]:
128
+ """Parse Anthropic Messages API stream context manager into typed events.
129
+
130
+ ``usage_sink``, when given, is called once with the final message's usage
131
+ payload so the client can accumulate token totals.
132
+ """
125
133
  with stream_ctx as stream:
126
134
  for event in stream:
127
135
  if event.type == "content_block_delta" and event.delta.type == "text_delta":
@@ -129,6 +137,9 @@ def _iter_anthropic_stream(stream_ctx: Any) -> Iterator[StreamEvent]:
129
137
 
130
138
  final_message = stream.get_final_message()
131
139
 
140
+ if usage_sink is not None:
141
+ usage_sink(final_message.usage)
142
+
132
143
  for block in final_message.content:
133
144
  if block.type == "tool_use":
134
145
  yield ToolCallEvent(
@@ -144,6 +155,13 @@ class LLMClient(Protocol):
144
155
 
145
156
  provider: str
146
157
  model: str
158
+ # Cumulative usage across this client's lifetime. Every implementation must
159
+ # maintain these honestly — eval solvers read them to attribute per-case
160
+ # cost/calls. Declared here so callers access them directly (no getattr
161
+ # defaults, which would silently fabricate zeros for a client that forgot them).
162
+ total_input_tokens: int
163
+ total_output_tokens: int
164
+ llm_calls: int
147
165
 
148
166
  def stream_with_tools(
149
167
  self,
@@ -332,6 +350,17 @@ class AnthropicClient:
332
350
  )
333
351
  self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
334
352
  self._client: Any = None
353
+ # Cumulative token usage + call count (see LLMClient protocol).
354
+ self.total_input_tokens = 0
355
+ self.total_output_tokens = 0
356
+ self.llm_calls = 0
357
+
358
+ def _accumulate_usage(self, usage: Any) -> None:
359
+ """Add an Anthropic Messages usage payload to the running token totals."""
360
+ if usage is None:
361
+ return
362
+ self.total_input_tokens += getattr(usage, "input_tokens", 0) or 0
363
+ self.total_output_tokens += getattr(usage, "output_tokens", 0) or 0
335
364
 
336
365
  @property
337
366
  def client(self) -> Any:
@@ -404,6 +433,7 @@ class AnthropicClient:
404
433
  system_prompt: str,
405
434
  tools: list[dict[str, Any]] | None = None,
406
435
  ) -> Iterator[StreamEvent]:
436
+ self.llm_calls += 1
407
437
  try:
408
438
  yield from _iter_anthropic_stream(
409
439
  self.client.messages.stream(
@@ -412,7 +442,8 @@ class AnthropicClient:
412
442
  messages=self._messages_to_input(messages),
413
443
  tools=_normalize_anthropic_tools(tools),
414
444
  max_tokens=4096,
415
- )
445
+ ),
446
+ usage_sink=self._accumulate_usage,
416
447
  )
417
448
  except Exception as exc:
418
449
  if _is_anthropic_api_error(exc):
@@ -8,6 +8,8 @@ from dataface.agent_api import ProjectSession
8
8
  from dataface.agent_api._paths import (
9
9
  build_face_render_context,
10
10
  build_yaml_render_context,
11
+ resolve_project_dir,
12
+ resolve_project_dir_from_paths,
11
13
  )
12
14
  from dataface.agent_api.cache import cache_from_env
13
15
  from dataface.agent_api.dashboards import RenderedDashboard
@@ -119,7 +121,10 @@ def render_command(
119
121
  if ignore_codes:
120
122
  _emit_unknown_code_notices(ignore_codes)
121
123
 
122
- ctx = build_face_render_context(face_path, project_dir)
124
+ resolved_project_dir = resolve_project_dir_from_paths(
125
+ [face_path.resolve()], project_dir
126
+ )
127
+ ctx = build_face_render_context(face_path, resolved_project_dir)
123
128
  output_dir = ctx.output_dir
124
129
 
125
130
  with (
@@ -208,7 +213,8 @@ def render_command_from_yaml(
208
213
  if ignore_codes:
209
214
  _emit_unknown_code_notices(ignore_codes)
210
215
 
211
- ctx = build_yaml_render_context(project_dir)
216
+ resolved_project_dir = resolve_project_dir(project_dir)
217
+ ctx = build_yaml_render_context(resolved_project_dir)
212
218
  output_dir = ctx.output_dir
213
219
 
214
220
  with (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataface
3
- Version: 0.1.6.dev62
3
+ Version: 0.1.6.dev76
4
4
  Summary: Dataface - dbt-native dashboard and visualization layer
5
5
  Author: Fivetran, Inc.
6
6
  License-Expression: Apache-2.0
@@ -3,7 +3,7 @@ dataface/_docs_site.py,sha256=wREocIbQJ9oUfIpQBMCBpNGKZv-w6R0khOg_tSMQTeA,639
3
3
  dataface/_install_hint.py,sha256=mtNmc3xBoe4qtgsZc7UMpiMq_ajOeUyJ3GixCYzPjtE,1162
4
4
  dataface/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  dataface/agent_api/__init__.py,sha256=aQygBBltbxFjhUErS4fJJTsUF2NYrY-2wHxq42ZR7TQ,3497
6
- dataface/agent_api/_paths.py,sha256=Y5V28t5faCm6P4LYibAdOmniJymQjiQ_pV5gq4rTdig,4915
6
+ dataface/agent_api/_paths.py,sha256=onfFrfW8MIPzRyE0xazGn7-0g06MfuyAZ2FQ3p1-ZXI,4641
7
7
  dataface/agent_api/_session_store.py,sha256=viTnMGWNlT0fIKwXmjz3pb95PR1taIrfRq9NOWEMivg,16932
8
8
  dataface/agent_api/_state.py,sha256=mD4JsshDhRANhqb2TrR_NLPXsw-dB3m3bi1UnG2jCF0,723
9
9
  dataface/agent_api/cache.py,sha256=m1W8jTkGsaDSnnQB9Yncn8QlBwn4prgutVizL0ymqUM,767
@@ -40,7 +40,7 @@ dataface/ai/context.py,sha256=YxIkDgNYbRzoezC1jiIX3ULVA7ipNh-KrpJzKgvrQAQ,1648
40
40
  dataface/ai/events.py,sha256=QJZA_t09AU9BLDB63jKwssODoG5l_I1hyRXi0lXas7Y,1161
41
41
  dataface/ai/external_mcp.py,sha256=7uClq0PQjX9KkBIkg5HZg4_Ze4VV49lQE0XEcyIeTek,23130
42
42
  dataface/ai/generate_sql.py,sha256=uOtgO5oFMmM8uvsrk2owl9so3DGxdN2QSk6MOstZXhA,2394
43
- dataface/ai/llm.py,sha256=8JiVx7aBTNyQOjtgMVe-JHxFRmPAULkDf72ok0Mgf9g,15195
43
+ dataface/ai/llm.py,sha256=1taZucoFb_LgdnzqXVk8oHwNxHIgBRKs8rUpw3kitPI,16628
44
44
  dataface/ai/memories.py,sha256=fua8IJvkomQAatVFhUeSbS-B421ozd6QOpsiOfhpCWs,2608
45
45
  dataface/ai/prompts.py,sha256=jzQYccHbBczA_ahkDbbn0o4PGQtZRlgSWTL7bv-Wk8E,6518
46
46
  dataface/ai/schema_context.py,sha256=oH2tnIrvuRHSbJ9RtWTdu7MJ5MGC3N6vXDQomkhDJmI,5807
@@ -68,7 +68,7 @@ dataface/cli/commands/init.py,sha256=H3bf3nE1AcYeMRJtMEEGiCHduBK_1_BllWyn_nJRxU4
68
68
  dataface/cli/commands/inspect.py,sha256=eN0AdJksPbsiMAn_GNfnr5rekW88zv8MpCJa3R4OtlA,3624
69
69
  dataface/cli/commands/mcp_init.py,sha256=bNPTOJzu2EtHTY0oLdJQ8F9N8aPoR4ZCxa_fZtsqsvE,6815
70
70
  dataface/cli/commands/query.py,sha256=4d5Mht1dDh6613U-ZR90Ytrs5UkEoFZHvkHii2Ga9SM,15370
71
- dataface/cli/commands/render.py,sha256=MuV7Lr2fGPL0QLeV7V45WZbapusGFYBCIIxd-3Q13ys,11635
71
+ dataface/cli/commands/render.py,sha256=gHZ4UiXHrLqgp1tXT6M2fTy8-H3sC3abwp2TFJHoUcM,11882
72
72
  dataface/cli/commands/schema.py,sha256=dmHCY3JLpe6pqXM1RF0g83CIomFvhIlk9xknNWVgaWg,16483
73
73
  dataface/cli/commands/search.py,sha256=VWty53aifdg1USW7fsjMiE4giXjC4jjfn2NFaoBsdLk,1217
74
74
  dataface/cli/commands/serve.py,sha256=-SaYudiPYncijdA52QAhTChQMBB48jyMie5EE7FbnP4,4294
@@ -519,8 +519,8 @@ mdsvg/renderer.py,sha256=RL09P1uXFxEVSsehzzp5QRts5qGeQ5-rMLn1JIrYWl4,61196
519
519
  mdsvg/style.py,sha256=RKQATn2C-bymM_5ATZtsNOyyKKakjiVdqLUNrSosTfo,16191
520
520
  mdsvg/types.py,sha256=MQlqsUP5y78D8kX0brD-82DkBvaUYdbmkq1U95TnyWA,5093
521
521
  mdsvg/utils.py,sha256=CIqFACFaJiM0A_dl-hJXYhRqH-T7ayazP62wgfWGMjw,1866
522
- dataface-0.1.6.dev62.dist-info/METADATA,sha256=bng4taU9r513uskvHqviRuHMyxFindInd7lauPQZ5mw,11454
523
- dataface-0.1.6.dev62.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
524
- dataface-0.1.6.dev62.dist-info/entry_points.txt,sha256=imfZRsAzmIbDRHW7jqhn0nT6EbYWAyOiuHisxmfVgXs,46
525
- dataface-0.1.6.dev62.dist-info/licenses/LICENSE,sha256=Iy9gBB2gC8WGQEwHxJd4-huwUvxB6OMOoT3no6emeaQ,11345
526
- dataface-0.1.6.dev62.dist-info/RECORD,,
522
+ dataface-0.1.6.dev76.dist-info/METADATA,sha256=7mxSdfhiakEh_DWtJ-ANtM7hILFWILnS9fu_sOGhiPQ,11454
523
+ dataface-0.1.6.dev76.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
524
+ dataface-0.1.6.dev76.dist-info/entry_points.txt,sha256=imfZRsAzmIbDRHW7jqhn0nT6EbYWAyOiuHisxmfVgXs,46
525
+ dataface-0.1.6.dev76.dist-info/licenses/LICENSE,sha256=Iy9gBB2gC8WGQEwHxJd4-huwUvxB6OMOoT3no6emeaQ,11345
526
+ dataface-0.1.6.dev76.dist-info/RECORD,,