pbi-enterprise-cli 0.1.0.dev0__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.
- pbi_cli/__init__.py +3 -0
- pbi_cli/_audit.py +57 -0
- pbi_cli/_snapshot.py +95 -0
- pbi_cli/backends/__init__.py +1 -0
- pbi_cli/backends/mock_backend.py +323 -0
- pbi_cli/backends/pbir_backend.py +813 -0
- pbi_cli/backends/protocol.py +52 -0
- pbi_cli/backends/tom_backend.py +650 -0
- pbi_cli/backends/xmla_backend.py +627 -0
- pbi_cli/cli.py +332 -0
- pbi_cli/commands/__init__.py +1 -0
- pbi_cli/commands/_doctor.py +84 -0
- pbi_cli/commands/_shared.py +88 -0
- pbi_cli/commands/calendar_cmd.py +186 -0
- pbi_cli/commands/connections.py +153 -0
- pbi_cli/commands/custom_visual.py +325 -0
- pbi_cli/commands/database.py +76 -0
- pbi_cli/commands/dax.py +174 -0
- pbi_cli/commands/deploy.py +193 -0
- pbi_cli/commands/docs.py +57 -0
- pbi_cli/commands/filter_cmd.py +235 -0
- pbi_cli/commands/govern.py +124 -0
- pbi_cli/commands/layout.py +104 -0
- pbi_cli/commands/measure.py +185 -0
- pbi_cli/commands/model.py +499 -0
- pbi_cli/commands/partition.py +89 -0
- pbi_cli/commands/repl.py +209 -0
- pbi_cli/commands/report.py +561 -0
- pbi_cli/commands/security.py +90 -0
- pbi_cli/commands/server_cmd.py +30 -0
- pbi_cli/commands/skills_cmd.py +168 -0
- pbi_cli/commands/source.py +581 -0
- pbi_cli/commands/theme.py +60 -0
- pbi_cli/commands/trace.py +142 -0
- pbi_cli/commands/visual.py +507 -0
- pbi_cli/commands/watch.py +145 -0
- pbi_cli/docs_gen/__init__.py +1 -0
- pbi_cli/docs_gen/confluence.py +24 -0
- pbi_cli/docs_gen/markdown.py +36 -0
- pbi_cli/governance/__init__.py +1 -0
- pbi_cli/governance/engine.py +70 -0
- pbi_cli/governance/rules/__init__.py +85 -0
- pbi_cli/governance/rules/measure_brackets.py +27 -0
- pbi_cli/governance/rules/measure_description.py +41 -0
- pbi_cli/governance/rules/measure_format.py +38 -0
- pbi_cli/governance/rules/measure_naming.py +93 -0
- pbi_cli/governance/rules/table_pascal_case.py +44 -0
- pbi_cli/intelligence/__init__.py +1 -0
- pbi_cli/intelligence/layout_engine.py +192 -0
- pbi_cli/intelligence/measure_generator.py +40 -0
- pbi_cli/intelligence/theme_generator.py +193 -0
- pbi_cli/intelligence/visual_builder.py +429 -0
- pbi_cli/intelligence/visual_recommender.py +42 -0
- pbi_cli/server/__init__.py +1 -0
- pbi_cli/server/api.py +185 -0
- pbi_enterprise_cli-0.1.0.dev0.dist-info/METADATA +103 -0
- pbi_enterprise_cli-0.1.0.dev0.dist-info/RECORD +61 -0
- pbi_enterprise_cli-0.1.0.dev0.dist-info/WHEEL +5 -0
- pbi_enterprise_cli-0.1.0.dev0.dist-info/entry_points.txt +2 -0
- pbi_enterprise_cli-0.1.0.dev0.dist-info/licenses/LICENSE +21 -0
- pbi_enterprise_cli-0.1.0.dev0.dist-info/top_level.txt +1 -0
pbi_cli/commands/repl.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""pbi repl — interactive REPL with tab completion, history, and persistent connection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import readline
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
_HISTORY_FILE = Path.home() / ".pbi-cli" / "repl_history"
|
|
15
|
+
_COMMANDS = [
|
|
16
|
+
"model info",
|
|
17
|
+
"model tables",
|
|
18
|
+
"model columns",
|
|
19
|
+
"model relationships",
|
|
20
|
+
"model lint",
|
|
21
|
+
"model stats",
|
|
22
|
+
"measure list",
|
|
23
|
+
"measure add",
|
|
24
|
+
"measure update",
|
|
25
|
+
"measure delete",
|
|
26
|
+
"measure generate",
|
|
27
|
+
"dax query",
|
|
28
|
+
"dax validate",
|
|
29
|
+
"dax test",
|
|
30
|
+
"source profile",
|
|
31
|
+
"source scaffold",
|
|
32
|
+
"report pages",
|
|
33
|
+
"report page-add",
|
|
34
|
+
"report page-delete",
|
|
35
|
+
"report bookmark-list",
|
|
36
|
+
"report bookmark-add",
|
|
37
|
+
"report bookmark-delete",
|
|
38
|
+
"visual list",
|
|
39
|
+
"visual add",
|
|
40
|
+
"govern check",
|
|
41
|
+
"govern fix",
|
|
42
|
+
"govern rules",
|
|
43
|
+
"security roles",
|
|
44
|
+
"security role-add",
|
|
45
|
+
"partition list",
|
|
46
|
+
"partition add",
|
|
47
|
+
"partition refresh",
|
|
48
|
+
"trace start",
|
|
49
|
+
"trace stop",
|
|
50
|
+
"trace fetch",
|
|
51
|
+
"connections list",
|
|
52
|
+
"connections last",
|
|
53
|
+
"deploy snapshot",
|
|
54
|
+
"deploy diff",
|
|
55
|
+
"database export-tmdl",
|
|
56
|
+
"database import-tmdl",
|
|
57
|
+
"database diff-tmdl",
|
|
58
|
+
"doctor",
|
|
59
|
+
"help",
|
|
60
|
+
"exit",
|
|
61
|
+
"quit",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _PbiCompleter:
|
|
66
|
+
"""Tab-completion provider for the pbi REPL."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, commands: list[str]) -> None:
|
|
69
|
+
self.commands = commands
|
|
70
|
+
self.matches: list[str] = []
|
|
71
|
+
|
|
72
|
+
def complete(self, text: str, state: int) -> str | None:
|
|
73
|
+
if state == 0:
|
|
74
|
+
if text:
|
|
75
|
+
self.matches = [c for c in self.commands if c.startswith(text)]
|
|
76
|
+
else:
|
|
77
|
+
self.matches = list(self.commands)
|
|
78
|
+
try:
|
|
79
|
+
return self.matches[state]
|
|
80
|
+
except IndexError:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _load_history() -> None:
|
|
85
|
+
_HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
if _HISTORY_FILE.exists():
|
|
87
|
+
try:
|
|
88
|
+
readline.read_history_file(str(_HISTORY_FILE)) # type: ignore[attr-defined]
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
readline.set_history_length(1000) # type: ignore[attr-defined]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _save_history() -> None:
|
|
95
|
+
try:
|
|
96
|
+
readline.write_history_file(str(_HISTORY_FILE)) # type: ignore[attr-defined]
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@click.command("repl")
|
|
102
|
+
@click.option(
|
|
103
|
+
"--backend",
|
|
104
|
+
default="desktop",
|
|
105
|
+
type=click.Choice(["desktop", "xmla", "mock"]),
|
|
106
|
+
help="Backend to connect to.",
|
|
107
|
+
)
|
|
108
|
+
@click.option("--port", default=None, type=int, help="Desktop server port.")
|
|
109
|
+
@click.option("--no-history", is_flag=True, help="Disable command history persistence.")
|
|
110
|
+
@click.pass_context
|
|
111
|
+
def repl(ctx: click.Context, backend: str, port: int | None, no_history: bool) -> None:
|
|
112
|
+
"""Start an interactive pbi REPL with tab completion and command history.
|
|
113
|
+
|
|
114
|
+
Maintains a persistent connection for the session so you don't have to
|
|
115
|
+
reconnect on every command.
|
|
116
|
+
|
|
117
|
+
\b
|
|
118
|
+
Controls:
|
|
119
|
+
Tab — command completion
|
|
120
|
+
Up/Down — command history
|
|
121
|
+
Ctrl+D/exit — quit
|
|
122
|
+
help — list all commands
|
|
123
|
+
|
|
124
|
+
\b
|
|
125
|
+
Example session:
|
|
126
|
+
$ pbi repl
|
|
127
|
+
pbi> model tables
|
|
128
|
+
pbi> measure list --table Sales
|
|
129
|
+
pbi> dax query "EVALUATE {[Total Revenue]}"
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
# Set up readline completion
|
|
133
|
+
completer = _PbiCompleter(_COMMANDS)
|
|
134
|
+
readline.set_completer(completer.complete) # type: ignore[attr-defined]
|
|
135
|
+
readline.parse_and_bind("tab: complete") # type: ignore[attr-defined]
|
|
136
|
+
|
|
137
|
+
if not no_history:
|
|
138
|
+
_load_history()
|
|
139
|
+
|
|
140
|
+
# Set up a fake context for the session backend
|
|
141
|
+
ctx.ensure_object(dict)
|
|
142
|
+
ctx.obj["backend"] = backend
|
|
143
|
+
if port:
|
|
144
|
+
ctx.obj["port"] = port
|
|
145
|
+
|
|
146
|
+
_print_banner(backend)
|
|
147
|
+
|
|
148
|
+
# Try connecting eagerly for desktop backend
|
|
149
|
+
session_backend: Any = None
|
|
150
|
+
if backend == "mock":
|
|
151
|
+
from pbi_cli.backends.mock_backend import MockTomBackend
|
|
152
|
+
|
|
153
|
+
session_backend = MockTomBackend()
|
|
154
|
+
session_backend.connect()
|
|
155
|
+
model_name = session_backend.model_info().get("name", "MockModel")
|
|
156
|
+
console.print(f"[green]Connected[/green] ({backend}) — [bold]{model_name}[/bold]")
|
|
157
|
+
else:
|
|
158
|
+
console.print(f"[dim]Backend: {backend} — will connect on first command.[/dim]")
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
while True:
|
|
162
|
+
try:
|
|
163
|
+
# Use plain input() since rich can't handle readline input
|
|
164
|
+
line = input("pbi> ").strip()
|
|
165
|
+
except (EOFError, KeyboardInterrupt):
|
|
166
|
+
console.print("\n[dim]Bye![/dim]")
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
if not line:
|
|
170
|
+
continue
|
|
171
|
+
if line.lower() in ("exit", "quit", "q"):
|
|
172
|
+
console.print("[dim]Bye![/dim]")
|
|
173
|
+
break
|
|
174
|
+
if line.lower() == "help":
|
|
175
|
+
_print_help()
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
_dispatch(ctx, line, session_backend)
|
|
179
|
+
|
|
180
|
+
finally:
|
|
181
|
+
if not no_history:
|
|
182
|
+
_save_history()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _print_banner(backend: str) -> None:
|
|
186
|
+
console.print("\n[bold blue]pbi-cli interactive REPL[/bold blue]")
|
|
187
|
+
console.print(
|
|
188
|
+
f" Backend: [cyan]{backend}[/cyan] | Tab: complete | ↑↓: history | Ctrl+D: exit\n"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _print_help() -> None:
|
|
193
|
+
console.print("[bold]Available commands:[/bold]")
|
|
194
|
+
for cmd in _COMMANDS:
|
|
195
|
+
console.print(f" pbi {cmd}")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _dispatch(ctx: click.Context, line: str, session_backend: Any) -> None:
|
|
199
|
+
"""Parse and execute a REPL line by delegating to the main CLI."""
|
|
200
|
+
import subprocess
|
|
201
|
+
|
|
202
|
+
args = ["pbi"] + line.split()
|
|
203
|
+
# Pass current backend flag
|
|
204
|
+
backend = ctx.obj.get("backend", "desktop")
|
|
205
|
+
args = ["pbi", "--backend", backend] + line.split()
|
|
206
|
+
try:
|
|
207
|
+
subprocess.run(args, capture_output=False)
|
|
208
|
+
except Exception as exc:
|
|
209
|
+
console.print(f"[red]Error:[/red] {exc}")
|