relationalai 1.0.0a3__py3-none-any.whl → 1.0.0a5__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.
Files changed (118) hide show
  1. relationalai/config/config.py +47 -21
  2. relationalai/config/connections/__init__.py +5 -2
  3. relationalai/config/connections/duckdb.py +2 -2
  4. relationalai/config/connections/local.py +31 -0
  5. relationalai/config/connections/snowflake.py +0 -1
  6. relationalai/config/external/raiconfig_converter.py +235 -0
  7. relationalai/config/external/raiconfig_models.py +202 -0
  8. relationalai/config/external/utils.py +31 -0
  9. relationalai/config/shims.py +1 -0
  10. relationalai/semantics/__init__.py +10 -8
  11. relationalai/semantics/backends/sql/sql_compiler.py +1 -4
  12. relationalai/semantics/experimental/__init__.py +0 -0
  13. relationalai/semantics/experimental/builder.py +295 -0
  14. relationalai/semantics/experimental/builtins.py +154 -0
  15. relationalai/semantics/frontend/base.py +67 -42
  16. relationalai/semantics/frontend/core.py +34 -6
  17. relationalai/semantics/frontend/front_compiler.py +209 -37
  18. relationalai/semantics/frontend/pprint.py +6 -2
  19. relationalai/semantics/metamodel/__init__.py +7 -0
  20. relationalai/semantics/metamodel/metamodel.py +2 -0
  21. relationalai/semantics/metamodel/metamodel_analyzer.py +58 -16
  22. relationalai/semantics/metamodel/pprint.py +6 -1
  23. relationalai/semantics/metamodel/rewriter.py +11 -7
  24. relationalai/semantics/metamodel/typer.py +116 -41
  25. relationalai/semantics/reasoners/__init__.py +11 -0
  26. relationalai/semantics/reasoners/graph/__init__.py +35 -0
  27. relationalai/semantics/reasoners/graph/core.py +9028 -0
  28. relationalai/semantics/std/__init__.py +30 -10
  29. relationalai/semantics/std/aggregates.py +641 -12
  30. relationalai/semantics/std/common.py +146 -13
  31. relationalai/semantics/std/constraints.py +71 -1
  32. relationalai/semantics/std/datetime.py +904 -21
  33. relationalai/semantics/std/decimals.py +143 -2
  34. relationalai/semantics/std/floats.py +57 -4
  35. relationalai/semantics/std/integers.py +98 -4
  36. relationalai/semantics/std/math.py +857 -35
  37. relationalai/semantics/std/numbers.py +216 -20
  38. relationalai/semantics/std/re.py +213 -5
  39. relationalai/semantics/std/strings.py +437 -44
  40. relationalai/shims/executor.py +60 -52
  41. relationalai/shims/fixtures.py +85 -0
  42. relationalai/shims/helpers.py +26 -2
  43. relationalai/shims/hoister.py +28 -9
  44. relationalai/shims/mm2v0.py +204 -173
  45. relationalai/tools/cli/cli.py +192 -10
  46. relationalai/tools/cli/components/progress_reader.py +1 -1
  47. relationalai/tools/cli/docs.py +394 -0
  48. relationalai/tools/debugger.py +11 -4
  49. relationalai/tools/qb_debugger.py +435 -0
  50. relationalai/tools/typer_debugger.py +1 -2
  51. relationalai/util/dataclasses.py +3 -5
  52. relationalai/util/docutils.py +1 -2
  53. relationalai/util/error.py +2 -5
  54. relationalai/util/python.py +23 -0
  55. relationalai/util/runtime.py +1 -2
  56. relationalai/util/schema.py +2 -4
  57. relationalai/util/structures.py +4 -2
  58. relationalai/util/tracing.py +8 -2
  59. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/METADATA +8 -5
  60. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/RECORD +118 -95
  61. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/WHEEL +1 -1
  62. v0/relationalai/__init__.py +1 -1
  63. v0/relationalai/clients/client.py +52 -18
  64. v0/relationalai/clients/exec_txn_poller.py +122 -0
  65. v0/relationalai/clients/local.py +23 -8
  66. v0/relationalai/clients/resources/azure/azure.py +36 -11
  67. v0/relationalai/clients/resources/snowflake/__init__.py +4 -4
  68. v0/relationalai/clients/resources/snowflake/cli_resources.py +12 -1
  69. v0/relationalai/clients/resources/snowflake/direct_access_resources.py +124 -100
  70. v0/relationalai/clients/resources/snowflake/engine_service.py +381 -0
  71. v0/relationalai/clients/resources/snowflake/engine_state_handlers.py +35 -29
  72. v0/relationalai/clients/resources/snowflake/error_handlers.py +43 -2
  73. v0/relationalai/clients/resources/snowflake/snowflake.py +277 -179
  74. v0/relationalai/clients/resources/snowflake/use_index_poller.py +8 -0
  75. v0/relationalai/clients/types.py +5 -0
  76. v0/relationalai/errors.py +19 -1
  77. v0/relationalai/semantics/lqp/algorithms.py +173 -0
  78. v0/relationalai/semantics/lqp/builtins.py +199 -2
  79. v0/relationalai/semantics/lqp/executor.py +68 -37
  80. v0/relationalai/semantics/lqp/ir.py +28 -2
  81. v0/relationalai/semantics/lqp/model2lqp.py +215 -45
  82. v0/relationalai/semantics/lqp/passes.py +13 -658
  83. v0/relationalai/semantics/lqp/rewrite/__init__.py +12 -0
  84. v0/relationalai/semantics/lqp/rewrite/algorithm.py +385 -0
  85. v0/relationalai/semantics/lqp/rewrite/constants_to_vars.py +70 -0
  86. v0/relationalai/semantics/lqp/rewrite/deduplicate_vars.py +104 -0
  87. v0/relationalai/semantics/lqp/rewrite/eliminate_data.py +108 -0
  88. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +25 -3
  89. v0/relationalai/semantics/lqp/rewrite/period_math.py +77 -0
  90. v0/relationalai/semantics/lqp/rewrite/quantify_vars.py +65 -31
  91. v0/relationalai/semantics/lqp/rewrite/unify_definitions.py +317 -0
  92. v0/relationalai/semantics/lqp/utils.py +11 -1
  93. v0/relationalai/semantics/lqp/validators.py +14 -1
  94. v0/relationalai/semantics/metamodel/builtins.py +2 -1
  95. v0/relationalai/semantics/metamodel/compiler.py +2 -1
  96. v0/relationalai/semantics/metamodel/dependency.py +12 -3
  97. v0/relationalai/semantics/metamodel/executor.py +11 -1
  98. v0/relationalai/semantics/metamodel/factory.py +2 -2
  99. v0/relationalai/semantics/metamodel/helpers.py +7 -0
  100. v0/relationalai/semantics/metamodel/ir.py +3 -2
  101. v0/relationalai/semantics/metamodel/rewrite/dnf_union_splitter.py +30 -20
  102. v0/relationalai/semantics/metamodel/rewrite/flatten.py +50 -13
  103. v0/relationalai/semantics/metamodel/rewrite/format_outputs.py +9 -3
  104. v0/relationalai/semantics/metamodel/typer/checker.py +6 -4
  105. v0/relationalai/semantics/metamodel/typer/typer.py +4 -3
  106. v0/relationalai/semantics/metamodel/visitor.py +4 -3
  107. v0/relationalai/semantics/reasoners/optimization/solvers_dev.py +1 -1
  108. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +336 -86
  109. v0/relationalai/semantics/rel/compiler.py +2 -1
  110. v0/relationalai/semantics/rel/executor.py +3 -2
  111. v0/relationalai/semantics/tests/lqp/__init__.py +0 -0
  112. v0/relationalai/semantics/tests/lqp/algorithms.py +345 -0
  113. v0/relationalai/tools/cli.py +339 -186
  114. v0/relationalai/tools/cli_controls.py +216 -67
  115. v0/relationalai/tools/cli_helpers.py +410 -6
  116. v0/relationalai/util/format.py +5 -2
  117. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/entry_points.txt +0 -0
  118. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,435 @@
