relationalai 1.0.0a3__py3-none-any.whl → 1.0.0a4__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 (29) hide show
  1. relationalai/config/shims.py +1 -0
  2. relationalai/semantics/__init__.py +7 -1
  3. relationalai/semantics/frontend/base.py +19 -13
  4. relationalai/semantics/frontend/core.py +30 -2
  5. relationalai/semantics/frontend/front_compiler.py +38 -11
  6. relationalai/semantics/frontend/pprint.py +1 -1
  7. relationalai/semantics/metamodel/rewriter.py +6 -2
  8. relationalai/semantics/metamodel/typer.py +70 -26
  9. relationalai/semantics/reasoners/__init__.py +11 -0
  10. relationalai/semantics/reasoners/graph/__init__.py +38 -0
  11. relationalai/semantics/reasoners/graph/core.py +9015 -0
  12. relationalai/shims/hoister.py +9 -0
  13. relationalai/shims/mm2v0.py +32 -24
  14. relationalai/tools/cli/cli.py +138 -0
  15. relationalai/tools/cli/docs.py +394 -0
  16. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a4.dist-info}/METADATA +5 -3
  17. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a4.dist-info}/RECORD +29 -24
  18. v0/relationalai/clients/exec_txn_poller.py +91 -0
  19. v0/relationalai/clients/resources/snowflake/__init__.py +2 -2
  20. v0/relationalai/clients/resources/snowflake/direct_access_resources.py +16 -10
  21. v0/relationalai/clients/resources/snowflake/snowflake.py +43 -14
  22. v0/relationalai/clients/resources/snowflake/use_index_poller.py +8 -0
  23. v0/relationalai/errors.py +18 -0
  24. v0/relationalai/semantics/lqp/executor.py +3 -1
  25. v0/relationalai/semantics/lqp/rewrite/extract_keys.py +25 -3
  26. v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +335 -84
  27. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a4.dist-info}/WHEEL +0 -0
  28. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a4.dist-info}/entry_points.txt +0 -0
  29. {relationalai-1.0.0a3.dist-info → relationalai-1.0.0a4.dist-info}/top_level.txt +0 -0
@@ -130,6 +130,15 @@ class Hoister(ContainerWalker):
130
130
  self.hoists[child] = parent_hoists
131
131
  self.allowed_vars[child] = allowed
132
132
  self.allowed_vars[container] = allowed
133
+ # this might be a match/union that just acts as a filter for some sibling logical,
134
+ # so we need to create an artificial demand for the allowed vars in the parent container
135
+ # to hoist them up if there's no other output providing them.
136
+ # ex: define(foo(Person)).where(union(Person.age > 10, Person.age < 5))
137
+ # parent = self.scope.parent.get(container, None)
138
+ # if parent:
139
+ # for allowed_var in allowed:
140
+ # self._register_use(allowed_var, input=True, container=parent)
141
+ # self.scope.var_refs[allowed_var]
133
142
 
134
143
  if container.scope:
135
144
  # when leaving the scope, compute hoisted vars
@@ -217,7 +217,7 @@ class Translator():
217
217
  "like": "like_match",
218
218
  "regex_escape": "escape_regex_metachars",
219
219
  }
220
- for lib in ["core", "aggregates", "common", "datetime", "floats", "math", "numbers", "strings", "re", "lqp"]:
220
+ for lib in ["core", "aggregates", "constraints", "common", "datetime", "floats", "math", "numbers", "strings", "re", "lqp"]:
221
221
  for x in b._libraries[lib].data.values():
222
222
  v0_name = RENAMES.get(x.name, x.name)
223
223
  if v0_name in v0_builtins.builtin_relations_by_name:
@@ -320,7 +320,13 @@ class Translator():
320
320
 
321
321
  def translate_annotation(self, a: mm.Annotation, parent, ctx) -> v0.Annotation:
322
322
  if a.relation.name in v0_builtins.builtin_annotations_by_name:
