tabsdata-cli 2.0.0__cp312-abi3-win_amd64.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 (39) hide show
  1. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/__init__.py +5 -0
  2. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/_registry.py +78 -0
  3. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/BANNER +5 -0
  4. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/LICENSE +3 -0
  5. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/README-PyPi.md +8 -0
  6. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/RELEASE-NOTES +1 -0
  7. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/THIRD-PARTY +3 -0
  8. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/assets/manifest/VERSION +1 -0
  9. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/cli_utils.py +993 -0
  10. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/shell.py +390 -0
  11. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/__init__.py +3 -0
  12. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/auth_group.py +197 -0
  13. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/cli.py +508 -0
  14. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/collection_group.py +458 -0
  15. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/config_utils.py +93 -0
  16. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/connection_group.py +159 -0
  17. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/examples_group.py +206 -0
  18. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/execution_utils.py +429 -0
  19. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fn_group.py +1109 -0
  20. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/fnrun_group.py +285 -0
  21. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/group_group.py +184 -0
  22. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/metrics_group.py +323 -0
  23. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/permission_group.py +259 -0
  24. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/plan_group.py +324 -0
  25. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/project_group.py +170 -0
  26. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/role_group.py +630 -0
  27. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/settings_group.py +344 -0
  28. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/table_group.py +600 -0
  29. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trigger_group.py +338 -0
  30. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/trx_group.py +329 -0
  31. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/user_group.py +200 -0
  32. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/td/worker_group.py +185 -0
  33. tabsdata_cli-2.0.0.data/purelib/tabsdatak/cli/template.py +781 -0
  34. tabsdata_cli-2.0.0.data/purelib/tabsdatak/py.typed +0 -0
  35. tabsdata_cli-2.0.0.dist-info/METADATA +39 -0
  36. tabsdata_cli-2.0.0.dist-info/RECORD +39 -0
  37. tabsdata_cli-2.0.0.dist-info/WHEEL +5 -0
  38. tabsdata_cli-2.0.0.dist-info/licenses/src/main/python/tabsdatak/cli/assets/manifest/LICENSE +3 -0
  39. tabsdata_cli-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,159 @@