1
+ import os
2
+ import re
3
+ from nicegui import ui
4
+ import json
5
+
6
+ from v0.relationalai.debugging import DEBUG_LOG_FILE # type: ignore[import-not-found]
7
+
8
+ #--------------------------------------------------
9
+ # Terminal nodes
10
+ #--------------------------------------------------
11
+
12
+ TERMINAL_NODES = [
13
+ "query"
14
+ ]
15
+
16
+ #--------------------------------------------------
17
+ # Debug log helpers
18
+ #--------------------------------------------------
19
+
20
+ class SpanNode:
21
+ def __init__(self, id, type, parent_id, start_timestamp, attrs=None):
22
+ self.id = id
23
+ self.type = type
24
+ self.parent_id = parent_id
25
+ self.start_timestamp = start_timestamp
26
+ self.end_timestamp = None
27
+ self.elapsed = None
28
+ self.start_attrs = attrs or {}
29
+ self.end_attrs = {}
30
+ self.children = []
31
+
32
+ def add_end_data(self, end_timestamp, elapsed, end_attrs=None):
33
+ self.end_timestamp = end_timestamp
34
+ self.elapsed = elapsed
35
+ if end_attrs:
36
+ # Merge end_attrs into attrs
37
+ self.end_attrs.update(end_attrs)
38
+
39
+ def add_child(self, child):
40
+ self.children.append(child)
41
+
42
+ def __str__(self, level=0):
43
+ indent = " " * level
44
+ result = f"{indent}{self.type} ({self.id}):\n"
45
+ result += f"{indent} elapsed: {self.elapsed:.6f}s\n"
46
+
47
+ if self.start_attrs or self.end_attrs:
48
+ result += f"{indent} start attributes:\n"
49
+ for key, value in self.start_attrs.items():
50
+ # Truncate long values for display
51
+ if isinstance(value, str) and len(value) > 100:
52
+ value = value[:97] + "..."
53
+ result += f"{indent} {key}: {value}\n"
54
+ result += f"{indent} end attributes:\n"
55
+ for key, value in self.end_attrs.items():
56
+ # Truncate long values for display
57
+ if isinstance(value, str) and len(value) > 100:
58
+ value = value[:97] + "..."
59
+ result += f"{indent} {key}: {value}\n"
60
+
61
+ if self.children:
62
+ result += f"{indent} children:\n"
63
+ for child in self.children:
64
+ result += child.__str__(level + 2)
65
+
66
+ return result
67
+
68
+ def parse_jsonl_to_tree(jsonl_content):
69
+ lines = jsonl_content.strip().split('\n')
70
+ nodes_by_id = {}
71
+ root_nodes = []
72
+
73
+ for line in lines:
74
+ data = json.loads(line)
75
+ event_type = data["event"]
76
+
77
+ if event_type == "span_start":
78
+ span = data["span"]
79
+ node = SpanNode(
80
+ id=span["id"],
81
+ type=span["type"],
82
+ parent_id=span["parent_id"],
83
+ start_timestamp=span["start_timestamp"],
84
+ attrs=span.get("attrs", {})
85
+ )
86
+ nodes_by_id[node.id] = node
87
+
88
+ # Link to parent if exists
89
+ if node.parent_id and node.parent_id in nodes_by_id:
90
+ nodes_by_id[node.parent_id].add_child(node)
91
+ elif node.parent_id is None:
92
+ root_nodes.append(node)
93
+
94
+ elif event_type == "span_end":
95
+ node_id = data["id"]
96
+ if node_id in nodes_by_id:
97
+ nodes_by_id[node_id].add_end_data(
98
+ end_timestamp=data["end_timestamp"],
99
+ elapsed=data.get("elapsed", 0),
100
+ end_attrs=data.get("end_attrs", {})
101
+ )
102
+
103
+ return root_nodes
104
+
105
+ #--------------------------------------------------
106
+ # UI
107
+ #--------------------------------------------------
108
+
109
+ last_mod_time = None
110
+ current_json_objects = []
111
+ active_ix = None
112
+ active_item = None
113
+
114
+ # this is more than toggles now that we accept regex for passes
115
+ toggles = {
116
+ "v1": False,
117
+ "dsl": False,
118
+ "metamodel": False,
119
+ "emitter": False,
120
+ "passes": False,
121
+ "pass_regex": None,
122
+ "type_graph": False,
123
+ "expand_all": False
124
+ }
125
+
126
+ checkboxes = {}
127
+
128
+ opened = set()
129
+
130
+ def toggle_open(span_id):
131
+ global opened
132
+ if span_id in opened:
133
+ opened.remove(span_id)
134
+ else:
135
+ opened.add(span_id)
136
+ sidebar.refresh() # type: ignore[attr-defined]
137
+ details.refresh() # type: ignore[attr-defined]
138
+
139
+ def to_ms(t):
140
+ if not t:
141
+ return "..."
142
+ return f"{t*1000:,.0f}"
143
+
144
+ def format_time(t):
145
+ if not t:
146
+ return "..."
147
+ if t > 1:
148
+ return f"{t:.1f}s"
149
+ elif t > 0.001:
150
+ return f"{t*1000:.1f}ms"
151
+ elif t > 0.0005:
152
+ return f"{t*1000:.2f}ms"
153
+ else:
154
+ return f"{t*1000000:.0f}us"
155
+
156
+ def header(text):
157
+ return ui.label(text).style("font-size: 1.3em; font-weight: bold;")
158
+
159
+ def replace_long_brace_contents(code_str):
160
+ def replacement(match):
161
+ string = match.group(0)
162
+ if len(string) > 300:
163
+ # Extract the first 50 and last 30 characters from the string within brackets
164
+ return '{' + string[1:51] + '...' + string[-31:-1] + '}'
165
+ else:
166
+ # If the string is not longer than 300 characters, return it unchanged
167
+ return string
168
+
169
+ # This regex matches sequences of characters wrapped in { }
170
+ brace_content_regex = r'\{[\s\S]*?\}'
171
+
172
+ # Use the sub method to replace the matched strings with the result of the replacement function
173
+ return re.sub(brace_content_regex, replacement, code_str)
174
+
175
+
176
+ def code(c, language="python"):
177
+ # do not wrap text when outputting Rel
178
+ if language != "rel":
179
+ c = replace_long_brace_contents(c)
180
+ # use python's syntax highlighting for Rel as well
181
+ if language == "rel":
182
+ language = "ruby"
183
+ if language == "lqp":
184
+ language = "clojure"
185
+ c = re.sub(r"→", "->", c)
186
+ c = re.sub(r"⇑", "^", c)
187
+ c = re.sub(r"…", "...", c)
188
+ c = re.sub(r"\?", "<optional>", c)
189
+ return ui.code(c, language=language).style("border:none; margin:0; padding-right: 30px; ").classes("w-full")
190
+
191
+ def code_with_header(header, c, language="python"):
192
+ header = f"# --- {header} {'-' * (40 - len(header))}"
193
+ c = header + "\n\n" + c.strip()
194
+ return code(c, language=language)
195
+
196
+ def span_code(span:SpanNode):
197
+ if span.type == "rule":
198
+ if span.start_attrs.get("source"):
199
+ code(span.start_attrs["source"]).on("click", lambda: toggle_open(span.id)).style("cursor: pointer;")
200
+ if not toggles["expand_all"] and span.id not in opened:
201
+ return
202
+ with ui.column().style("gap:0px; padding-left: 30px; "):
203
+ if toggles["dsl"]:
204
+ code_with_header("dsl", span.start_attrs["dsl"])
205
+ if toggles["metamodel"] and "metamodel" in span.end_attrs:
206
+ code_with_header("metamodel", span.end_attrs["metamodel"])
207
+ elif span.type == "query":
208
+ if span.start_attrs.get("source"):
209
+ code(span.start_attrs["source"]).on("click", lambda: toggle_open(span.id)).style("cursor: pointer;")
210
+ if not toggles["expand_all"] and span.id not in opened:
211
+ return
212
+ with ui.column().style("gap:0px; padding-left: 30px; "):
213
+ if toggles["dsl"]:
214
+ code_with_header("dsl", span.start_attrs["dsl"])
215
+ for child in span.children:
216
+ span_code(child)
217
+ elif span.type == "compile" and span.end_attrs.get("compile_type") == "model":
218
+ if toggles["metamodel"]:
219
+ code_with_header("model metamodel", span.start_attrs["metamodel"])
220
+ for child in span.children:
221
+ span_code(child)
222
+ if toggles["emitter"]:
223
+ if "rel" in span.end_attrs:
224
+ code_with_header("rel model", span.end_attrs["rel"], language="rel")
225
+ elif "lqp" in span.end_attrs:
226
+ code_with_header("lqp model", span.end_attrs["lqp"], language="lqp")
227
+ # else:
228
+ # code_with_header("model", "# No code was emitted", language="python")
229
+ elif span.type == "compile" and span.end_attrs.get("compile_type") == "query":
230
+ if toggles["metamodel"]:
231
+ code_with_header("metamodel", span.start_attrs["metamodel"])
232
+ for child in span.children:
233
+ span_code(child)
234
+ if toggles["emitter"]:
235
+ if "rel" in span.end_attrs:
236
+ code_with_header("rel query", span.end_attrs["rel"], language="rel")
237
+ elif "lqp" in span.end_attrs:
238
+ code_with_header("lqp query", span.end_attrs["lqp"], language="lqp")
239
+ # else:
240
+ # code_with_header("query", "# No code was emitted", language="python")
241
+ elif span.type == "passes":
242
+ type_graph_shown = False
243
+ if toggles["passes"]:
244
+ previous_code = None
245
+ for child in span.children:
246
+ if toggles["pass_regex"] and not re.match(toggles["pass_regex"], child.type):
247
+ continue
248
+ current_code = ""
249
+ if "metamodel" in child.end_attrs:
250
+ current_code = child.end_attrs["metamodel"]
251
+ if current_code == previous_code:
252
+ current_code = ""
253
+ else:
254
+ previous_code = current_code
255
+ code_element = code_with_header(child.type + f' ({format_time(child.elapsed)})', current_code)
256
+ code_element.props(f'id="pass-{child.id}"')
257
+ code_element.style("scroll-margin-top: 40px;")
258
+ if child.type == "InferTypes" and toggles["type_graph"]:
259
+ # show the type graph inline with InferTypes
260
+ type_graph_shown = True
261
+ for child2 in child.children:
262
+ if child2.end_attrs.get("type_graph"):
263
+ ui.mermaid(child2.end_attrs['type_graph'], {"maxEdges":10000}).style("display:flex; padding:0px 0px 0px 15px; gap:30px; overflow: auto;")
264
+ # show the type graph if it was not shown inline above
265
+ if toggles["type_graph"] and not type_graph_shown:
266
+ for child in span.children:
267
+ if child.type == "InferTypes":
268
+ for child2 in child.children:
269
+ if child2.end_attrs.get("type_graph"):
270
+ ui.mermaid(child2.end_attrs['type_graph'], {"maxEdges":10000}).style("display:flex; padding:0px 0px 0px 15px; gap:30px; overflow: auto;")
271
+ elif span.type == "v1":
272
+ if toggles["v1"]:
273
+ previous_code = None
274
+ for child in span.children:
275
+ current_code = ""
276
+ if "metamodel" in child.end_attrs:
277
+ current_code = child.end_attrs["metamodel"]
278
+ if current_code == previous_code:
279
+ current_code = ""
280
+ else:
281
+ previous_code = current_code
282
+ code_element = code_with_header(child.type + f' ({format_time(child.elapsed)})', current_code)
283
+ code_element.props(f'id="pass-{child.id}"')
284
+ code_element.style("scroll-margin-top: 40px;")
285
+ else:
286
+ for child in span.children:
287
+ span_code(child)
288
+
289
+ @ui.refreshable
290
+ def details():
291
+ with ui.column().style("gap:2px;"):
292
+ for root in current_json_objects:
293
+ span_code(root)
294
+
295
+
296
+ def handle_attributes(attrs):
297
+ if attrs.get("file"):
298
+ ui.label(f"{attrs['file']}: {attrs['line']}")
299
+ if attrs.get("source"):
300
+ code(attrs["source"])
301
+ if attrs.get("txn_id"):
302
+ ui.label(f"txn_id: {attrs['txn_id']}")
303
+ if attrs.get("name"):
304
+ ui.label(f"{attrs['name']}")
305
+ if attrs.get("code"):
306
+ code(attrs["code"])
307
+ if attrs.get("dsl"):
308
+ code(attrs["dsl"])
309
+ if attrs.get("metamodel"):
310
+ code(attrs["metamodel"])
311
+ if attrs.get("rel"):
312
+ code(attrs["rel"], language="rel")
313
+ if attrs.get("sql"):
314
+ code(attrs["sql"], language="sql")
315
+ if attrs.get("results"):
316
+ vals = attrs['results']
317
+ if len(vals):
318
+ keys = [k for k in vals[0].keys()]
319
+ columns = [{'name': k, 'label': k, 'field': k, "align": "left"} for k in keys]
320
+ ui.table(columns=columns, rows=vals)
321
+
322
+ def handle_body(span: SpanNode):
323
+ if span.children and span.type not in ["rule_batch"]:
324
+ for child in span.children:
325
+ span_ui(child, True)
326
+
327
+ def span_ui(span: SpanNode, is_pass: bool=False):
328
+ def jump_to_pass():
329
+ global toggles
330
+ checkboxes["passes"].set_value(True)
331
+ toggles["passes"] = True
332
+ sidebar.refresh() # type: ignore[attr-defined]
333
+ details.refresh() # type: ignore[attr-defined]
334
+ ui.navigate.to(f"#pass-{span.id}")
335
+
336
+ with ui.column().classes("w-full").style("padding:0px 0px 0px 15px; gap:2px; "):
337
+ with ui.row().classes("w-full").style("padding-left:5px; flex-wrap:nowrap; margin:0; background: #2c2c2c") as me:
338
+ name = span.type
339
+ if name == "create_v2":
340
+ name += " ➚"
341
+ ui.label(name).style("padding:0; margin:0; color: #DDD;")
342
+ ui.space()
343
+ ui.label(f"{to_ms(span.elapsed)}").style("padding-right: 5px; color:#999;")
344
+ if span.type == "create_v2":
345
+ txn_id = span.end_attrs.get("txn_id")
346
+ me.on("click", lambda: ui.navigate.to(f"https://171608476159.observeinc.com/workspace/41759331/log-explorer?datasetId=41832558&filter-rai_transaction_id={txn_id}", new_tab=True))
347
+ me.style("cursor: pointer;")
348
+ elif is_pass:
349
+ me.on("click", lambda: jump_to_pass())
350
+ me.style("cursor: pointer;")
351
+ handle_body(span)
352
+
353
+
354
+ @ui.refreshable
355
+ def sidebar():
356
+ with ui.column().style("min-width: 270px; max-width: 270px; margin-top: 50px; height: calc(100vh - 60px); overflow-x: hidden; overflow-y: auto; position: fixed; top: 0; right: 0; z-index: 1000;"):
357
+ for root in current_json_objects:
358
+ span_ui(root)
359
+
360
+
361
+ def poll():
362
+ global last_mod_time, active_item
363
+ global current_json_objects
364
+ # Check the last modification time of the file
365
+ try:
366
+ mod_time = os.path.getmtime(DEBUG_LOG_FILE)
367
+ if last_mod_time is None or mod_time > last_mod_time:
368
+ last_mod_time = mod_time
369
+ # File has changed, read and parse the new content
370
+ with open(DEBUG_LOG_FILE, 'r') as file:
371
+ content = file.read()
372
+ if content:
373
+ new_tree = parse_jsonl_to_tree(content)
374
+ # Update the current JSON objects
375
+ current_json_objects = new_tree
376
+
377
+ if active_ix is not None and len(current_json_objects) > active_ix:
378
+ active_item = current_json_objects[active_ix]
379
+ # Refresh the UI with the new objects
380
+ sidebar.refresh() # type: ignore[attr-defined]
381
+ details.refresh() # type: ignore[attr-defined]
382
+ except FileNotFoundError:
383
+ pass
384
+
385
+ def toggle(key):
386
+ global toggles
387
+ toggles[key] = not toggles[key]
388
+ sidebar.refresh() # type: ignore[attr-defined]
389
+ details.refresh() # type: ignore[attr-defined]
390
+
391
+ def set_toggle_value(key, value):
392
+ global toggles
393
+ toggles[key] = value
394
+ sidebar.refresh() # type: ignore[attr-defined]
395
+ details.refresh() # type: ignore[attr-defined]
396
+
397
+ def main(host="0.0.0.0", port=8080):
398
+ global checkboxes
399
+ global toggles
400
+ ui.dark_mode(None)
401
+ with ui.column():
402
+ with ui.row().style("flex-wrap: nowrap; position: fixed; top: 0; left: 0; right: 0; z-index: 1000; background: #3c3c3c; color: #DDD;"):
403
+ ui.switch('expand all', value=toggles["expand_all"], on_change=lambda: toggle("expand_all"))
404
+ checkboxes["dsl"] = ui.checkbox('dsl', on_change=lambda: toggle("dsl"))
405
+ checkboxes["v1"] = ui.checkbox('v1 passes', on_change=lambda: toggle("v1"))
406
+ checkboxes["passes"] = ui.checkbox('v0 passes', on_change=lambda: toggle("passes")).props('id=passes_box')
407
+ checkboxes["emitter"] = ui.checkbox('emitter', on_change=lambda: toggle("emitter"))
408
+ ui.button("↓", on_click=lambda: ui.run_javascript("window.scrollTo({top: document.body.scrollHeight, behavior: 'smooth'})")).style(
409
+ "margin: 2px;"
410
+ "border-radius: 20%; "
411
+ "border: 2px solid #666; "
412
+ ).props("title='Scroll to bottom'")
413
+ ui.button("↑", on_click=lambda: ui.run_javascript("window.scrollTo({top: 0, behavior: 'smooth'})")).style(
414
+ "margin: 2px;"
415
+ "border-radius: 20%; "
416
+ "border: 2px solid #666; "
417
+ ).props("title='Scroll to top'")
418
+
419
+ checkboxes["metamodel"] = ui.checkbox('v0 metamodel', on_change=lambda: toggle("metamodel"))
420
+ checkboxes["type_graph"] = ui.checkbox('v0 type graph', on_change=lambda: toggle("type_graph"))
421
+ ui.input(label='Filter passes by regex', on_change=lambda e: set_toggle_value("pass_regex", e.value)
422
+ ).props('clearable input-style="color: white" input-class="font-mono" flat dense')
423
+
424
+ with ui.row().style("flex-wrap: nowrap; margin-top: 30px;"):
425
+ with ui.column() as c:
426
+ c.style("padding-left: 2em;")
427
+ details()
428
+ with ui.column() as c:
429
+ sidebar()
430
+
431
+ ui.timer(1, poll)
432
+ ui.run(reload=False, host=host, port=port)
433
+
434
+ if __name__ in {"__main__", "__mp_main__"}:
435
+ main()
@@ -2,7 +2,6 @@ import os
2
2
  import re
