relationalai 1.0.0a2__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.
- relationalai/config/shims.py +1 -0
- relationalai/semantics/__init__.py +7 -1
- relationalai/semantics/frontend/base.py +19 -13
- relationalai/semantics/frontend/core.py +30 -2
- relationalai/semantics/frontend/front_compiler.py +38 -11
- relationalai/semantics/frontend/pprint.py +1 -1
- relationalai/semantics/metamodel/rewriter.py +6 -2
- relationalai/semantics/metamodel/typer.py +70 -26
- relationalai/semantics/reasoners/__init__.py +11 -0
- relationalai/semantics/reasoners/graph/__init__.py +38 -0
- relationalai/semantics/reasoners/graph/core.py +9015 -0
- relationalai/shims/executor.py +4 -1
- relationalai/shims/hoister.py +9 -0
- relationalai/shims/mm2v0.py +47 -34
- relationalai/tools/cli/cli.py +138 -0
- relationalai/tools/cli/docs.py +394 -0
- {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/METADATA +5 -3
- {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/RECORD +57 -43
- v0/relationalai/__init__.py +69 -22
- v0/relationalai/clients/__init__.py +15 -2
- v0/relationalai/clients/client.py +4 -4
- v0/relationalai/clients/exec_txn_poller.py +91 -0
- v0/relationalai/clients/local.py +5 -5
- v0/relationalai/clients/resources/__init__.py +8 -0
- v0/relationalai/clients/{azure.py → resources/azure/azure.py} +12 -12
- v0/relationalai/clients/resources/snowflake/__init__.py +20 -0
- v0/relationalai/clients/resources/snowflake/cli_resources.py +87 -0
- v0/relationalai/clients/resources/snowflake/direct_access_resources.py +717 -0
- v0/relationalai/clients/resources/snowflake/engine_state_handlers.py +309 -0
- v0/relationalai/clients/resources/snowflake/error_handlers.py +199 -0
- v0/relationalai/clients/resources/snowflake/resources_factory.py +99 -0
- v0/relationalai/clients/{snowflake.py → resources/snowflake/snowflake.py} +642 -1399
- v0/relationalai/clients/{use_index_poller.py → resources/snowflake/use_index_poller.py} +51 -12
- v0/relationalai/clients/resources/snowflake/use_index_resources.py +188 -0
- v0/relationalai/clients/resources/snowflake/util.py +387 -0
- v0/relationalai/early_access/dsl/ir/executor.py +4 -4
- v0/relationalai/early_access/dsl/snow/api.py +2 -1
- v0/relationalai/errors.py +18 -0
- v0/relationalai/experimental/solvers.py +7 -7
- v0/relationalai/semantics/devtools/benchmark_lqp.py +4 -5
- v0/relationalai/semantics/devtools/extract_lqp.py +1 -1
- v0/relationalai/semantics/internal/snowflake.py +1 -1
- v0/relationalai/semantics/lqp/executor.py +7 -12
- v0/relationalai/semantics/lqp/rewrite/extract_keys.py +25 -3
- v0/relationalai/semantics/metamodel/util.py +6 -5
- v0/relationalai/semantics/reasoners/optimization/solvers_pb.py +335 -84
- v0/relationalai/semantics/rel/executor.py +14 -11
- v0/relationalai/semantics/sql/executor/snowflake.py +9 -5
- v0/relationalai/semantics/tests/test_snapshot_abstract.py +1 -1
- v0/relationalai/tools/cli.py +26 -30
- v0/relationalai/tools/cli_helpers.py +10 -2
- v0/relationalai/util/otel_configuration.py +2 -1
- v0/relationalai/util/otel_handler.py +1 -1
- {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/WHEEL +0 -0
- {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/entry_points.txt +0 -0
- {relationalai-1.0.0a2.dist-info → relationalai-1.0.0a4.dist-info}/top_level.txt +0 -0
- /v0/relationalai/clients/{cache_store.py → resources/snowflake/cache_store.py} +0 -0
relationalai/shims/executor.py
CHANGED
|
@@ -10,7 +10,10 @@ from v0.relationalai.semantics.rel.executor import RelExecutor
|
|
|
10
10
|
from v0.relationalai.semantics.metamodel import ir as v0, factory as v0_factory
|
|
11
11
|
from v0.relationalai.semantics.metamodel.visitor import collect_by_type
|
|
12
12
|
from v0.relationalai.semantics.snowflake import Table as v0Table
|
|
13
|
-
|
|
13
|
+
try:
|
|
14
|
+
from v0.relationalai.clients.snowflake import Provider as v0Provider #type: ignore
|
|
15
|
+
except ImportError:
|
|
16
|
+
from v0.relationalai.clients.resources.snowflake import Provider as v0Provider
|
|
14
17
|
from v0.relationalai.clients.config import Config
|
|
15
18
|
|
|
16
19
|
# from ..config import Config
|
relationalai/shims/hoister.py
CHANGED
|
@@ -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
|
relationalai/shims/mm2v0.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -456,6 +462,17 @@ class Translator():
|
|
|
456
462
|
def translate_table(self, t: mm.Table, parent, ctx):
|
|
457
463
|
return self.translate_scalartype(t, parent, ctx)
|
|
458
464
|
|
|
465
|
+
def rewrite_cdc_args(self, l: mm.Lookup, args, parent, ctx):
|
|
466
|
+
# If this is a lookup for an external table column,
|
|
467
|
+
# we have to prepend the column symbol to the args
|
|
468
|
+
root_type = l.relation.fields[0].type
|
|
469
|
+
if isinstance(root_type, mm.Table):
|
|
470
|
+
self.used_tables.add(root_type)
|
|
471
|
+
is_table = l.relation == root_type
|
|
472
|
+
if is_table or l.relation in root_type.columns:
|
|
473
|
+
sym = "METADATA$KEY" if is_table else l.relation.name
|
|
474
|
+
args = (v0.Literal(type=v0_types.Symbol, value=sym), *args)
|
|
475
|
+
return args
|
|
459
476
|
|
|
460
477
|
# -----------------------------------------------------------------------------
|
|
461
478
|
# Values
|
|
@@ -734,7 +751,6 @@ class Translator():
|
|
|
734
751
|
assert isinstance(r.domain, mm.Logical)
|
|
735
752
|
assert isinstance(r.check, mm.Logical)
|
|
736
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):
|
|
737
|
-
return
|
|
738
754
|
v0_reqs = []
|
|
739
755
|
# v0 expects a check with both the domain and the relation in it followed by the unique
|
|
740
756
|
# constraint, so we construct a logical with all of that in it
|
|
@@ -743,7 +759,7 @@ class Translator():
|
|
|
743
759
|
fields = c.args[0] # tuple of fields
|
|
744
760
|
assert isinstance(fields, tuple) and all(isinstance(f, mm.Field) for f in fields)
|
|
745
761
|
first_field = fields[0]
|
|
746
|
-
if isinstance(first_field.type, mm.Table):
|
|
762
|
+
if isinstance(first_field.type, mm.Table): #type: ignore
|
|
747
763
|
# skip union types for now
|
|
748
764
|
continue
|
|
749
765
|
assert isinstance(first_field, mm.Field)
|
|
@@ -768,17 +784,17 @@ class Translator():
|
|
|
768
784
|
))
|
|
769
785
|
return v0_reqs # type: ignore
|
|
770
786
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
+
)
|
|
782
798
|
return None
|
|
783
799
|
|
|
784
800
|
# -----------------------------
|
|
@@ -851,19 +867,11 @@ class Translator():
|
|
|
851
867
|
if relation is None:
|
|
852
868
|
return None
|
|
853
869
|
|
|
854
|
-
# External Table Column lookups
|
|
855
|
-
# we have to prepend the column symbol to the args
|
|
856
|
-
root_type = l.relation.fields[0].type
|
|
857
|
-
if isinstance(root_type, mm.Table):
|
|
858
|
-
self.used_tables.add(root_type)
|
|
859
|
-
is_table = l.relation == root_type
|
|
860
|
-
if is_table or l.relation in root_type.columns:
|
|
861
|
-
sym = "METADATA$KEY" if is_table else l.relation.name
|
|
862
|
-
args = (v0.Literal(type=v0_types.Symbol, value=sym), *args)
|
|
863
870
|
|
|
864
871
|
# Specific rewrites
|
|
865
872
|
rewrite = self.rewrite_lookup(l, parent, ctx)
|
|
866
873
|
if rewrite is None:
|
|
874
|
+
args = self.rewrite_cdc_args(l, args, parent, ctx)
|
|
867
875
|
# General translation
|
|
868
876
|
rewrite = v0.Lookup(
|
|
869
877
|
engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
|
|
@@ -1100,16 +1108,18 @@ class Translator():
|
|
|
1100
1108
|
else:
|
|
1101
1109
|
replaced_args.append(arg)
|
|
1102
1110
|
# translate the lookup
|
|
1111
|
+
args = tuple(self.translate_value(arg, l, ctx) for arg in replaced_args)
|
|
1112
|
+
args = self.rewrite_cdc_args(l, args, l, ctx)
|
|
1103
1113
|
lookup = v0.Lookup(
|
|
1104
1114
|
engine=self.translate_reasoner(l.reasoner, l, Context.MODEL),
|
|
1105
1115
|
relation=self.translate_node(l.relation), # type: ignore
|
|
1106
|
-
args=
|
|
1116
|
+
args=args,
|
|
1107
1117
|
annotations=self.translate_frozen(l.annotations, l, ctx) # type: ignore
|
|
1108
1118
|
)
|
|
1109
1119
|
# subtract 1 from the index to convert from 1-based to 0-based
|
|
1110
1120
|
tasks = []
|
|
1111
1121
|
for index in indices:
|
|
1112
|
-
arg = args[index]
|
|
1122
|
+
arg = l.args[index]
|
|
1113
1123
|
if isinstance(arg, mm.Literal):
|
|
1114
1124
|
if l.relation.fields[index].input:
|
|
1115
1125
|
new = mm.Literal(arg.type, arg.value + 1) # type: ignore
|
|
@@ -1117,7 +1127,7 @@ class Translator():
|
|
|
1117
1127
|
new = mm.Literal(arg.type, arg.value + 1) # type: ignore
|
|
1118
1128
|
tasks.append(v0.Lookup(
|
|
1119
1129
|
engine=None,
|
|
1120
|
-
relation=v0_builtins.eq,
|
|
1130
|
+
relation=v0_builtins.eq,
|
|
1121
1131
|
args=(
|
|
1122
1132
|
self.translate_value(tmps[index], lookup, ctx),
|
|
1123
1133
|
self.translate_value(new, lookup, ctx),
|
|
@@ -1126,9 +1136,9 @@ class Translator():
|
|
|
1126
1136
|
elif l.relation.fields[index].input:
|
|
1127
1137
|
tasks.append(v0.Lookup(
|
|
1128
1138
|
engine=None,
|
|
1129
|
-
relation=v0_builtins.plus,
|
|
1139
|
+
relation=v0_builtins.plus,
|
|
1130
1140
|
args=(
|
|
1131
|
-
self.translate_value(
|
|
1141
|
+
self.translate_value(arg, lookup, ctx),
|
|
1132
1142
|
v0.Literal(v0_types.Int128, 1),
|
|
1133
1143
|
self.translate_value(tmps[index], lookup, ctx),
|
|
1134
1144
|
),
|
|
@@ -1141,7 +1151,7 @@ class Translator():
|
|
|
1141
1151
|
args=(
|
|
1142
1152
|
self.translate_value(tmps[index], lookup, ctx),
|
|
1143
1153
|
v0.Literal(v0_types.Int128, 1),
|
|
1144
|
-
self.translate_value(
|
|
1154
|
+
self.translate_value(arg, lookup, ctx),
|
|
1145
1155
|
),
|
|
1146
1156
|
)
|
|
1147
1157
|
)
|
|
@@ -1212,7 +1222,10 @@ class Translator():
|
|
|
1212
1222
|
if target_type is None or not isinstance(arg, (v0.Var, v0.Literal)) or arg.type == target_type:
|
|
1213
1223
|
args.append(arg)
|
|
1214
1224
|
continue
|
|
1215
|
-
if
|
|
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:
|
|
1216
1229
|
cast_var = v0.Var(type=target_type, name="cast_var")
|
|
1217
1230
|
args.append(cast_var)
|
|
1218
1231
|
inputs.append(v0.Lookup(
|
relationalai/tools/cli/cli.py
CHANGED
|
@@ -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()
|