1
+ #
2
+ # Copyright 2026 Tabsdata Inc.
3
+ #
4
+
5
+ """``tdk connection`` -- discover connectors and build connection documents.
6
+
7
+ These commands are offline: they introspect the connectors registered through
8
+ the SPI registry (``avail_srcs`` / ``avail_stream_srcs`` / ``avail_dests``) and
9
+ emit the connectors' own envelope dialect (``{kind, apiVersion, type, spec}``).
10
+ They do not talk to a server.
11
+ """
12
+
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import rich_click as click
17
+ from rich.table import Table
18
+
19
+ from tabsdatak.cli._registry import cli_command
20
+ from tabsdatak.cli.cli_utils import format_option, logical_prompt, make_console, render
21
+
22
+
23
+ @cli_command
24
+ @click.group()
25
+ def connection():
26
+ """Connection template commands"""
27
+
28
+
29
+ @connection.command()
30
+ @format_option
31
+ @click.pass_context
32
+ def types(ctx):
33
+ """List the connection types available from installed connectors"""
34
+ # noinspection PyProtectedMember
35
+ from tabsdatak.cli import template
36
+
37
+ rows = template.list_connectors()
38
+
39
+ def _table():
40
+ table = Table(title="Available connectors")
41
+ table.add_column("Type", style="cyan", no_wrap=True)
42
+ table.add_column("Mode")
43
+ table.add_column("System")
44
+ for row in rows:
45
+ table.add_row(row.type, row.mode, row.system)
46
+ click.echo()
47
+ make_console().print(table)
48
+ click.echo(f"Number of connectors: {len(rows)}")
49
+ click.echo()
50
+
51
+ render(
52
+ ctx,
53
+ data=[{"type": r.type, "mode": r.mode, "system": r.system} for r in rows],
54
+ table=_table,
55
+ )
56
+
57
+
58
+ def _type_option(fn):
59
+ return click.option(
60
+ "--type",
61
+ "-t",
62
+ "type_",
63
+ help="Connector type, e.g. 'mysql-sql-in', 's3-file-out'. "
64
+ "See 'tdk connection types'.",
65
+ )(fn)
66
+
67
+
68
+ def _resolve(ctx, type_):
69
+ # noinspection PyProtectedMember
70
+ from tabsdatak.cli import template
71
+
72
+ type_ = type_ or logical_prompt(ctx, "Connector type (see 'tdk connection types')")
73
+ try:
74
+ # Types are globally unique, so the direction is always inferred.
75
+ return template.resolve_conn(type_, None)
76
+ except template.TemplateError as e:
77
+ raise click.ClickException(str(e))
78
+
79
+
80
+ def _emit(document, file):
81
+ # noinspection PyProtectedMember
82
+ from tabsdatak.cli import template
83
+
84
+ text = template.to_yaml(document)
85
+ if file and file != "-":
86
+ Path(file).write_text(text)
87
+ click.echo(f"Wrote connection document to {file}")
88
+ else:
89
+ click.echo(text, nl=False)
90
+
91
+
92
+ # Named "template" explicitly: the function can't be called ``template`` without
93
+ # shadowing the imported engine module.
94
+ @connection.command("template")
95
+ @_type_option
96
+ # The template is always a commented YAML document (the "# accepts:" hints are
97
+ # the point, and JSON can't carry them), so there is no output *format* to pick
98
+ # here -- ``-o/--format`` is reserved CLI-wide for that. Use ``--file``/``-f``
99
+ # for the output path, matching validate/table/exe.
100
+ @click.option(
101
+ "--file",
102
+ "-f",
103
+ help="Write the template to this file. '-' (or omitting it) writes to stdout.",
104
+ )
105
+ @click.pass_context
106
+ def template_(ctx, type_, file):
107
+ """Print a fill-in connection template for a connector type"""
108
+ # noinspection PyProtectedMember
109
+ from tabsdatak.cli import template
110
+
111
+ info = _resolve(ctx, type_)
112
+ # Show every config: required fields as fill-ins, optional ones with their
113
+ # default (default-None fields commented out so the document stays valid).
114
+ _emit(template.build_template(info.conn_cls, all_fields=True), file)
115
+
116
+
117
+ @connection.command()
118
+ @click.option(
119
+ "--file",
120
+ "-f",
121
+ "file",
122
+ required=True,
123
+ help="Connection YAML file to validate, or '-' to read from stdin.",
124
+ )
125
+ def validate(file):
126
+ """Validate a connection YAML document against its connector schema"""
127
+ # noinspection PyProtectedMember
128
+ from tabsdatak.cli import template
129
+ from tabsdatak.common._error import CommonException
130
+ from tabsdatak.common._json_serde import (
131
+ env_secret_ref_vars,
132
+ resolve_env_secret_refs,
133
+ )
134
+
135
+ raw = sys.stdin.read() if file == "-" else Path(file).read_text()
136
+ try:
137
+ document = template.from_yaml(raw)
138
+ except Exception as e: # noqa: BLE001 -- any YAML parse failure
139
+ raise click.ClickException(f"not valid YAML: {e}")
140
+ # Resolve any `$secret:<ENV_VAR>` references from the environment first,
141
+ # exactly as connection create/upsert does, so a document that sources
142
+ # secret values from the environment validates the same way it is created
143
+ # (an unset referenced var is reported, not silently rejected as a bad
144
+ # secret value).
145
+ env_vars = env_secret_ref_vars(document)
146
+ try:
147
+ document = resolve_env_secret_refs(document)
148
+ except CommonException as e:
149
+ raise click.ClickException(str(e))
150
+ if env_vars:
151
+ click.echo(
152
+ "Connection secrets resolved from environment variable(s): "
153
+ + ", ".join(env_vars)
154
+ )
155
+ try:
156
+ template.validate(document)
157
+ except template.TemplateError as e:
158
+ raise click.ClickException(str(e))
159
+ click.echo("Connection document is valid")
@@ -0,0 +1,206 @@
1
+ #
2
+ # Copyright 2026 Tabsdata Inc.
3
+ #
4
+
5
+ """``tdk examples`` -- discover and extract bundled example use cases.
6
+
7
+ Each example is a folder bundled under ``resources/examples`` (from the
8
+ ``variant/resources`` tree) with the layout
9
+ ``<name>/{input-data,connections,functions,_setup.py}``, resolved through
10
+ :func:`td_resource`. ``extract`` copies the folder into a target directory
11
+ (flattening every subfolder except the ``*-data`` ones), then runs the
12
+ example's ``_setup.py`` standing in that directory to finalize it.
13
+ """
14
+
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ import rich_click as click
21
+ from rich.table import Table
22
+
23
+ from tabsdatak.cli._registry import cli_command
24
+ from tabsdatak.cli.cli_utils import format_option, make_console, render, show_hint
25
+
26
+ EXAMPLES_RESOURCE = "tdresources/examples"
27
+ META_FILE = "_meta.yaml"
28
+ SETUP_FILE = "_setup.py"
29
+ ERROR_FILE = "error.txt"
30
+ DATA_DIR_SUFFIX = "-data"
31
+
32
+
33
+ @cli_command
34
+ @click.group()
35
+ def examples():
36
+ """Example use case commands"""
37
+
38
+
39
+ def _examples_root() -> Path:
40
+ # noinspection PyProtectedMember
41
+ from tabsdatak.common._resources.reader import td_resource
42
+
43
+ try:
44
+ return td_resource(EXAMPLES_RESOURCE)
45
+ except FileNotFoundError as error:
46
+ raise click.ClickException(
47
+ "Failed to locate examples: the examples resources are not "
48
+ "available. Reinstalling the tabsdata package is strongly "
49
+ f"recommended. ({error})"
50
+ )
51
+
52
+
53
+ def _example_names(root: Path) -> list[str]:
54
+ return sorted(
55
+ entry.name
56
+ for entry in root.iterdir()
57
+ if entry.is_dir() and not entry.name.startswith((".", "_"))
58
+ )
59
+
60
+
61
+ def _read_meta(example_dir: Path) -> dict:
62
+ meta_path = example_dir / META_FILE
63
+ if not meta_path.exists():
64
+ return {}
65
+ from ruamel.yaml import YAML
66
+
67
+ try:
68
+ data = YAML(typ="safe").load(meta_path.read_text())
69
+ except Exception:
70
+ return {}
71
+ return data if isinstance(data, dict) else {}
72
+
73
+
74
+ @examples.command("list")
75
+ @format_option
76
+ @click.pass_context
77
+ def list_(ctx: click.Context):
78
+ """List the available examples"""
79
+ root = _examples_root()
80
+ rows = [
81
+ {
82
+ "name": name,
83
+ "description": str(_read_meta(root / name).get("description", "")).strip(),
84
+ }
85
+ for name in _example_names(root)
86
+ ]
87
+
88
+ def _table():
89
+ table = Table(title="Available examples")
90
+ table.add_column("Name", style="cyan", no_wrap=True)
91
+ table.add_column("Description")
92
+ for row in rows:
93
+ table.add_row(row["name"], row["description"])
94
+ click.echo()
95
+ make_console().print(table)
96
+ click.echo(f"Number of examples: {len(rows)}")
97
+ click.echo()
98
+
99
+ render(ctx, data=rows, table=_table)
100
+
101
+
102
+ def _skip(entry: Path) -> bool:
103
+ name = entry.name
104
+ return (
105
+ name == META_FILE
106
+ or name == "__pycache__"
107
+ or name.startswith(".")
108
+ or name.endswith((".pyc", ".pyo"))
109
+ )
110
+
111
+
112
+ def _copy_flattened(src: Path, target: Path) -> None:
113
+ """Copy ``src`` into ``target``, flattening every subfolder into the target
114
+ root except ``*-data`` folders, which are copied verbatim. ``_meta.yaml`` and
115
+ build artifacts are left behind."""
116
+ for entry in sorted(src.iterdir()):
117
+ if _skip(entry):
118
+ continue
119
+ if entry.is_dir():
120
+ if entry.name.endswith(DATA_DIR_SUFFIX):
121
+ shutil.copytree(
122
+ entry,
123
+ target / entry.name,
124
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"),
125
+ )
126
+ else:
127
+ for member in sorted(entry.iterdir()):
128
+ if member.is_file() and not _skip(member):
129
+ shutil.copy2(member, target / member.name)
130
+ else:
131
+ shutil.copy2(entry, target / entry.name)
132
+
133
+
134
+ def _run_setup(target: Path) -> None:
135
+ """Run the example's ``_setup.py`` in ``target``. On success the script is
136
+ removed; on failure it is kept and the full output is dumped to
137
+ ``error.txt`` in ``target``."""
138
+ setup_path = target / SETUP_FILE
139
+ if not setup_path.exists():
140
+ return
141
+ completed = subprocess.run(
142
+ [sys.executable, SETUP_FILE],
143
+ cwd=target,
144
+ capture_output=True,
145
+ text=True,
146
+ )
147
+ if completed.returncode == 0:
148
+ setup_path.unlink()
149
+ return
150
+ error_path = target / ERROR_FILE
151
+ error_path.write_text(
152
+ f"Example setup failed with exit code {completed.returncode}.\n"
153
+ f"command: {sys.executable} {SETUP_FILE}\n"
154
+ f"directory: {target}\n\n"
155
+ f"--- stdout ---\n{completed.stdout}\n"
156
+ f"--- stderr ---\n{completed.stderr}\n"
157
+ )
158
+ raise click.ClickException(
159
+ f"Failed to run example setup (exit code {completed.returncode}). The "
160
+ f"setup script was left in place and the full output was written to "
161
+ f"'{error_path}'."
162
+ )
163
+
164
+
165
+ @examples.command()
166
+ @click.option(
167
+ "--name",
168
+ "-n",
169
+ required=True,
170
+ help="Name of the example to extract. See 'tdk examples list'.",
171
+ )
172
+ @click.option(
173
+ "--dir",
174
+ "-d",
175
+ "dir_",
176
+ required=True,
177
+ help=(
178
+ "Directory to generate the example in. The directory must not exist "
179
+ "beforehand, as it will be created by the CLI."
180
+ ),
181
+ )
182
+ @click.pass_context
183
+ def extract(ctx: click.Context, name: str, dir_: str):
184
+ """Extract an example into a new directory"""
185
+ root = _examples_root()
186
+ available = _example_names(root)
187
+ if name not in available:
188
+ raise click.ClickException(
189
+ f"Failed to extract example: unknown example '{name}'. Available "
190
+ f"examples: {', '.join(available) if available else '(none)'}."
191
+ )
192
+
193
+ target = Path(dir_)
194
+ if target.exists():
195
+ raise click.ClickException(f"Failed to extract example: {dir_} already exists.")
196
+
197
+ target.mkdir(parents=True)
198
+ _copy_flattened(root / name, target)
199
+ _run_setup(target)
200
+
201
+ click.echo(f"Example '{name}' generated in '{dir_}'")
202
+ show_hint(
203
+ ctx,
204
+ "Remember that in order to run the example, tdserver must be running in "
205
+ "the same host as the CLI.",
206
+ )