datbox-core 0.0.1__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.
@@ -0,0 +1,10 @@
1
+ """datbox-core: the `dat` CLI, plugin registry, and shared foundations."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("datbox-core")
7
+ except PackageNotFoundError: # pragma: no cover - source checkout without install
8
+ __version__ = "0.0.0"
9
+
10
+ __all__ = ["__version__"]
datbox_core/cli.py ADDED
@@ -0,0 +1,68 @@
1
+ """The `dat` root command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from datbox_core import __version__
8
+ from datbox_core.plugins import discover
9
+
10
+ app = typer.Typer(
11
+ name="dat",
12
+ help="dat: the Data Analyst Toolbox. Portable, zero-infrastructure utilities for analysts.",
13
+ no_args_is_help=True,
14
+ add_completion=False,
15
+ )
16
+
17
+
18
+ def _version_callback(value: bool) -> None:
19
+ if value:
20
+ typer.echo(f"dat {__version__}")
21
+ raise typer.Exit()
22
+
23
+
24
+ @app.callback()
25
+ def root(
26
+ version: bool = typer.Option(
27
+ False,
28
+ "--version",
29
+ "-V",
30
+ help="Show the dat version and exit.",
31
+ callback=_version_callback,
32
+ is_eager=True,
33
+ ),
34
+ ) -> None:
35
+ """Root callback; subcommands are supplied by installed datbox-* plugins."""
36
+
37
+
38
+ @app.command()
39
+ def plugins() -> None:
40
+ """List installed dat modules and report any that failed to load."""
41
+ loaded, failed = discover()
42
+ if not loaded and not failed:
43
+ typer.echo("No plugins installed. Try: pip install datbox-viz")
44
+ return
45
+ for plugin in loaded:
46
+ dist = f" ({plugin.distribution})" if plugin.distribution else ""
47
+ typer.echo(f" {plugin.name}{dist}")
48
+ for broken in failed:
49
+ typer.secho(f" {broken.name} FAILED: {broken.error}", fg=typer.colors.RED, err=True)
50
+ if failed:
51
+ raise typer.Exit(code=1)
52
+
53
+
54
+ def build_app() -> typer.Typer:
55
+ """Attach every discovered plugin to the root app and return it."""
56
+ loaded, _failed = discover()
57
+ for plugin in loaded:
58
+ app.add_typer(plugin.app, name=plugin.name)
59
+ return app
60
+
61
+
62
+ def main() -> None:
63
+ """Console-script entry point."""
64
+ build_app()()
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()
datbox_core/plugins.py ADDED
@@ -0,0 +1,67 @@
1
+ """Plugin discovery for `dat` subcommands.
2
+
3
+ Any installed distribution can register a Typer app under the ``datbox.plugins``
4
+ entry-point group. The entry-point *name* becomes the subcommand name and the
5
+ *value* must resolve to a ``typer.Typer`` instance.
6
+
7
+ Example ``pyproject.toml`` for a module::
8
+
9
+ [project.entry-points."datbox.plugins"]
10
+ viz = "datbox_viz.cli:app"
11
+
12
+ Modules never import each other; they only depend on ``datbox-core``.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from importlib.metadata import EntryPoint, entry_points
19
+
20
+ import typer
21
+
22
+ PLUGIN_GROUP = "datbox.plugins"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class LoadedPlugin:
27
+ """A successfully loaded plugin subcommand."""
28
+
29
+ name: str
30
+ app: typer.Typer
31
+ distribution: str | None
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class FailedPlugin:
36
+ """A plugin that was found but could not be loaded."""
37
+
38
+ name: str
39
+ error: str
40
+
41
+
42
+ def discover() -> tuple[list[LoadedPlugin], list[FailedPlugin]]:
43
+ """Find and load every registered plugin.
44
+
45
+ A plugin that fails to import never breaks the root CLI; it is reported
46
+ instead so the user can see what is broken.
47
+ """
48
+ loaded: list[LoadedPlugin] = []
49
+ failed: list[FailedPlugin] = []
50
+ for ep in _iter_entry_points():
51
+ try:
52
+ obj = ep.load()
53
+ except Exception as exc:
54
+ failed.append(FailedPlugin(name=ep.name, error=f"{type(exc).__name__}: {exc}"))
55
+ continue
56
+ if not isinstance(obj, typer.Typer):
57
+ failed.append(
58
+ FailedPlugin(name=ep.name, error=f"entry point is {type(obj).__name__}, not Typer")
59
+ )
60
+ continue
61
+ dist = ep.dist.name if ep.dist is not None else None
62
+ loaded.append(LoadedPlugin(name=ep.name, app=obj, distribution=dist))
63
+ return loaded, failed
64
+
65
+
66
+ def _iter_entry_points() -> list[EntryPoint]:
67
+ return sorted(entry_points(group=PLUGIN_GROUP), key=lambda ep: ep.name)
datbox_core/py.typed ADDED
File without changes
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.5
2
+ Name: datbox-core
3
+ Version: 0.0.1
4
+ Summary: Core of the Data Analyst Toolbox: the `dat` CLI, plugin registry, config, secrets, and output writers.
5
+ Project-URL: Homepage, https://github.com/nick-hebert/datbox
6
+ Project-URL: Repository, https://github.com/nick-hebert/datbox
7
+ Project-URL: Issues, https://github.com/nick-hebert/datbox/issues
8
+ Author: Nick Hebert
9
+ License-Expression: Apache-2.0
10
+ Keywords: analytics,cli,data,toolbox
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: pydantic>=2.7
21
+ Requires-Dist: typer>=0.12
22
+ Description-Content-Type: text/markdown
23
+
24
+ # datbox-core
25
+
26
+ The core of **dat**, the Data Analyst Toolbox. Provides the `dat` command-line entry point
27
+ and the plugin registry that other `datbox-*` packages register their subcommands with.
28
+
29
+ ```bash
30
+ pip install datbox-core
31
+ dat --help
32
+ ```
33
+
34
+ Other modules (`datbox-viz`, `datbox-fake`, ...) add subcommands automatically when installed.
@@ -0,0 +1,8 @@
1
+ datbox_core/__init__.py,sha256=nG02Wi-ntEAfZmZuIHZjVHHJ2WbhNKdLj_sp-wlteSI,320
2
+ datbox_core/cli.py,sha256=Gdvn66Dn8mElHpjp2HggU4KCFA6brRIqjvY-z2LxVMI,1720
3
+ datbox_core/plugins.py,sha256=8isbGhFpT7-C6vuij7YWdeT1PZ3lcNb60AITG2XaYXU,1957
4
+ datbox_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ datbox_core-0.0.1.dist-info/METADATA,sha256=ZuCbQt2WOXHDCzpQ1GwwpZwC1EU7kcVh-iu88uwpb6c,1300
6
+ datbox_core-0.0.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
7
+ datbox_core-0.0.1.dist-info/entry_points.txt,sha256=1qm3VCyUmy_5rJ4nYCOqk18rzU3jzowcWYiDO-3prQM,45
8
+ datbox_core-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dat = datbox_core.cli:main