3
3
  from nicegui import ui
4
4
  import json
5
- import dataclasses
6
5
 
7
6
 
8
7
  #--------------------------------------------------
@@ -32,7 +31,7 @@ def poll():
32
31
  content = file.read()
33
32
  if content:
34
33
  current_content = parse_file(content)
35
- view.refresh()
34
+ view.refresh() # type: ignore[attr-defined]
36
35
  except FileNotFoundError:
37
36
  pass
38
37
 
@@ -1,6 +1,4 @@
1
- from dataclasses import dataclass, field, fields, is_dataclass
2
- from functools import lru_cache
3
- from typing import Tuple, Any
1
+ from dataclasses import fields, is_dataclass
4
2
 
5
3
  def is_dataclass_instance(obj) -> bool:
6
4
  """Checks if the object is a dataclass instance, excluding the class itself."""
@@ -27,11 +25,11 @@ def print_tree(node, field_name=None, indent=0, hide_fields=[], seen=set()):
27
25
  elif isinstance(node, list) or isinstance(node, tuple):
28
26
  # just ignore sequences that are empty
29
27
  if node:
30
- _indent_print(indent, f"[", field_name)
28
+ _indent_print(indent, "[", field_name)
31
29
  next = indent + 2
32
30
  for item in node:
33
31
  print_tree(item, None, next, hide_fields)
