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.
- bedrock_core-0.1.0/PKG-INFO +116 -0
- bedrock_core-0.1.0/README.md +82 -0
- bedrock_core-0.1.0/pyproject.toml +60 -0
- bedrock_core-0.1.0/src/bedrock/__init__.py +67 -0
- bedrock_core-0.1.0/src/bedrock/cli/__init__.py +0 -0
- bedrock_core-0.1.0/src/bedrock/cli/apps.py +290 -0
- bedrock_core-0.1.0/src/bedrock/cli/db.py +264 -0
- bedrock_core-0.1.0/src/bedrock/cli/main.py +30 -0
- bedrock_core-0.1.0/src/bedrock/cli/manage.py +132 -0
- bedrock_core-0.1.0/src/bedrock/cli/run.py +159 -0
- bedrock_core-0.1.0/src/bedrock/common/__init__.py +0 -0
- bedrock_core-0.1.0/src/bedrock/common/registry/__init__.py +13 -0
- bedrock_core-0.1.0/src/bedrock/common/registry/class_registry.py +46 -0
- bedrock_core-0.1.0/src/bedrock/common/registry/dict_manager.py +118 -0
- bedrock_core-0.1.0/src/bedrock/conf.py +99 -0
- bedrock_core-0.1.0/src/bedrock/constants.py +5 -0
- bedrock_core-0.1.0/src/bedrock/contrib/__init__.py +1 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/__init__.py +47 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/backends/__init__.py +0 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/backends/memory.py +421 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/backends/redis.py +371 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/base.py +97 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/bootstrap.py +19 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/coder.py +59 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/entities.py +28 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/exc.py +47 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/lock.py +313 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/manifest.yaml +3 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/schema.py +221 -0
- bedrock_core-0.1.0/src/bedrock/contrib/cache/service.py +371 -0
- bedrock_core-0.1.0/src/bedrock/database/__init__.py +17 -0
- bedrock_core-0.1.0/src/bedrock/database/base.py +68 -0
- bedrock_core-0.1.0/src/bedrock/database/config.py +56 -0
- bedrock_core-0.1.0/src/bedrock/database/connection.py +45 -0
- bedrock_core-0.1.0/src/bedrock/database/filters.py +372 -0
- bedrock_core-0.1.0/src/bedrock/database/manager.py +108 -0
- bedrock_core-0.1.0/src/bedrock/database/migrations/env.py +182 -0
- bedrock_core-0.1.0/src/bedrock/database/migrations/script.py.mako +28 -0
- bedrock_core-0.1.0/src/bedrock/database/migrations_manager.py +685 -0
- bedrock_core-0.1.0/src/bedrock/database/observer.py +196 -0
- bedrock_core-0.1.0/src/bedrock/database/service.py +184 -0
- bedrock_core-0.1.0/src/bedrock/database/session_factory.py +106 -0
- bedrock_core-0.1.0/src/bedrock/database/utils.py +24 -0
- bedrock_core-0.1.0/src/bedrock/di/__init__.py +21 -0
- bedrock_core-0.1.0/src/bedrock/di/_scope.py +81 -0
- bedrock_core-0.1.0/src/bedrock/di/container.py +225 -0
- bedrock_core-0.1.0/src/bedrock/di/decorators.py +158 -0
- bedrock_core-0.1.0/src/bedrock/di/exc.py +27 -0
- bedrock_core-0.1.0/src/bedrock/di/lifetime.py +17 -0
- bedrock_core-0.1.0/src/bedrock/entities.py +7 -0
- bedrock_core-0.1.0/src/bedrock/exc.py +38 -0
- bedrock_core-0.1.0/src/bedrock/hooks/__init__.py +20 -0
- bedrock_core-0.1.0/src/bedrock/hooks/caller.py +102 -0
- bedrock_core-0.1.0/src/bedrock/hooks/exc.py +27 -0
- bedrock_core-0.1.0/src/bedrock/hooks/markers.py +68 -0
- bedrock_core-0.1.0/src/bedrock/hooks/namespace.py +218 -0
- bedrock_core-0.1.0/src/bedrock/hooks/registry.py +251 -0
- bedrock_core-0.1.0/src/bedrock/logging.py +96 -0
- bedrock_core-0.1.0/src/bedrock/module/__init__.py +32 -0
- bedrock_core-0.1.0/src/bedrock/module/entities.py +94 -0
- bedrock_core-0.1.0/src/bedrock/module/exc.py +47 -0
- bedrock_core-0.1.0/src/bedrock/module/manifest.py +128 -0
- bedrock_core-0.1.0/src/bedrock/module/registry.py +363 -0
- bedrock_core-0.1.0/src/bedrock/module/signals.py +32 -0
- bedrock_core-0.1.0/src/bedrock/py.typed +0 -0
- bedrock_core-0.1.0/src/bedrock/settings.py +19 -0
- bedrock_core-0.1.0/src/bedrock/signal/LICENSE.txt +27 -0
- bedrock_core-0.1.0/src/bedrock/signal/__init__.py +17 -0
- bedrock_core-0.1.0/src/bedrock/signal/_utilities.py +117 -0
- bedrock_core-0.1.0/src/bedrock/signal/base.py +674 -0
- bedrock_core-0.1.0/src/bedrock/testing/__init__.py +19 -0
- bedrock_core-0.1.0/src/bedrock/testing/di.py +24 -0
- bedrock_core-0.1.0/src/bedrock/testing/hooks.py +35 -0
- bedrock_core-0.1.0/src/bedrock/utils/__init__.py +5 -0
- bedrock_core-0.1.0/src/bedrock/utils/inspect_func.py +67 -0
- bedrock_core-0.1.0/src/bedrock/utils/lazyload.py +303 -0
- 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"]
|