bedrock-core 0.1.0__tar.gz

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 (77) hide show
  1. bedrock_core-0.1.0/PKG-INFO +116 -0
  2. bedrock_core-0.1.0/README.md +82 -0
  3. bedrock_core-0.1.0/pyproject.toml +60 -0
  4. bedrock_core-0.1.0/src/bedrock/__init__.py +67 -0
  5. bedrock_core-0.1.0/src/bedrock/cli/__init__.py +0 -0
  6. bedrock_core-0.1.0/src/bedrock/cli/apps.py +290 -0
  7. bedrock_core-0.1.0/src/bedrock/cli/db.py +264 -0
  8. bedrock_core-0.1.0/src/bedrock/cli/main.py +30 -0
  9. bedrock_core-0.1.0/src/bedrock/cli/manage.py +132 -0
  10. bedrock_core-0.1.0/src/bedrock/cli/run.py +159 -0
  11. bedrock_core-0.1.0/src/bedrock/common/__init__.py +0 -0
  12. bedrock_core-0.1.0/src/bedrock/common/registry/__init__.py +13 -0
  13. bedrock_core-0.1.0/src/bedrock/common/registry/class_registry.py +46 -0
  14. bedrock_core-0.1.0/src/bedrock/common/registry/dict_manager.py +118 -0
  15. bedrock_core-0.1.0/src/bedrock/conf.py +99 -0
  16. bedrock_core-0.1.0/src/bedrock/constants.py +5 -0
  17. bedrock_core-0.1.0/src/bedrock/contrib/__init__.py +1 -0
  18. bedrock_core-0.1.0/src/bedrock/contrib/cache/__init__.py +47 -0
  19. bedrock_core-0.1.0/src/bedrock/contrib/cache/backends/__init__.py +0 -0
  20. bedrock_core-0.1.0/src/bedrock/contrib/cache/backends/memory.py +421 -0
  21. bedrock_core-0.1.0/src/bedrock/contrib/cache/backends/redis.py +371 -0
  22. bedrock_core-0.1.0/src/bedrock/contrib/cache/base.py +97 -0
  23. bedrock_core-0.1.0/src/bedrock/contrib/cache/bootstrap.py +19 -0
  24. bedrock_core-0.1.0/src/bedrock/contrib/cache/coder.py +59 -0
  25. bedrock_core-0.1.0/src/bedrock/contrib/cache/entities.py +28 -0
  26. bedrock_core-0.1.0/src/bedrock/contrib/cache/exc.py +47 -0
  27. bedrock_core-0.1.0/src/bedrock/contrib/cache/lock.py +313 -0
  28. bedrock_core-0.1.0/src/bedrock/contrib/cache/manifest.yaml +3 -0
  29. bedrock_core-0.1.0/src/bedrock/contrib/cache/schema.py +221 -0
  30. bedrock_core-0.1.0/src/bedrock/contrib/cache/service.py +371 -0
  31. bedrock_core-0.1.0/src/bedrock/database/__init__.py +17 -0
  32. bedrock_core-0.1.0/src/bedrock/database/base.py +68 -0
  33. bedrock_core-0.1.0/src/bedrock/database/config.py +56 -0
  34. bedrock_core-0.1.0/src/bedrock/database/connection.py +45 -0
  35. bedrock_core-0.1.0/src/bedrock/database/filters.py +372 -0
  36. bedrock_core-0.1.0/src/bedrock/database/manager.py +108 -0
  37. bedrock_core-0.1.0/src/bedrock/database/migrations/env.py +182 -0
  38. bedrock_core-0.1.0/src/bedrock/database/migrations/script.py.mako +28 -0
  39. bedrock_core-0.1.0/src/bedrock/database/migrations_manager.py +685 -0
  40. bedrock_core-0.1.0/src/bedrock/database/observer.py +196 -0
  41. bedrock_core-0.1.0/src/bedrock/database/service.py +184 -0
  42. bedrock_core-0.1.0/src/bedrock/database/session_factory.py +106 -0
  43. bedrock_core-0.1.0/src/bedrock/database/utils.py +24 -0
  44. bedrock_core-0.1.0/src/bedrock/di/__init__.py +21 -0
  45. bedrock_core-0.1.0/src/bedrock/di/_scope.py +81 -0
  46. bedrock_core-0.1.0/src/bedrock/di/container.py +225 -0
  47. bedrock_core-0.1.0/src/bedrock/di/decorators.py +158 -0
  48. bedrock_core-0.1.0/src/bedrock/di/exc.py +27 -0
  49. bedrock_core-0.1.0/src/bedrock/di/lifetime.py +17 -0
  50. bedrock_core-0.1.0/src/bedrock/entities.py +7 -0
  51. bedrock_core-0.1.0/src/bedrock/exc.py +38 -0
  52. bedrock_core-0.1.0/src/bedrock/hooks/__init__.py +20 -0
  53. bedrock_core-0.1.0/src/bedrock/hooks/caller.py +102 -0
  54. bedrock_core-0.1.0/src/bedrock/hooks/exc.py +27 -0
  55. bedrock_core-0.1.0/src/bedrock/hooks/markers.py +68 -0
  56. bedrock_core-0.1.0/src/bedrock/hooks/namespace.py +218 -0
  57. bedrock_core-0.1.0/src/bedrock/hooks/registry.py +251 -0
  58. bedrock_core-0.1.0/src/bedrock/logging.py +96 -0
  59. bedrock_core-0.1.0/src/bedrock/module/__init__.py +32 -0
  60. bedrock_core-0.1.0/src/bedrock/module/entities.py +94 -0
  61. bedrock_core-0.1.0/src/bedrock/module/exc.py +47 -0
  62. bedrock_core-0.1.0/src/bedrock/module/manifest.py +128 -0
  63. bedrock_core-0.1.0/src/bedrock/module/registry.py +363 -0
  64. bedrock_core-0.1.0/src/bedrock/module/signals.py +32 -0
  65. bedrock_core-0.1.0/src/bedrock/py.typed +0 -0
  66. bedrock_core-0.1.0/src/bedrock/settings.py +19 -0
  67. bedrock_core-0.1.0/src/bedrock/signal/LICENSE.txt +27 -0
  68. bedrock_core-0.1.0/src/bedrock/signal/__init__.py +17 -0
  69. bedrock_core-0.1.0/src/bedrock/signal/_utilities.py +117 -0
  70. bedrock_core-0.1.0/src/bedrock/signal/base.py +674 -0
  71. bedrock_core-0.1.0/src/bedrock/testing/__init__.py +19 -0
  72. bedrock_core-0.1.0/src/bedrock/testing/di.py +24 -0
  73. bedrock_core-0.1.0/src/bedrock/testing/hooks.py +35 -0
  74. bedrock_core-0.1.0/src/bedrock/utils/__init__.py +5 -0
  75. bedrock_core-0.1.0/src/bedrock/utils/inspect_func.py +67 -0
  76. bedrock_core-0.1.0/src/bedrock/utils/lazyload.py +303 -0
  77. bedrock_core-0.1.0/src/bedrock/utils/proxy_obj.py +64 -0
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: bedrock-core
3
+ Version: 0.1.0
4
+ Summary: A modular Python application framework with manifest-driven module loading, lifecycle management, and contrib modules.
5
+ Keywords: framework,modular,module-system,lifecycle,sqlalchemy
6
+ Author: maacck
7
+ Author-email: maacck <c.mai@madainchina.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: alembic>=1.18.4
18
+ Requires-Dist: asgiref>=3.8.1
19
+ Requires-Dist: loguru>=0.7.0
20
+ Requires-Dist: orjson>=3.11.8
21
+ Requires-Dist: pydantic>=2.12.5
22
+ Requires-Dist: pydantic-settings>=2.13.1
23
+ Requires-Dist: pyyaml>=6.0.2
24
+ Requires-Dist: sqlalchemy>=2.0.49
25
+ Requires-Dist: typer>=0.24.1
26
+ Requires-Dist: redis>=4.2.0 ; extra == 'cache-redis'
27
+ Requires-Python: >=3.12
28
+ Project-URL: Bug Tracker, https://github.com/maacck/bedrock-py/issues
29
+ Project-URL: Documentation, https://bedrock-py.com
30
+ Project-URL: Homepage, https://bedrock-py.com
31
+ Project-URL: Repository, https://github.com/maacck/bedrock-py
32
+ Provides-Extra: cache-redis
33
+ Description-Content-Type: text/markdown
34
+
35
+ # bedrock
36
+
37
+ `bedrock` is the runtime core of the Bedrock modular framework.
38
+
39
+ It provides manifest-driven module loading, lifecycle management, and first-party contrib modules for common capabilities.
40
+
41
+ ## Status
42
+
43
+ **The core runtime is fully implemented and functional.**
44
+
45
+ This package contains production-ready subsystems:
46
+
47
+ - Module registry with dependency resolution and lifecycle hooks
48
+ - Dependency injection container with singleton/transient/scoped lifetimes
49
+ - Hook system for structured call/response extension points (sync, async, robust)
50
+ - SQLAlchemy 2.0 database layer with Alembic migrations
51
+ - Cache system with memory and Redis backends
52
+ - Signal/event system (blinker-derived)
53
+ - Typer-based CLI for module and database management
54
+ - Comprehensive utility library (lazy loading, introspection, string helpers)
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ # With uv (recommended)
60
+ uv add bedrock-core
61
+
62
+ # With poetry
63
+ poetry add bedrock-core
64
+
65
+ # With pip
66
+ pip install bedrock-core
67
+ ```
68
+
69
+ ## Quick Start
70
+
71
+ ```python
72
+ import bedrock
73
+
74
+ # Initialize the runtime (discovers and loads modules)
75
+ bedrock.setup()
76
+
77
+ # Access key singletons
78
+ from bedrock.module import apps # ModuleRegistry
79
+ from bedrock.database import db # DatabaseManager
80
+ from bedrock.contrib.cache import cache # CacheService
81
+ ```
82
+
83
+ ## Key Singletons
84
+
85
+ | Singleton | Class | Import |
86
+ |-----------|-------|--------|
87
+ | `apps` | `ModuleRegistry` | `from bedrock.module import apps` |
88
+ | `container` | `Container` | `from bedrock.di import container` |
89
+ | `hooks` | `HookRegistry` | `from bedrock.hooks import hooks` |
90
+ | `db` | `DatabaseManager` | `from bedrock.database import db` |
91
+ | `cache` | `CacheService` | `from bedrock.contrib.cache import cache` |
92
+
93
+ ## Module Structure
94
+
95
+ ```
96
+ modules/
97
+ └── inventory/
98
+ ├── __init__.py
99
+ ├── manifest.yaml # Module identity and dependencies
100
+ ├── models.py # SQLAlchemy models (optional)
101
+ ├── entities.py # Pydantic models for validation
102
+ ├── service.py # Business logic
103
+ ├── exc.py # Module exceptions
104
+ └── bootstrap.py # Lifecycle hooks
105
+ ```
106
+
107
+ ## Architecture
108
+
109
+ Bedrock is framework-agnostic by design. The core runtime has no HTTP dependencies and is usable in:
110
+
111
+ - API servers (FastAPI, Flask, etc.)
112
+ - Background workers (Celery, etc.)
113
+ - CLI tools
114
+ - Scripts and automation
115
+
116
+ For more details, see the [documentation](https://bedrock-py.com).
@@ -0,0 +1,82 @@
1
+ # bedrock
2
+
3
+ `bedrock` is the runtime core of the Bedrock modular framework.
4
+
5
+ It provides manifest-driven module loading, lifecycle management, and first-party contrib modules for common capabilities.
6
+
7
+ ## Status
8
+
9
+ **The core runtime is fully implemented and functional.**
10
+
11
+ This package contains production-ready subsystems:
12
+
13
+ - Module registry with dependency resolution and lifecycle hooks
14
+ - Dependency injection container with singleton/transient/scoped lifetimes
15
+ - Hook system for structured call/response extension points (sync, async, robust)
16
+ - SQLAlchemy 2.0 database layer with Alembic migrations
17
+ - Cache system with memory and Redis backends
18
+ - Signal/event system (blinker-derived)
19
+ - Typer-based CLI for module and database management
20
+ - Comprehensive utility library (lazy loading, introspection, string helpers)
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ # With uv (recommended)
26
+ uv add bedrock-core
27
+
28
+ # With poetry
29
+ poetry add bedrock-core
30
+
31
+ # With pip
32
+ pip install bedrock-core
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ import bedrock
39
+
40
+ # Initialize the runtime (discovers and loads modules)
41
+ bedrock.setup()
42
+
43
+ # Access key singletons
44
+ from bedrock.module import apps # ModuleRegistry
45
+ from bedrock.database import db # DatabaseManager
46
+ from bedrock.contrib.cache import cache # CacheService
47
+ ```
48
+
49
+ ## Key Singletons
50
+
51
+ | Singleton | Class | Import |
52
+ |-----------|-------|--------|
53
+ | `apps` | `ModuleRegistry` | `from bedrock.module import apps` |
54
+ | `container` | `Container` | `from bedrock.di import container` |
55
+ | `hooks` | `HookRegistry` | `from bedrock.hooks import hooks` |
56
+ | `db` | `DatabaseManager` | `from bedrock.database import db` |
57
+ | `cache` | `CacheService` | `from bedrock.contrib.cache import cache` |
58
+
59
+ ## Module Structure
60
+
61
+ ```
62
+ modules/
63
+ └── inventory/
64
+ ├── __init__.py
65
+ ├── manifest.yaml # Module identity and dependencies
66
+ ├── models.py # SQLAlchemy models (optional)
67
+ ├── entities.py # Pydantic models for validation
68
+ ├── service.py # Business logic
69
+ ├── exc.py # Module exceptions
70
+ └── bootstrap.py # Lifecycle hooks
71
+ ```
72
+
73
+ ## Architecture
74
+
75
+ Bedrock is framework-agnostic by design. The core runtime has no HTTP dependencies and is usable in:
76
+
77
+ - API servers (FastAPI, Flask, etc.)
78
+ - Background workers (Celery, etc.)
79
+ - CLI tools
80
+ - Scripts and automation
81
+
82
+ For more details, see the [documentation](https://bedrock-py.com).
@@ -0,0 +1,60 @@
1
+ [project]
2
+ name = "bedrock-core"
3
+ version = "0.1.0"
4
+ description = "A modular Python application framework with manifest-driven module loading, lifecycle management, and contrib modules."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "maacck", email = "c.mai@madainchina.com" }
9
+ ]
10
+ keywords = ["framework", "modular", "module-system", "lifecycle", "sqlalchemy"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
19
+ "Typing :: Typed",
20
+ ]
21
+ requires-python = ">=3.12"
22
+ dependencies = [
23
+ "alembic>=1.18.4",
24
+ "asgiref>=3.8.1",
25
+ "loguru>=0.7.0",
26
+ "orjson>=3.11.8",
27
+ "pydantic>=2.12.5",
28
+ "pydantic-settings>=2.13.1",
29
+ "pyyaml>=6.0.2",
30
+ "sqlalchemy>=2.0.49",
31
+ "typer>=0.24.1",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://bedrock-py.com"
36
+ Repository = "https://github.com/maacck/bedrock-py"
37
+ Documentation = "https://bedrock-py.com"
38
+ "Bug Tracker" = "https://github.com/maacck/bedrock-py/issues"
39
+
40
+ [project.optional-dependencies]
41
+ cache-redis = ["redis>=4.2.0"]
42
+
43
+ [project.scripts]
44
+ bedrock = "bedrock.cli.main:entrypoint"
45
+
46
+ [build-system]
47
+ requires = ["uv_build>=0.8.15,<0.9.0"]
48
+ build-backend = "uv_build"
49
+
50
+ [tool.uv.build-backend]
51
+ module-root = "src"
52
+ module-name = "bedrock"
53
+
54
+ [dependency-groups]
55
+ dev = [
56
+ "pytest>=8.4.2",
57
+ "pytest-asyncio>=1.2.0",
58
+ "coverage>=7.13.5",
59
+ "pytest-cov>=7.1.0",
60
+ ]
@@ -0,0 +1,67 @@
1
+ from .conf import SettingsProxy
2
+ from .entities import BedrockEntity
3
+ from .logging import get_logger
4
+ from .module import ModuleRegistry, apps
5
+
6
+ __all__ = [
7
+ "ModuleRegistry",
8
+ "apps",
9
+ "BedrockEntity",
10
+ "setup",
11
+ "get_logger",
12
+ "SettingsProxy",
13
+ ]
14
+
15
+
16
+ def setup(app: str | None = None) -> None:
17
+ """Bootstrap the Bedrock application registry.
18
+
19
+ Reads the target app from the ``app`` argument, falling back to the
20
+ ``BEDROCK_APP`` environment variable (via :attr:`bedrock.settings.settings`).
21
+ Calls :meth:`~bedrock.module.ModuleRegistry.populate` on the global
22
+ :data:`apps` registry, which installs modules and fires lifecycle hooks.
23
+
24
+ This function is idempotent: if the registry is already ready it returns
25
+ immediately without re-populating.
26
+
27
+ Args:
28
+ app: Dotted import path of the root application module to install,
29
+ e.g. ``"myproject.app"``. When *None* the value is read from
30
+ ``BEDROCK_APP`` in the environment.
31
+
32
+ Raises:
33
+ ImproperlyConfigured: When no app name can be resolved.
34
+
35
+ Example — ASGI entry-point (``myproject/asgi.py``)::
36
+
37
+ import bedrock
38
+
39
+ bedrock.setup()
40
+
41
+ from myproject.api import create_app
42
+
43
+ application = create_app()
44
+
45
+ Example — Celery entry-point (``myproject/celery.py``)::
46
+
47
+ import bedrock
48
+
49
+ bedrock.setup()
50
+
51
+ from celery import Celery
52
+
53
+ app = Celery("myproject")
54
+ """
55
+ if apps.ready:
56
+ return
57
+
58
+ from bedrock.exc import ImproperlyConfigured
59
+ from bedrock.settings import settings
60
+
61
+ app_name = app or settings.APP
62
+ if app_name is None:
63
+ raise ImproperlyConfigured(
64
+ "No app specified. Pass an app name to setup() or set the BEDROCK_APP environment variable."
65
+ )
66
+
67
+ apps.populate([app_name])
File without changes
@@ -0,0 +1,290 @@
1
+ """Application inspection and management commands for Bedrock modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass, field
7
+ from importlib.util import find_spec
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+
13
+ from bedrock.utils import inspect_func
14
+
15
+ from ..module.exc import InvalidManifestError, InvalidModuleCallableError, ModuleError
16
+ from ..module.manifest import build_app_config, load_manifest
17
+ from ..utils.lazyload import load_optional_callable
18
+
19
+ apps = typer.Typer(
20
+ rich_markup_mode="rich",
21
+ help="Manage Bedrock applications and modules.",
22
+ )
23
+
24
+
25
+ @dataclass
26
+ class InspectResult:
27
+ """Result of a basic module inspection."""
28
+
29
+ import_path: str
30
+ manifest_valid: bool = False
31
+ module_loads: bool = False
32
+ has_bootstrap: bool = False
33
+ has_models: bool = False
34
+ installation_valid: bool = False
35
+ errors: list[str] = field(default_factory=list)
36
+
37
+
38
+ def _status_icon(passed: bool, *, optional: bool = False) -> str:
39
+ """Return a colored status icon for Rich table display."""
40
+ if passed:
41
+ return "[bold green]✓[/bold green]"
42
+ return "[dim]-[/dim]" if optional else "[bold red]✗[/bold red]"
43
+
44
+
45
+ def _check_installation_hooks(func: Callable):
46
+ if not inspect_func.func_accepts_kwargs(func):
47
+ raise InvalidModuleCallableError(
48
+ f"Installation hook '{func.__module__}.{func.__name__}' must accept **kwargs for future extensibility."
49
+ )
50
+
51
+
52
+ def _run_basic_inspect(import_path: str, console: Console) -> InspectResult:
53
+ """Validate manifest, loadability, submodules, and installation hooks with console output."""
54
+ result = InspectResult(import_path=import_path)
55
+
56
+ try:
57
+ load_manifest(import_path)
58
+ result.manifest_valid = True
59
+ console.print(f"[bold green]✓[/bold green] Manifest for ''[bold]{import_path}[/bold]'' is valid.")
60
+ except InvalidManifestError as exc:
61
+ result.errors.append(f"Manifest validation failed: {exc}")
62
+ console.print(f"[bold red]✗[/bold red] Manifest validation failed: {exc}")
63
+ return result
64
+
65
+ try:
66
+ app_config = build_app_config(import_path)
67
+ result.module_loads = True
68
+ console.print(f"[bold green]✓[/bold green] Module ''[bold]{import_path}[/bold]'' loads successfully.")
69
+
70
+ if app_config.bootstrap_module:
71
+ result.has_bootstrap = True
72
+ console.print("[bold green]✓[/bold green] Bootstrap submodule is available.")
73
+ else:
74
+ console.print("[bold yellow]![/bold yellow] No bootstrap submodule found.")
75
+
76
+ if app_config.models_module:
77
+ result.has_models = True
78
+ console.print("[bold green]✓[/bold green] Models submodule is available.")
79
+ else:
80
+ console.print("[bold yellow]![/bold yellow] No models submodule found.")
81
+ except ModuleError as exc:
82
+ result.errors.append(f"Module load failed: {exc}")
83
+ console.print(f"[bold red]✗[/bold red] Module load failed: {exc}")
84
+ return result
85
+
86
+ try:
87
+ installation = load_optional_callable(f"{app_config.name}.installation:install")
88
+ if not installation:
89
+ result.errors.append(f"No 'install' function found in installation.py for {app_config.name}.")
90
+ console.print(
91
+ f"[bold red]✗[/bold red] No 'install' function found in installation.py for {app_config.name}."
92
+ )
93
+ return result
94
+ _check_installation_hooks(installation)
95
+ pre_install = load_optional_callable(f"{app_config.name}.installation:pre_install")
96
+ if pre_install:
97
+ _check_installation_hooks(pre_install)
98
+ installation()
99
+ post_install = load_optional_callable(f"{app_config.name}.installation:post_install")
100
+ if post_install:
101
+ _check_installation_hooks(post_install)
102
+ result.installation_valid = True
103
+ console.print(
104
+ f"[bold green]✓[/bold green] Installation hooks for '{app_config.name}' are valid and executable."
105
+ )
106
+ except (InvalidModuleCallableError, ModuleError) as exc:
107
+ result.errors.append(f"Installation hook check failed: {exc}")
108
+ console.print(f"[bold red]✗[/bold red] Installation hook check failed: {exc}")
109
+
110
+ return result
111
+
112
+
113
+ def _inspect_dependency(dep_path: str) -> InspectResult:
114
+ """Run all inspection checks on a dependency without console output."""
115
+ result = InspectResult(import_path=dep_path)
116
+
117
+ spec = find_spec(dep_path)
118
+ if spec is None:
119
+ result.errors.append(f"Cannot import: package '{dep_path}' not found in Python path.")
120
+ return result
121
+
122
+ try:
123
+ load_manifest(dep_path)
124
+ result.manifest_valid = True
125
+ except InvalidManifestError as exc:
126
+ result.errors.append(f"Manifest: {exc}")
127
+ return result
128
+
129
+ try:
130
+ app_config = build_app_config(dep_path)
131
+ result.module_loads = True
132
+ result.has_bootstrap = app_config.bootstrap_module is not None
133
+ result.has_models = app_config.models_module is not None
134
+ except ModuleError as exc:
135
+ result.errors.append(f"Load: {exc}")
136
+ return result
137
+
138
+ try:
139
+ installation = load_optional_callable(f"{app_config.name}.installation:install")
140
+ if not installation:
141
+ result.errors.append(f"No 'install' function in installation.py for {app_config.name}.")
142
+ return result
143
+ _check_installation_hooks(installation)
144
+ pre_install = load_optional_callable(f"{app_config.name}.installation:pre_install")
145
+ if pre_install:
146
+ _check_installation_hooks(pre_install)
147
+ installation()
148
+ post_install = load_optional_callable(f"{app_config.name}.installation:post_install")
149
+ if post_install:
150
+ _check_installation_hooks(post_install)
151
+ result.installation_valid = True
152
+ except (InvalidModuleCallableError, ModuleError) as exc:
153
+ result.errors.append(f"Installation: {exc}")
154
+
155
+ return result
156
+
157
+
158
+ @apps.command()
159
+ def inspect(
160
+ import_path: str = typer.Argument(..., help="Python import path of the module, e.g., ''bedrock.contrib.cache''."),
161
+ ) -> None:
162
+ """Inspect a module''s manifest format and loadability.
163
+
164
+ Validates the manifest.yaml structure, checks bootstrap and models
165
+ submodules, installation hooks, and verifies that all declared
166
+ dependencies (depends_on) are importable and pass basic checks.
167
+
168
+ Args:
169
+ import_path: Python import path of the module.
170
+
171
+ Raises:
172
+ typer.Exit: If the manifest or module fails to load.
173
+ """
174
+ console = Console()
175
+
176
+ result = _run_basic_inspect(import_path, console)
177
+ if result.errors:
178
+ raise typer.Exit(1)
179
+
180
+ manifest = load_manifest(import_path)
181
+ if not manifest.depends_on:
182
+ console.print("\n[dim]No dependencies declared in depends_on.[/dim]")
183
+ return
184
+
185
+ console.print(f"\n[bold]Checking dependencies ({len(manifest.depends_on)})...[/bold]\n")
186
+
187
+ dep_results: list[InspectResult] = []
188
+ for dep_path in manifest.depends_on:
189
+ dep_results.append(_inspect_dependency(dep_path))
190
+
191
+ table = Table(title="Dependency Inspection", show_lines=True)
192
+ table.add_column("Module", style="bold cyan", no_wrap=True)
193
+ table.add_column("Importable", justify="center")
194
+ table.add_column("Manifest", justify="center")
195
+ table.add_column("Loads", justify="center")
196
+ table.add_column("Bootstrap", justify="center")
197
+ table.add_column("Models", justify="center")
198
+ table.add_column("Installation", justify="center")
199
+ table.add_column("Errors", style="red")
200
+
201
+ has_failures = False
202
+ for dep_result in dep_results:
203
+ importable = find_spec(dep_result.import_path) is not None
204
+ if not importable or dep_result.errors:
205
+ has_failures = True
206
+
207
+ table.add_row(
208
+ dep_result.import_path,
209
+ _status_icon(importable),
210
+ _status_icon(dep_result.manifest_valid),
211
+ _status_icon(dep_result.module_loads),
212
+ _status_icon(dep_result.has_bootstrap, optional=True),
213
+ _status_icon(dep_result.has_models, optional=True),
214
+ _status_icon(dep_result.installation_valid),
215
+ "; ".join(dep_result.errors) if dep_result.errors else "",
216
+ )
217
+
218
+ console.print(table)
219
+
220
+ if has_failures:
221
+ console.print("\n[bold red]✗[/bold red] Some dependencies failed inspection.")
222
+ raise typer.Exit(1)
223
+ else:
224
+ console.print("\n[bold green]✓[/bold green] All dependencies pass inspection.")
225
+
226
+
227
+ @apps.command()
228
+ def info(
229
+ import_path: str = typer.Argument(..., help="Python import path of the module, e.g., ''bedrock.contrib.cache''."),
230
+ ) -> None:
231
+ """Display rich information about a Bedrock module.
232
+
233
+ Shows the module title, description, version, dependencies, bootstrap
234
+ and models status, and configured commands.
235
+
236
+ Args:
237
+ import_path: Python import path of the module.
238
+
239
+ Raises:
240
+ typer.Exit: If the module cannot be loaded.
241
+ """
242
+ console = Console()
243
+
244
+ try:
245
+ app_config = build_app_config(import_path)
246
+ manifest = app_config.manifest
247
+ except (InvalidManifestError, ModuleError) as exc:
248
+ console.print(f"[bold red]Error:[/bold red] {exc}")
249
+ raise typer.Exit(1) from exc
250
+
251
+ table = Table(show_header=False, box=None, padding=(0, 1))
252
+ table.add_column("Property", style="bold cyan", no_wrap=True)
253
+ table.add_column("Value", style="white")
254
+
255
+ table.add_row("Title", manifest.title)
256
+ table.add_row("Description", manifest.description or "N/A")
257
+ table.add_row("Version", manifest.version)
258
+ table.add_row("Import Path", import_path)
259
+
260
+ if manifest.depends_on:
261
+ table.add_row("Dependencies", "\n".join(manifest.depends_on))
262
+ else:
263
+ table.add_row("Dependencies", "None")
264
+
265
+ table.add_row("Bootstrap", "Yes" if app_config.bootstrap_module else "No")
266
+ table.add_row("Models", "Yes" if app_config.models_module else "No")
267
+
268
+ if manifest.commands:
269
+ table.add_row("Commands", manifest.commands)
270
+
271
+ console.print(table)
272
+
273
+
274
+ @apps.command()
275
+ def install(
276
+ import_path: str = typer.Argument(..., help="Python import path of the module, e.g., ''bedrock.contrib.cache''."),
277
+ ) -> None:
278
+ """Install a Bedrock module.
279
+
280
+ This command is a skeleton placeholder and is not yet implemented.
281
+
282
+ Args:
283
+ import_path: Python import path of the module.
284
+ """
285
+ console = Console()
286
+ console.print("[bold yellow]⚠[/bold yellow] Install command is not yet implemented.")
287
+ console.print(f"[dim]Module: {import_path}[/dim]")
288
+
289
+
290
+ __all__ = ["apps"]