34
- _indent_print(indent, f"]")
32
+ _indent_print(indent, "]")
35
33
  else:
36
34
  _indent_print(indent, str(node), field_name)
37
35
 
@@ -1,5 +1,4 @@
1
- from typing import Any, TypeVar
2
- from typing import Optional
1
+ from typing import TypeVar
3
2
 
4
3
  T = TypeVar('T')
5
4
  def include_in_docs(obj: T) -> T:
@@ -1,15 +1,13 @@
1
- from dataclasses import dataclass, field
1
+ from dataclasses import dataclass
2
2
  from io import StringIO
3
3
  from typing import Any, NoReturn, Optional
4
4
  import html
5
5
 
6
6
  from .source import SourcePos
7
- from .tracing import get_tracer
8
7
 
9
8
  from rich import box
10
9
  from rich.console import Console, Group
11
10
  from rich.panel import Panel
12
- from rich.rule import Rule
13
11
  from rich.syntax import Syntax
14
12
  from rich.table import Table as RichTable
15
13
  from rich.text import Text
@@ -70,7 +68,6 @@ class RichEmitter:
70
68
  # write to our buffer, not the real terminal
71
69
  console = Console(file=buf, record=False, color_system="auto", soft_wrap=False, force_terminal=True)
72
70
  sev_style = {"error": "bold red", "warning": "bold yellow", "info": "bold cyan"}.get(diag.severity, "bold")