323
- return getattr(v0_builtins, a.relation.name + "_annotation") # type: ignore
323
+ if not a.args:
324
+ return getattr(v0_builtins, a.relation.name + "_annotation") # type: ignore
325
+ else:
326
+ return v0.Annotation(
327
+ relation=getattr(v0_builtins, a.relation.name), # type: ignore
328
+ args=tuple(self.translate_value(arg, a, ctx) for arg in a.args)
329
+ )
324
330
 
325
331
  return v0.Annotation(
326
332
  relation=self.translate_node(a.relation, a, Context.MODEL), # type: ignore
@@ -335,7 +341,7 @@ class Translator():
335
341
  # Abstract types (should be removed once our typer is done because they should not
336
342
  # show up in the typed metamodel.)
337
343
  b.core.Any: v0_types.Any,
338
- # b.core.AnyEntity: v0_types.AnyEntity,
344
+ b.core.AnyEntity: v0_types.AnyEntity,
339
345
  b.core.Number: v0_types.Number, # v0 typer can figure the rest out
340
346
  # Concrete types
341
347
  b.core.Boolean: v0_types.Bool,
@@ -362,6 +368,7 @@ class Translator():
362
368
  annotations.append(v0_builtins.external_annotation)
363
369
  name = self.translate_table_name(t)
364
370
  fields.insert(0, v0.Field(name="symbol", type=v0_types.Symbol, input=False)) # type: ignore
371
+ annotations.extend(self.translate_seq(t.annotations, t, ctx)) # type: ignore
365
372
 
366
373
  type_relation = v0.Relation(
367
374
  name=name,
@@ -372,9 +379,8 @@ class Translator():
372
379
  )
373
380
  for super_type in t.super_types:
374
381
  super_rel = self.translate_node(super_type, t, Context.TASK)
375
- if not super_rel:
382
+ if not isinstance(super_rel, v0.Relation):
376
383
  continue
377
- assert isinstance(super_rel, v0.Relation)
378
384
  v = v0.Var(type=actual_type, name=type_relation.name.lower())
379
385
  self.maintenance_rules.append(v0.Logical(
380
386
  engine=None,
@@ -745,7 +751,6 @@ class Translator():
745
751
  assert isinstance(r.domain, mm.Logical)
746
752
  assert isinstance(r.check, mm.Logical)
747
753
  if not r.domain.body and all(isinstance(c, mm.Lookup) and c.relation is b.constraints.unique_fields for c in r.check.body):
748
- return
749
754
  v0_reqs = []
750
755
  # v0 expects a check with both the domain and the relation in it followed by the unique
751
756
  # constraint, so we construct a logical with all of that in it
@@ -754,7 +759,7 @@ class Translator():
754
759
  fields = c.args[0] # tuple of fields
755
760
  assert isinstance(fields, tuple) and all(isinstance(f, mm.Field) for f in fields)
756
761
  first_field = fields[0]
757
- if isinstance(first_field.type, mm.Table):
762
+ if isinstance(first_field.type, mm.Table): #type: ignore
758
763
  # skip union types for now
759
764
  continue
760
765
  assert isinstance(first_field, mm.Field)
@@ -779,17 +784,17 @@ class Translator():
779
784
  ))
780
785
  return v0_reqs # type: ignore
781
786
 
782
- # check = v0.Check(
783
- # check=self.translate_node(r.check, r, ctx), # type: ignore
784
- # error=self.translate_node(r.error, r, ctx) if r.error else None, # type: ignore
785
- # annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
786
- # )
787
- # return v0.Require(
788
- # engine=self.translate_reasoner(r.reasoner, r, Context.MODEL),
789
- # domain=self.translate_node(r.domain, r, ctx), # type: ignore
790
- # checks=(check,), # type: ignore
791
- # annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
792
- # )
787
+ check = v0.Check(
788
+ check=self.translate_node(r.check, r, ctx), # type: ignore
789
+ error=self.translate_node(r.error, r, ctx) if r.error else None, # type: ignore
790
+ annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
791
+ )
792
+ return v0.Require(
793
+ engine=self.translate_reasoner(r.reasoner, r, Context.MODEL),
794
+ domain=self.translate_node(r.domain, r, ctx), # type: ignore
795
+ checks=(check,), # type: ignore
796
+ annotations=self.translate_frozen(r.annotations, r, ctx) # type: ignore
797
+ )
793
798
  return None
794
799
 
795
800
  # -----------------------------
@@ -1114,7 +1119,7 @@ class Translator():
1114
1119
  # subtract 1 from the index to convert from 1-based to 0-based
1115
1120
  tasks = []
1116
1121
  for index in indices:
1117
- arg = args[index]
1122
+ arg = l.args[index]
1118
1123
  if isinstance(arg, mm.Literal):
1119
1124
  if l.relation.fields[index].input:
1120
1125
  new = mm.Literal(arg.type, arg.value + 1) # type: ignore
@@ -1122,7 +1127,7 @@ class Translator():
1122
1127
  new = mm.Literal(arg.type, arg.value + 1) # type: ignore
1123
1128
  tasks.append(v0.Lookup(
1124
1129
  engine=None,
1125
- relation=v0_builtins.eq, # type: ignore
1130
+ relation=v0_builtins.eq,
1126
1131
  args=(
1127
1132
  self.translate_value(tmps[index], lookup, ctx),
1128
1133
  self.translate_value(new, lookup, ctx),
@@ -1131,9 +1136,9 @@ class Translator():
1131
1136
  elif l.relation.fields[index].input:
1132
1137
  tasks.append(v0.Lookup(
1133
1138
  engine=None,
1134
- relation=v0_builtins.plus, # type: ignore
1139
+ relation=v0_builtins.plus,
1135
1140
  args=(
1136
- self.translate_value(args[index], lookup, ctx),
1141
+ self.translate_value(arg, lookup, ctx),
1137
1142
  v0.Literal(v0_types.Int128, 1),
1138
1143
  self.translate_value(tmps[index], lookup, ctx),
1139
1144
  ),
@@ -1146,7 +1151,7 @@ class Translator():
1146
1151
  args=(
1147
1152
  self.translate_value(tmps[index], lookup, ctx),
1148
1153
  v0.Literal(v0_types.Int128, 1),
1149
- self.translate_value(args[index], lookup, ctx),
1154
+ self.translate_value(arg, lookup, ctx),
1150
1155
  ),
1151
1156
  )
1152
1157
  )
@@ -1217,7 +1222,10 @@ class Translator():
1217
1222
  if target_type is None or not isinstance(arg, (v0.Var, v0.Literal)) or arg.type == target_type:
1218
1223
  args.append(arg)
1219
1224
  continue
1220
- if field.input:
1225
+ if isinstance(arg, v0.Literal):
1226
+ # for literals, we can just create a new literal with the target type
1227
+ args.append(v0.Literal(type=target_type, value=arg.value))
1228
+ elif field.input:
1221
1229
  cast_var = v0.Var(type=target_type, name="cast_var")
1222
1230
  args.append(cast_var)
1223
1231
  inputs.append(v0.Lookup(
@@ -5,6 +5,7 @@ For more documentation on these commands, visit: https://docs.relationalai.com
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import sys
8
9
  from pathlib import Path
9
10
 
10
11
  import click
@@ -86,5 +87,142 @@ def test():
86
87
  console.print("test")
87
88
 
88
89
 
90
+ @cli.group()
91
+ def docs():
92
+ """Generate and serve documentation site."""
93
+ pass
94
+
95
+
96
+ @docs.command()
97
+ @click.option(
98
+ "--output",
99
+ "-o",
100
+ default=None,
101
+ help="Output directory for generated documentation (defaults to docs/dist)",
102
+ )
103
+ def generate(output: str | None):
104
+ """Generate static documentation site from models."""
105
+ from .docs import generate_docs
106
+
107
+ try:
108
+ generate_docs(output_dir=output)
109
+ except click.ClickException as e:
110
+ # Print formatted error message and exit
111
+ console.print(f"[red]✗ Failed to generate documentation: {e.message}[/red]")
112
+ sys.exit(1)
113
+ except Exception as e:
114
+ console.print(f"[red]✗ Failed to generate documentation: {e}[/red]")
115
+ sys.exit(1)
116
+
117
+
118
+ @docs.command()
119
+ @click.option(
120
+ "--port",
121
+ "-p",
122
+ default=8000,
123
+ type=int,
124
+ help="Port to serve documentation on (default: 8000)",
125
+ )
126
+ @click.option(
127
+ "--host",
128
+ default="127.0.0.1",
129
+ help="Host to bind to (default: 127.0.0.1)",
130
+ )
131
+ @click.option(
132
+ "--build-dir",
133
+ default=None,
134
+ help="Directory containing built documentation (defaults to docs/dist)",
135
+ )
136
+ @click.option(
137
+ "--no-open",
138
+ is_flag=True,
139
+ help="Don't open browser automatically",
140
+ )
141
+ def serve(port: int, host: str, build_dir: str | None, no_open: bool):
142
+ """Serve generated documentation site."""
143
+ from .docs import find_docs_dir, serve_docs
144
+
145
+ # Determine build directory
146
+ if build_dir is None:
147
+ docs_dir = find_docs_dir()
148
+ build_dir = str(docs_dir / "dist")
149
+
150
+ try:
151
+ serve_docs(
152
+ build_dir=build_dir,
153
+ host=host,
154
+ port=port,
155
+ open_browser=not no_open,
156
+ )
157
+ except click.ClickException as e:
158
+ # Print formatted error message and exit
159
+ console.print(f"[red]✗ Failed to start server: {e.message}[/red]")
160
+ sys.exit(1)
161
+ except Exception as e:
162
+ console.print(f"[red]✗ Failed to start server: {e}[/red]")
163
+ sys.exit(1)
164
+
165
+
166
+ @docs.command()
167
+ @click.option(
168
+ "--port",
169
+ "-p",
170
+ default=5173,
171
+ type=int,
172
+ help="Port to serve development server on (default: 5173)",
173
+ )
174
+ @click.option(
175
+ "--host",
176
+ default="127.0.0.1",
177
+ help="Host to bind to (default: 127.0.0.1)",
178
+ )
179
+ @click.option(
180
+ "--no-open",
181
+ is_flag=True,
182
+ help="Don't open browser automatically",
183
+ )
184
+ def dev(port: int, host: str, no_open: bool):
185
+ """Start development server with hot module replacement (HMR).
186
+
187
+ This command runs Vite's dev server which provides:
188
+ - Hot module replacement - changes reflect instantly
189
+ - Fast refresh - no need to rebuild on every change
190
+ - Source maps for debugging
191
+
192
+ Use this for development instead of 'rai docs generate' + 'rai docs serve'.
193
+ """
194
+ from .docs import dev_docs
195
+
196
+ try:
197
+ dev_docs(
198
+ host=host,
199
+ port=port,
200
+ open_browser=not no_open,
201
+ )
202
+ except click.ClickException as e:
203
+ # Print formatted error message and exit
204
+ console.print(f"[red]✗ Failed to start dev server: {e.message}[/red]")
205
+ sys.exit(1)
206
+ except Exception as e:
207
+ console.print(f"[red]✗ Failed to start dev server: {e}[/red]")
208
+ sys.exit(1)
209
+
210
+
211
+ @docs.command(name="clean")
212
+ def clean_docs():
213
+ """Remove auto generated files and directories."""
214
+ from .docs import cleanup_docs
215
+
216
+ try:
217
+ cleanup_docs()
218
+ except click.ClickException as e:
219
+ # Print formatted error message and exit
220
+ console.print(f"[red]✗ Failed to cleanup: {e.message}[/red]")
221
+ sys.exit(1)
222
+ except Exception as e:
223
+ console.print(f"[red]✗ Failed to cleanup: {e}[/red]")
224
+ sys.exit(1)
225
+
226
+
89
227
  if __name__ == "__main__":
90
228
  cli()