hexastack 0.0.0__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.
hexastack/__init__.py ADDED
@@ -0,0 +1,64 @@
1
+ import importlib
2
+ import importlib.util
3
+ import sys
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ import hexastack_ai as ai
8
+ import hexastack_auth as auth
9
+ import hexastack_cli as cli
10
+ import hexastack_core as core
11
+ import hexastack_cqrs as cqrs
12
+ import hexastack_db as db
13
+ import hexastack_events as events
14
+ import hexastack_fastapi as fastapi
15
+ import hexastack_graphql as graphql
16
+ import hexastack_grpc as grpc
17
+ import hexastack_logging as logging
18
+ import hexastack_mcp as mcp
19
+ import hexastack_otel as otel
20
+
21
+ __all__ = [
22
+ "ai",
23
+ "auth",
24
+ "cli",
25
+ "core",
26
+ "cqrs",
27
+ "db",
28
+ "events",
29
+ "fastapi",
30
+ "graphql",
31
+ "grpc",
32
+ "logging",
33
+ "mcp",
34
+ "otel",
35
+ ]
36
+
37
+ _installed_shorthands: list[str] = []
38
+
39
+ # Dynamically discover and expose only currently installed packages
40
+ for _shorthand in __all__:
41
+ _module_name = f"hexastack_{_shorthand}"
42
+ if importlib.util.find_spec(_module_name) is not None:
43
+ try:
44
+ _mod = importlib.import_module(_module_name)
45
+ globals()[_shorthand] = _mod
46
+ sys.modules[f"hexastack.{_shorthand}"] = _mod
47
+ _installed_shorthands.append(_shorthand)
48
+ except (ImportError, AttributeError):
49
+ pass
50
+
51
+
52
+ def __dir__() -> list[str]:
53
+ """Return only installed package shorthands and module globals."""
54
+ return sorted(set(list(globals().keys()) + _installed_shorthands))
55
+
56
+
57
+ def __getattr__(name: str) -> Any:
58
+ """Provide clear guidance when an uninstalled optional package is accessed."""
59
+ if name in __all__:
60
+ raise AttributeError(
61
+ f"Package 'hexastack-{name}' is not installed. "
62
+ f"Install it via 'pip install hexastack[{name}]' or 'pip install hexastack-{name}'."
63
+ )
64
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
@@ -0,0 +1,7 @@
1
+ from hexastack.adapters.cli import add_serve_command
2
+ from hexastack.adapters.fastapi import create_demo_app
3
+
4
+ __all__ = [
5
+ "add_serve_command",
6
+ "create_demo_app",
7
+ ]
@@ -0,0 +1,28 @@
1
+ """CLI adapters for Hexastack umbrella package.
2
+
3
+ Sub-packages:
4
+ scaffolding: Project creation and initialization commands (`new`, `init`).
5
+ demo: Showcase, diagnostics, server, and UI commands (`demo`, `ui`, `serve`, `db`, `grpc`, `mcp`).
6
+ """
7
+
8
+ from hexastack.adapters.cli.demo.commands import (
9
+ DemoGroupDocs,
10
+ InspectGroupDocs,
11
+ add_db_commands,
12
+ add_grpc_commands,
13
+ add_mcp_commands,
14
+ add_serve_command,
15
+ add_ui_commands,
16
+ )
17
+ from hexastack.adapters.cli.scaffolding.commands import add_scaffold_commands
18
+
19
+ __all__ = [
20
+ "add_db_commands",
21
+ "add_grpc_commands",
22
+ "add_mcp_commands",
23
+ "add_scaffold_commands",
24
+ "add_serve_command",
25
+ "add_ui_commands",
26
+ "DemoGroupDocs",
27
+ "InspectGroupDocs",
28
+ ]
@@ -0,0 +1,21 @@
1
+ """Demo showcase and diagnostic CLI commands."""
2
+
3
+ from hexastack.adapters.cli.demo.commands import (
4
+ DemoGroupDocs,
5
+ InspectGroupDocs,
6
+ add_db_commands,
7
+ add_grpc_commands,
8
+ add_mcp_commands,
9
+ add_serve_command,
10
+ add_ui_commands,
11
+ )
12
+
13
+ __all__ = [
14
+ "add_db_commands",
15
+ "add_grpc_commands",
16
+ "add_mcp_commands",
17
+ "add_serve_command",
18
+ "add_ui_commands",
19
+ "DemoGroupDocs",
20
+ "InspectGroupDocs",
21
+ ]
@@ -0,0 +1,334 @@
1
+ """CLI command definitions for demo showcase and diagnostics.
2
+
3
+ Notes/Architectural Intent:
4
+ Provides subcommands for inspecting registries, running diagnostic queries,
5
+ and launching interactive developer servers.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ import os
12
+ from typing import Any
13
+
14
+ import typer
15
+
16
+ from hexastack.domain.diagnostics import (
17
+ GetSystemInfoQuery,
18
+ InspectRegistryQuery,
19
+ PingDemoCommand,
20
+ )
21
+ from hexastack_cli.infra.decorators import (
22
+ cli_command,
23
+ cli_group,
24
+ cli_query,
25
+ )
26
+ from hexastack_core.domain.exceptions import MissingDependencyError
27
+
28
+ __all__ = [
29
+ "add_db_commands",
30
+ "add_grpc_commands",
31
+ "add_mcp_commands",
32
+ "add_serve_command",
33
+ "add_ui_commands",
34
+ "DemoGroupDocs",
35
+ "InspectGroupDocs",
36
+ ]
37
+
38
+
39
+ @cli_group("demo", help="Interactive demonstration commands")
40
+ class DemoGroupDocs:
41
+ """CLI group documentation container for demo commands."""
42
+
43
+ pass
44
+
45
+
46
+ @cli_group("inspect", help="Introspect registered CQRS handlers, routes, and config")
47
+ class InspectGroupDocs:
48
+ """CLI group documentation container for inspect commands."""
49
+
50
+ pass
51
+
52
+
53
+ # Decorate diagnostics domain models with CLI exposure metadata
54
+ cli_query(
55
+ "info",
56
+ aliases=["doctor", "status"],
57
+ help="Display installed Hexastack packages and optional dependency statuses.",
58
+ )(GetSystemInfoQuery)
59
+
60
+ cli_query(
61
+ "registry",
62
+ group="inspect",
63
+ aliases=["handlers"],
64
+ help="Display registered CQRS commands, queries, and configurations.",
65
+ )(InspectRegistryQuery)
66
+
67
+ cli_command(
68
+ "ping",
69
+ group="demo",
70
+ aliases=["/ping"],
71
+ help="Send a test ping command through the CQRS execution pipeline.",
72
+ )(PingDemoCommand)
73
+
74
+
75
+ def add_db_commands(app: typer.Typer) -> None:
76
+ """Register 'db' subcommand group with migration management commands."""
77
+ if importlib.util.find_spec("hexastack_db") is None:
78
+ return
79
+
80
+ db_app = typer.Typer(
81
+ name="db",
82
+ help="Database migration management (requires hexastack[db,migrations]).",
83
+ no_args_is_help=True,
84
+ )
85
+ app.add_typer(db_app, name="db")
86
+
87
+ def _require_migrations() -> None:
88
+ if importlib.util.find_spec("alembic") is None:
89
+ raise MissingDependencyError(
90
+ "alembic is required for migration commands. "
91
+ "Install via 'pip install hexastack-db[migrations]'."
92
+ )
93
+
94
+ def _get_config(migrations_dir: str, url: str | None) -> Any:
95
+ from hexastack_db.infra.migrations import get_alembic_config
96
+
97
+ db_url = url or os.environ.get("DATABASE_URL", "sqlite:///hexastack.db")
98
+ return get_alembic_config(
99
+ migrations_dir=migrations_dir,
100
+ db_url=db_url,
101
+ )
102
+
103
+ @db_app.command(name="init", help="Scaffold a new migrations directory.")
104
+ def db_init(
105
+ directory: str = typer.Argument(
106
+ "migrations", help="Path to create the migrations directory."
107
+ ),
108
+ ) -> None:
109
+ _require_migrations()
110
+ from hexastack_db.infra.migrations import init_migrations
111
+
112
+ init_migrations(directory)
113
+
114
+ @db_app.command(
115
+ name="migrate", help="Apply pending database migrations (upgrade to head)."
116
+ )
117
+ def db_migrate(
118
+ directory: str = typer.Option(
119
+ "migrations", "--dir", help="Migrations directory."
120
+ ),
121
+ revision: str = typer.Option("head", "--revision", help="Target revision."),
122
+ url: str | None = typer.Option(
123
+ None, "--url", help="Database URL (overrides DATABASE_URL env var)."
124
+ ),
125
+ ) -> None:
126
+ _require_migrations()
127
+ from hexastack_db.infra.migrations import run_upgrade
128
+
129
+ run_upgrade(_get_config(directory, url), revision=revision)
130
+
131
+ @db_app.command(name="revision", help="Generate a new migration revision script.")
132
+ def db_revision(
133
+ message: str = typer.Argument(..., help="Short description of the migration."),
134
+ directory: str = typer.Option(
135
+ "migrations", "--dir", help="Migrations directory."
136
+ ),
137
+ no_autogenerate: bool = typer.Option(
138
+ False, "--no-autogenerate", help="Disable schema autogeneration."
139
+ ),
140
+ url: str | None = typer.Option(
141
+ None, "--url", help="Database URL (overrides DATABASE_URL env var)."
142
+ ),
143
+ ) -> None:
144
+ _require_migrations()
145
+ from hexastack_db.infra.migrations import run_revision
146
+
147
+ run_revision(
148
+ _get_config(directory, url),
149
+ message=message,
150
+ autogenerate=not no_autogenerate,
151
+ )
152
+
153
+ @db_app.command(name="current", help="Show the current applied revision.")
154
+ def db_current(
155
+ directory: str = typer.Option(
156
+ "migrations", "--dir", help="Migrations directory."
157
+ ),
158
+ url: str | None = typer.Option(
159
+ None, "--url", help="Database URL (overrides DATABASE_URL env var)."
160
+ ),
161
+ ) -> None:
162
+ _require_migrations()
163
+ from hexastack_db.infra.migrations import run_current
164
+
165
+ run_current(_get_config(directory, url))
166
+
167
+ @db_app.command(name="history", help="Show migration revision history.")
168
+ def db_history(
169
+ directory: str = typer.Option(
170
+ "migrations", "--dir", help="Migrations directory."
171
+ ),
172
+ url: str | None = typer.Option(
173
+ None, "--url", help="Database URL (overrides DATABASE_URL env var)."
174
+ ),
175
+ ) -> None:
176
+ _require_migrations()
177
+ from hexastack_db.infra.migrations import run_history
178
+
179
+ run_history(_get_config(directory, url))
180
+
181
+ @db_app.command(
182
+ name="stamp",
183
+ help="Stamp the database at a revision without running migrations.",
184
+ )
185
+ def db_stamp(
186
+ revision: str = typer.Argument(
187
+ "head", help="Revision to stamp (default: head)."
188
+ ),
189
+ directory: str = typer.Option(
190
+ "migrations", "--dir", help="Migrations directory."
191
+ ),
192
+ url: str | None = typer.Option(
193
+ None, "--url", help="Database URL (overrides DATABASE_URL env var)."
194
+ ),
195
+ ) -> None:
196
+ _require_migrations()
197
+ from hexastack_db.infra.migrations import stamp
198
+
199
+ stamp(_get_config(directory, url), revision)
200
+
201
+
202
+ def add_grpc_commands(app: typer.Typer) -> None:
203
+ """Register 'grpc' subcommand group for RPC services."""
204
+ if importlib.util.find_spec("hexastack_grpc") is None:
205
+ return
206
+
207
+ grpc_app = typer.Typer(
208
+ name="grpc",
209
+ help="High-performance gRPC server management.",
210
+ no_args_is_help=True,
211
+ )
212
+ app.add_typer(grpc_app, name="grpc")
213
+
214
+ @grpc_app.command(
215
+ name="serve",
216
+ help="Launch the gRPC server daemon.",
217
+ )
218
+ def grpc_serve(
219
+ host: str = typer.Option("0.0.0.0", "--host", "-h", help="Bind host."),
220
+ port: int = typer.Option(50051, "--port", "-p", help="Bind port."),
221
+ ) -> None:
222
+ import grpc
223
+
224
+ import hexastack.application.diagnostics
225
+ from hexastack_core.infra.bootstrap import bootstrap
226
+ from hexastack_grpc.adapters.server import run_grpc_server
227
+
228
+ runtime = bootstrap(packages_to_scan=[hexastack.application.diagnostics])
229
+ server = runtime.container.resolve(grpc.Server)
230
+ typer.echo(f"Starting gRPC server on {host}:{port}...")
231
+ run_grpc_server(server, block=True)
232
+
233
+
234
+ def add_mcp_commands(app: typer.Typer) -> None:
235
+ """Register 'mcp' subcommand group for AI agent integration."""
236
+ if importlib.util.find_spec("hexastack_mcp") is None:
237
+ return
238
+
239
+ mcp_app = typer.Typer(
240
+ name="mcp",
241
+ help="Model Context Protocol (MCP) AI agent tools and server.",
242
+ no_args_is_help=True,
243
+ )
244
+ app.add_typer(mcp_app, name="mcp")
245
+
246
+ @mcp_app.command(
247
+ name="run",
248
+ help="Launch the MCP server in stdio mode (for Claude, Cursor, Antigravity).",
249
+ )
250
+ def mcp_run() -> None:
251
+ from mcp.server.fastmcp import FastMCP as McpServer
252
+
253
+ import hexastack.application.diagnostics
254
+ from hexastack_core.infra.bootstrap import bootstrap
255
+ from hexastack_mcp.adapters.stdio import run_stdio_server
256
+
257
+ runtime = bootstrap(packages_to_scan=[hexastack.application.diagnostics])
258
+ server = runtime.container.resolve(McpServer)
259
+ run_stdio_server(server)
260
+
261
+
262
+ def add_ui_commands(app: typer.Typer) -> None:
263
+ """Register 'ui' command to launch the interactive DevTools web dashboard."""
264
+
265
+ @app.command(
266
+ name="ui",
267
+ help="Launch the Hexastack DevTools interactive web UI (requires hexastack[ui]).",
268
+ )
269
+ def ui_command(
270
+ host: str = typer.Option("127.0.0.1", "--host", "-h", help="Bind host."),
271
+ port: int = typer.Option(8000, "--port", "-p", help="Bind port."),
272
+ reload: bool = typer.Option(
273
+ False, "--reload/--no-reload", help="Enable auto-reloading."
274
+ ),
275
+ ) -> None:
276
+ if importlib.util.find_spec("nicegui") is None:
277
+ raise MissingDependencyError(
278
+ "NiceGUI is required to launch the interactive UI. "
279
+ "Install via 'pip install hexastack[ui]' or 'pip install hexastack-fastapi[ui]'."
280
+ )
281
+
282
+ if importlib.util.find_spec("uvicorn") is None:
283
+ raise MissingDependencyError(
284
+ "uvicorn is required to run the web server. "
285
+ "Install via 'pip install hexastack[web]' or 'pip install uvicorn[standard]'."
286
+ )
287
+
288
+ import uvicorn
289
+
290
+ from hexastack.adapters.fastapi import create_demo_app
291
+
292
+ typer.echo(f"Starting Hexastack DevTools at http://{host}:{port}/_devtools ...")
293
+ demo_app = create_demo_app()
294
+ uvicorn.run(demo_app, host=host, port=port, reload=reload)
295
+
296
+
297
+ def add_serve_command(app: typer.Typer) -> None:
298
+ """Register 'serve' command to launch the local FastAPI dev server using Uvicorn.
299
+
300
+ Args:
301
+ app: Target Typer application instance.
302
+ """
303
+
304
+ @app.command(
305
+ name="serve",
306
+ help="Launch the Hexastack local development server (requires hexastack[web]).",
307
+ )
308
+ def serve(
309
+ host: str = typer.Option(
310
+ "127.0.0.1", "--host", "-h", help="Bind host address."
311
+ ),
312
+ port: int = typer.Option(8000, "--port", "-p", help="Bind port number."),
313
+ reload: bool = typer.Option(
314
+ True, "--reload/--no-reload", help="Enable live reloading."
315
+ ),
316
+ ) -> None:
317
+ if importlib.util.find_spec("uvicorn") is None:
318
+ raise MissingDependencyError(
319
+ "uvicorn is required to run the local server. "
320
+ "Install via 'pip install hexastack[web]' or 'pip install uvicorn[standard]'."
321
+ )
322
+
323
+ if importlib.util.find_spec("fastapi") is None:
324
+ raise MissingDependencyError(
325
+ "fastapi is required to run the local server. "
326
+ "Install via 'pip install hexastack[fastapi]'."
327
+ )
328
+
329
+ import uvicorn
330
+
331
+ from hexastack.adapters.fastapi import create_demo_app
332
+
333
+ demo_app = create_demo_app()
334
+ uvicorn.run(demo_app, host=host, port=port, reload=reload)
@@ -0,0 +1,7 @@
1
+ """CLI adapter for project scaffolding."""
2
+
3
+ from hexastack.adapters.cli.scaffolding.commands import add_scaffold_commands
4
+
5
+ __all__ = [
6
+ "add_scaffold_commands",
7
+ ]
@@ -0,0 +1,156 @@
1
+ """CLI command definitions for project scaffolding and template subcommands.
2
+
3
+ Notes/Architectural Intent:
4
+ Supports both `hexastack new <name> --template <template>` and template subcommands
5
+ like `hexastack new web-api <name>` or `hexastack new event-driven <name>`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import cast
12
+
13
+ import typer
14
+
15
+ from hexastack.application.scaffolding.generator import (
16
+ TemplateType,
17
+ scaffold_project,
18
+ )
19
+
20
+ __all__ = [
21
+ "add_scaffold_commands",
22
+ ]
23
+
24
+
25
+ def add_scaffold_commands(app: typer.Typer) -> None:
26
+ """Register 'new' and 'init' project scaffolding commands with template subcommands.
27
+
28
+ Args:
29
+ app: Target Typer application instance.
30
+ """
31
+ new_app = typer.Typer(
32
+ name="new",
33
+ help="Scaffold a new Hexagonal microservice project.",
34
+ no_args_is_help=True,
35
+ invoke_without_command=True,
36
+ )
37
+ app.add_typer(new_app, name="new")
38
+
39
+ @new_app.command(
40
+ name="web-api",
41
+ help="Scaffold a RESTful Web API microservice (FastAPI + UoW + DevTools UI).",
42
+ )
43
+ def new_web_api(
44
+ name: str = typer.Argument(..., help="Name of the new microservice project."),
45
+ description: str = typer.Option(
46
+ "A modern RESTful microservice powered by Hexastack.",
47
+ "--description",
48
+ "-d",
49
+ help="Project description.",
50
+ ),
51
+ db: str = typer.Option(
52
+ "in-memory",
53
+ "--db",
54
+ help="Database driver: in-memory, sqlite, postgres.",
55
+ ),
56
+ ) -> None:
57
+ target_path = scaffold_project(
58
+ name=name,
59
+ template="web-api",
60
+ description=description,
61
+ db_type=db,
62
+ )
63
+ typer.echo(f"🎉 Created new Hexastack Web API project at '{target_path}'")
64
+ typer.echo(f" Next steps:\n cd {name}\n uv sync\n uv run pytest")
65
+
66
+ @new_app.command(
67
+ name="minimal",
68
+ help="Scaffold a lightweight CLI or worker service (Core + CQRS + Logging).",
69
+ )
70
+ def new_minimal(
71
+ name: str = typer.Argument(..., help="Name of the new microservice project."),
72
+ description: str = typer.Option(
73
+ "A lightweight Hexastack service.",
74
+ "--description",
75
+ "-d",
76
+ help="Project description.",
77
+ ),
78
+ ) -> None:
79
+ target_path = scaffold_project(
80
+ name=name,
81
+ template="minimal",
82
+ description=description,
83
+ )
84
+ typer.echo(f"🎉 Created new Minimal Hexastack project at '{target_path}'")
85
+ typer.echo(f" Next steps:\n cd {name}\n uv sync\n uv run pytest")
86
+
87
+ @new_app.command(
88
+ name="event-driven",
89
+ help="Scaffold an Event-Driven service with CloudEvents and Transactional Outbox.",
90
+ )
91
+ def new_event_driven(
92
+ name: str = typer.Argument(..., help="Name of the new microservice project."),
93
+ description: str = typer.Option(
94
+ "An event-driven Hexastack service.",
95
+ "--description",
96
+ "-d",
97
+ help="Project description.",
98
+ ),
99
+ ) -> None:
100
+ target_path = scaffold_project(
101
+ name=name,
102
+ template="event-driven",
103
+ description=description,
104
+ include_events=True,
105
+ )
106
+ typer.echo(f"🎉 Created new Event-Driven Hexastack project at '{target_path}'")
107
+ typer.echo(f" Next steps:\n cd {name}\n uv sync\n uv run pytest")
108
+
109
+ @new_app.command(
110
+ name="mcp-agent",
111
+ help="Scaffold an AI Model Context Protocol (MCP) server & agent tools service.",
112
+ )
113
+ def new_mcp_agent(
114
+ name: str = typer.Argument(..., help="Name of the new microservice project."),
115
+ description: str = typer.Option(
116
+ "An MCP AI agent tools service powered by Hexastack.",
117
+ "--description",
118
+ "-d",
119
+ help="Project description.",
120
+ ),
121
+ ) -> None:
122
+ target_path = scaffold_project(
123
+ name=name,
124
+ template="mcp-agent",
125
+ description=description,
126
+ include_mcp=True,
127
+ )
128
+ typer.echo(f"🎉 Created new MCP Agent Hexastack project at '{target_path}'")
129
+ typer.echo(f" Next steps:\n cd {name}\n uv sync\n uv run pytest")
130
+
131
+ @app.command(
132
+ name="init",
133
+ help="Initialize a new Hexastack microservice in the current working directory.",
134
+ )
135
+ def init(
136
+ name: str = typer.Option(
137
+ None,
138
+ "--name",
139
+ "-n",
140
+ help="Project name (defaults to current directory name).",
141
+ ),
142
+ template: str = typer.Option(
143
+ "web-api",
144
+ "--template",
145
+ "-t",
146
+ help="Project template: minimal, web-api, event-driven, mcp-agent, enterprise.",
147
+ ),
148
+ ) -> None:
149
+ current_dir = Path.cwd()
150
+ proj_name = name or current_dir.name
151
+ target_path = scaffold_project(
152
+ name=proj_name,
153
+ template=cast("TemplateType", template),
154
+ output_dir=current_dir.parent,
155
+ )
156
+ typer.echo(f"🎉 Initialized Hexastack project in '{target_path}'")
@@ -0,0 +1,27 @@
1
+ """Compatibility module for CLI adapter commands.
2
+
3
+ Notes/Architectural Intent:
4
+ Re-exports commands from hexastack.adapters.cli.demo and hexastack.adapters.cli.scaffolding.
5
+ """
6
+
7
+ from hexastack.adapters.cli.demo.commands import (
8
+ DemoGroupDocs,
9
+ InspectGroupDocs,
10
+ add_db_commands,
11
+ add_grpc_commands,
12
+ add_mcp_commands,
13
+ add_serve_command,
14
+ add_ui_commands,
15
+ )
16
+ from hexastack.adapters.cli.scaffolding.commands import add_scaffold_commands
17
+
18
+ __all__ = [
19
+ "add_db_commands",
20
+ "add_grpc_commands",
21
+ "add_mcp_commands",
22
+ "add_scaffold_commands",
23
+ "add_serve_command",
24
+ "add_ui_commands",
25
+ "DemoGroupDocs",
26
+ "InspectGroupDocs",
27
+ ]