73
- sev_sub_style = {"error": "red", "warning": "yellow", "info": "cyan"}.get(diag.severity, "cyan")
74
71
 
75
72
  # Title
76
73
  title_text = Text(f"[{diag.name}] ", style=sev_style)
@@ -196,4 +193,4 @@ def source(obj:Any) -> Source:
196
193
  return Source(source)
197
194
 
198
195
  def table(headers: list[str] = [], rows: list[list[str]] = []) -> Table:
199
- return Table(headers=headers, rows=rows)
196
+ return Table(headers=headers, rows=rows)
@@ -3,11 +3,33 @@ from decimal import Decimal as PyDecimal
3
3
  from types import NoneType
4
4
  import numpy as np
5
5
  import pandas as pd
6
+ import datetime as dt
7
+ from pandas import DataFrame
6
8
 
7
9
  #--------------------------------------------------
8
10
  # Python types
9
11
  #--------------------------------------------------
10
12
 
13
+ def column_to_concept_name(df:DataFrame, col) -> str:
14
+ """ Infer the type of the column in the dataframe and return the corresponding concept name. """
15
+ _type = df[col].dtype
16
+ if pd.api.types.is_datetime64_any_dtype(_type):
17
+ return "DateTime"
18
+ elif pd.api.types.is_object_dtype(_type) and is_date_column(df[col]):
19
+ return "Date"
20
+ elif pd.api.types.is_string_dtype(_type):
21
+ return "String"
22
+ else:
23
+ return pytype_to_concept_name.get(_type, "Any")
24
+
25
+ def is_date_column(col) -> bool:
26
+ """ Check whether the dataframe column contains date or datetime values. """
27
+ sample = col.dropna()
28
+ if sample.empty:
29
+ return False
30
+ sample_value = sample.iloc[0]
31
+ return isinstance(sample_value, dt.date) and not isinstance(sample_value, dt.datetime)
32
+
11
33
  pytype_to_concept_name: dict[object, str] = {
12
34
  NoneType: "Any",
13
35
  int: "Integer", float: "Number(38,14)", str: "String", bool: "Boolean",
@@ -27,6 +49,7 @@ pytype_to_concept_name: dict[object, str] = {
27
49
  **{t(): "Integer" for t in (pd.Int8Dtype, pd.Int16Dtype, pd.Int32Dtype, pd.Int64Dtype,
28
50
  pd.UInt8Dtype, pd.UInt16Dtype, pd.UInt32Dtype, pd.UInt64Dtype)},
29
51
  **{t(): "Number(38,14)" for t in (pd.Float32Dtype, pd.Float64Dtype)},
52
+ pd.StringDtype(na_value=np.nan): "String",
30
53
  pd.StringDtype(): "String",
31
54
  pd.BooleanDtype(): "Boolean",
32
55
  "int": "Integer", "float": "Number(38,14)", "str": "String", "bool": "Boolean",
@@ -4,7 +4,6 @@ import warnings
4
4
  import more_itertools
5
5
  from .tracing import get_tracer
6
6
  from .error import Diagnostic, RAIWarning, RichEmitter, HTMLEmitter, RAIException
7
- import traceback
8
7
 
9
8
  #------------------------------------------------------
10
9
  # Base Env
@@ -47,7 +46,7 @@ class EnvBase:
47
46
  if self.prefers_html:
48
47
  # In notebook-y envs, try to display inline
49
48
  try:
50
- from IPython.display import display, HTML as _HTML
49
+ from IPython.display import display, HTML as _HTML # type: ignore[import-not-found]
51
50
  display(_HTML(rendered))
52
51
  except Exception:
53
52
  sys.stderr.write("[HTML diagnostic suppressed]\n")
@@ -6,8 +6,6 @@ import re
6
6
  import textwrap
7
7
 
8
8
  from relationalai.util.error import warn
9
- from ..config import Config
10
- from ..config.connections.snowflake import SnowflakeConnection
11
9
 
12
10
  # config = Config()
13
11
  # conn = config.get_connection(SnowflakeConnection, name="snowflake")
@@ -112,7 +110,7 @@ def fetch_snowflake(database:str, schema:str, table_names:list[str]) -> dict[str
112
110
  # with debugging.span("fetch_schema", sql=query):
113
111
  try:
114
112
  columns = get_provider().sql(query)
115
- except Exception as e:
113
+ except Exception:
116
114
  columns = []
117
115
  assert isinstance(columns, list)
118
116
 
@@ -192,6 +190,6 @@ def sf_numeric_to_type_str(column_name: str, sf_type_info: dict) -> str:
192
190
  # Integers (load_csv only supports these two (and not 8/16/32 bit ints)
193
191
  bits = digits_to_bits(precision)
194
192
  # return Int128 if bits == 128 else Int64
195
- return f"Decimal(38,0)" if bits == 128 else f"Decimal(18,0)"
193
+ return "Decimal(38,0)" if bits == 128 else "Decimal(18,0)"
196
194
 
197
195
  return f"Decimal({precision},{scale})"
@@ -91,8 +91,10 @@ class KeyedDict(Generic[K, V]):
91
91
  del self._dict[self._key(k)]
92
92
 
93
93
  def __contains__(self, k: object) -> bool:
94
- try: return self._key(k) in self._dict # type: ignore[arg-type]
95
- except Exception: return False
94
+ try:
95
+ return self._key(k) in self._dict # type: ignore[arg-type]
96
+ except Exception:
97
+ return False
96
98
 
97
99
  def __len__(self) -> int:
98
100
  return len(self._dict)
@@ -1,6 +1,11 @@
1
1
  # tracer.py — stdlib-only OTEL-shaped tracer with live span_start + 200ms write throttling
2
2
  from __future__ import annotations
3
- import os, json, time, secrets, threading, atexit
3
+ import os
4
+ import json
5
+ import time
6
+ import secrets
7
+ import threading
8
+ import atexit
4
9
  from contextlib import ContextDecorator, asynccontextmanager
5
10
  from contextvars import ContextVar
6
11
  from typing import Optional, Callable, Any, Dict
@@ -160,7 +165,8 @@ class Tracer(ContextDecorator):
160
165
 
161
166
  def traceparent(self) -> str:
162
167
  s = self.current_span()
163
- if not s: return ""
168
+ if not s:
169
+ return ""
164
170
  # version(00)-trace_id-span_id-flags(01)
165
171
  return f"00-{s.trace_id}-{s.span_id}-01"
166
172
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: relationalai
3
- Version: 1.0.0a3
3
+ Version: 1.0.0a5
4
4
  Summary: RelationalAI Library and CLI
5
5
  Author-email: RelationalAI <support@relational.ai>
6
- Requires-Python: >=3.10
6
+ Requires-Python: <3.14,>=3.10
7
7
  Description-Content-Type: text/markdown
8
- Requires-Dist: lqp==0.1.19
8
+ Requires-Dist: lqp
9
9
  Requires-Dist: snowflake-connector-python[secure-local-storage]
10
10
  Requires-Dist: snowflake-snowpark-python>=1.38.0
11
11
  Requires-Dist: typing-extensions
@@ -15,10 +15,9 @@ Requires-Dist: numpy<2
15
15
  Requires-Dist: pandas
16
16
  Requires-Dist: colorama
17
17
  Requires-Dist: inquirerpy
18
- Requires-Dist: click==8.2.1
18
+ Requires-Dist: click
19
19
  Requires-Dist: gravis
20
20
  Requires-Dist: toml
21
- Requires-Dist: tomlkit
22
21
  Requires-Dist: requests
23
22
  Requires-Dist: pyarrow
24
23
  Requires-Dist: websockets
@@ -33,8 +32,12 @@ Requires-Dist: pyjwt
33
32
  Requires-Dist: more-itertools
34
33
  Requires-Dist: sqlglot
35
34
  Requires-Dist: pyyaml
35
+ Requires-Dist: confocal
36
36
  Requires-Dist: pydantic<2.12.0,>=2.0.0
37
37
  Requires-Dist: pydantic-settings<2.11.0,>=2.0.0
38
+ Requires-Dist: tomlkit
39
+ Requires-Dist: fastapi
40
+ Requires-Dist: uvicorn[standard]
38
41
  Provides-Extra: dev
39
42
  Requires-Dist: pytest; extra == "dev"
40
43
  Requires-Dist: pytest-cov; extra == "dev"