simple-module-hosting 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.
Files changed (65) hide show
  1. simple_module_hosting/__init__.py +7 -0
  2. simple_module_hosting/_error_handlers.py +54 -0
  3. simple_module_hosting/_hydrate_step.py +39 -0
  4. simple_module_hosting/_inertia_setup.py +73 -0
  5. simple_module_hosting/_inertia_shared.py +61 -0
  6. simple_module_hosting/_observability.py +108 -0
  7. simple_module_hosting/_phase_helpers.py +160 -0
  8. simple_module_hosting/app_builder.py +281 -0
  9. simple_module_hosting/bootstrap_settings.py +55 -0
  10. simple_module_hosting/cli.py +292 -0
  11. simple_module_hosting/health.py +79 -0
  12. simple_module_hosting/host_settings.py +33 -0
  13. simple_module_hosting/i18n_deps.py +25 -0
  14. simple_module_hosting/i18n_manifest.py +202 -0
  15. simple_module_hosting/i18n_middleware.py +95 -0
  16. simple_module_hosting/inertia_deps.py +27 -0
  17. simple_module_hosting/inertia_utils.py +31 -0
  18. simple_module_hosting/logging.py +91 -0
  19. simple_module_hosting/manifest.py +250 -0
  20. simple_module_hosting/middleware.py +272 -0
  21. simple_module_hosting/migrations.py +65 -0
  22. simple_module_hosting/permissions.py +75 -0
  23. simple_module_hosting/py.typed +0 -0
  24. simple_module_hosting/redirects.py +45 -0
  25. simple_module_hosting/scaffolding.py +294 -0
  26. simple_module_hosting/settings.py +10 -0
  27. simple_module_hosting/templates/host/.env.example +20 -0
  28. simple_module_hosting/templates/host/.gitignore +19 -0
  29. simple_module_hosting/templates/host/Makefile +24 -0
  30. simple_module_hosting/templates/host/README.md.tpl +59 -0
  31. simple_module_hosting/templates/host/alembic.ini +36 -0
  32. simple_module_hosting/templates/host/client_app/app.tsx +16 -0
  33. simple_module_hosting/templates/host/client_app/main.tsx +2 -0
  34. simple_module_hosting/templates/host/client_app/package.json.tpl +23 -0
  35. simple_module_hosting/templates/host/client_app/pages/Error.tsx +13 -0
  36. simple_module_hosting/templates/host/client_app/pages.ts +47 -0
  37. simple_module_hosting/templates/host/client_app/styles.css +7 -0
  38. simple_module_hosting/templates/host/client_app/tsconfig.json +16 -0
  39. simple_module_hosting/templates/host/client_app/vite.config.ts +39 -0
  40. simple_module_hosting/templates/host/main.py +27 -0
  41. simple_module_hosting/templates/host/migrations/env.py +80 -0
  42. simple_module_hosting/templates/host/migrations/script.py.mako +26 -0
  43. simple_module_hosting/templates/host/migrations/versions/.gitkeep +1 -0
  44. simple_module_hosting/templates/host/pyproject.toml.tpl +17 -0
  45. simple_module_hosting/templates/host/templates/index.html +12 -0
  46. simple_module_hosting/templates/module/.github/workflows/ci.yml +32 -0
  47. simple_module_hosting/templates/module/.github/workflows/publish.yml.tpl +52 -0
  48. simple_module_hosting/templates/module/.gitignore +14 -0
  49. simple_module_hosting/templates/module/README.md.tpl +82 -0
  50. simple_module_hosting/templates/module/__PACKAGE__/__init__.py +0 -0
  51. simple_module_hosting/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
  52. simple_module_hosting/templates/module/__PACKAGE__/endpoints/api.py.tpl +11 -0
  53. simple_module_hosting/templates/module/__PACKAGE__/module.py.tpl +46 -0
  54. simple_module_hosting/templates/module/__PACKAGE__/pages/.gitkeep +1 -0
  55. simple_module_hosting/templates/module/__PACKAGE__/services.py.tpl +22 -0
  56. simple_module_hosting/templates/module/package.json.tpl +16 -0
  57. simple_module_hosting/templates/module/pyproject.toml.tpl +39 -0
  58. simple_module_hosting/templates/module/tests/__init__.py +0 -0
  59. simple_module_hosting/templates/module/tests/test_module.py.tpl +27 -0
  60. simple_module_hosting/templates/module/tsconfig.json.tpl +11 -0
  61. simple_module_hosting-0.0.1.dist-info/METADATA +93 -0
  62. simple_module_hosting-0.0.1.dist-info/RECORD +65 -0
  63. simple_module_hosting-0.0.1.dist-info/WHEEL +4 -0
  64. simple_module_hosting-0.0.1.dist-info/entry_points.txt +3 -0
  65. simple_module_hosting-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,27 @@
1
+ """Host application entry point.
2
+
3
+ This file was generated by `sm create-host`. Modules are discovered at boot
4
+ via entry_points; add them to this host's pyproject.toml to install them.
5
+ """
6
+
7
+ from simple_module_hosting import Settings, create_app
8
+ from simple_module_hosting.logging import setup_logging
9
+
10
+ settings = Settings()
11
+
12
+ setup_logging(
13
+ level=settings.log_level,
14
+ json_format=settings.log_format == "json",
15
+ )
16
+
17
+ app = create_app(settings)
18
+
19
+ if __name__ == "__main__":
20
+ import uvicorn
21
+
22
+ uvicorn.run(
23
+ "main:app",
24
+ host="0.0.0.0",
25
+ port=8000,
26
+ reload=settings.is_development,
27
+ )
@@ -0,0 +1,80 @@
1
+ """Alembic environment for a SimpleModule host.
2
+
3
+ Discovery + metadata aggregation lives in `simple_module_db.migrations` so
4
+ this file stays small and identical across all hosts. Don't add module-
5
+ specific imports here — install the module package and re-run autogenerate.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from logging.config import fileConfig
12
+
13
+ from alembic import context
14
+ from simple_module_db import build_module_metadata, make_include_object, render_item
15
+ from simple_module_hosting.settings import Settings
16
+ from sqlalchemy import engine_from_config, pool
17
+
18
+ logger = logging.getLogger("alembic.env")
19
+
20
+ config = context.config
21
+
22
+ if config.config_file_name is not None:
23
+ fileConfig(config.config_file_name)
24
+
25
+ target_metadata = build_module_metadata()
26
+ include_object = make_include_object(target_metadata)
27
+
28
+
29
+ def _get_url() -> str:
30
+ """Read database URL from settings, convert async to sync driver."""
31
+ settings = Settings()
32
+ url = settings.database_url
33
+ url = url.replace("+aiosqlite", "")
34
+ url = url.replace("+asyncpg", "+psycopg2")
35
+ return url
36
+
37
+
38
+ def run_migrations_offline() -> None:
39
+ """Run migrations in 'offline' mode — generates SQL without a live DB."""
40
+ url = _get_url()
41
+ context.configure(
42
+ url=url,
43
+ target_metadata=target_metadata,
44
+ literal_binds=True,
45
+ dialect_opts={"paramstyle": "named"},
46
+ include_object=include_object,
47
+ render_item=render_item,
48
+ )
49
+
50
+ with context.begin_transaction():
51
+ context.run_migrations()
52
+
53
+
54
+ def run_migrations_online() -> None:
55
+ """Run migrations against a live database."""
56
+ configuration = config.get_section(config.config_ini_section, {})
57
+ configuration["sqlalchemy.url"] = _get_url()
58
+
59
+ connectable = engine_from_config(
60
+ configuration,
61
+ prefix="sqlalchemy.",
62
+ poolclass=pool.NullPool,
63
+ )
64
+
65
+ with connectable.connect() as connection:
66
+ context.configure(
67
+ connection=connection,
68
+ target_metadata=target_metadata,
69
+ include_object=include_object,
70
+ render_item=render_item,
71
+ )
72
+
73
+ with context.begin_transaction():
74
+ context.run_migrations()
75
+
76
+
77
+ if context.is_offline_mode():
78
+ run_migrations_offline()
79
+ else:
80
+ run_migrations_online()
@@ -0,0 +1,26 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+ ${imports if imports else ""}
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = ${repr(up_revision)}
16
+ down_revision: str | None = ${repr(down_revision)}
17
+ branch_labels: str | Sequence[str] | None = ${repr(branch_labels)}
18
+ depends_on: str | Sequence[str] | None = ${repr(depends_on)}
19
+
20
+
21
+ def upgrade() -> None:
22
+ ${upgrades if upgrades else "pass"}
23
+
24
+
25
+ def downgrade() -> None:
26
+ ${downgrades if downgrades else "pass"}
@@ -0,0 +1 @@
1
+ # Keep this directory in git
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "{{HOST_NAME}}"
3
+ version = "0.1.0"
4
+ description = "SimpleModule host application"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "simple_module_core>=1.0,<2.0",
8
+ "simple_module_db>=1.0,<2.0",
9
+ "simple_module_hosting>=1.0,<2.0",
10
+ "alembic>=1.13",
11
+ "uvicorn[standard]>=0.34",
12
+ {{MODULE_DEPS}}
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>SimpleModule</title>
7
+ {% inertia_head %}
8
+ </head>
9
+ <body class="antialiased">
10
+ {% inertia_body %}
11
+ </body>
12
+ </html>
@@ -0,0 +1,32 @@
1
+ # Fast feedback for pull requests and main-branch pushes.
2
+ # No publishing here — see publish.yml.
3
+
4
+ name: CI
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v3
19
+ with:
20
+ version: latest
21
+
22
+ - name: Set up Python
23
+ run: uv python install 3.12
24
+
25
+ - name: Install project + dev deps
26
+ run: uv sync --extra dev
27
+
28
+ - name: Lint
29
+ run: uv run ruff check .
30
+
31
+ - name: Tests
32
+ run: uv run pytest
@@ -0,0 +1,52 @@
1
+ # Publish simple_module_{{PACKAGE_NAME}} to PyPI on tagged releases.
2
+ #
3
+ # Setup (one-time, in the PyPI project settings → "Publishing"):
4
+ # 1. Create the project on https://pypi.org/manage/account/publishing/
5
+ # with: PyPI project name = simple_module_{{PACKAGE_NAME}}
6
+ # Owner = <your GitHub org/user>
7
+ # Repository name = <this repo's name>
8
+ # Workflow filename = publish.yml
9
+ # Environment = pypi (optional but recommended)
10
+ # 2. In GitHub: Settings → Environments → New environment "pypi"
11
+ # with required reviewers if you want a manual approval gate.
12
+ #
13
+ # After setup, pushing a tag like `v0.1.0` builds the wheel + sdist and
14
+ # uploads via OIDC — no API token needs to exist anywhere.
15
+
16
+ name: Publish
17
+
18
+ on:
19
+ push:
20
+ tags:
21
+ - "v*"
22
+
23
+ jobs:
24
+ build-and-publish:
25
+ runs-on: ubuntu-latest
26
+ environment: pypi
27
+ permissions:
28
+ id-token: write # required for PyPI trusted publishing
29
+ contents: read
30
+
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+
34
+ - name: Install uv
35
+ uses: astral-sh/setup-uv@v3
36
+ with:
37
+ version: latest
38
+
39
+ - name: Set up Python
40
+ run: uv python install 3.12
41
+
42
+ - name: Install project + dev deps
43
+ run: uv sync --extra dev
44
+
45
+ - name: Run tests
46
+ run: uv run pytest
47
+
48
+ - name: Build distribution
49
+ run: uv build
50
+
51
+ - name: Publish to PyPI
52
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ *.db
8
+ *.sqlite3
9
+ .env
10
+
11
+ # Built frontend assets — shipped inside the wheel via force-include but
12
+ # NOT source-controlled. Re-run `npm run build` (or `vite build`) before
13
+ # `uv build` to repopulate.
14
+ */static/dist/
@@ -0,0 +1,82 @@
1
+ # simple_module_{{PACKAGE_NAME}}
2
+
3
+ The `{{MODULE_NAME}}` module for SimpleModule hosts.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install simple_module_{{PACKAGE_NAME}}
9
+ ```
10
+
11
+ A host that installs this package picks it up automatically via
12
+ `entry_points`. No additional wiring is required.
13
+
14
+ ## Development
15
+
16
+ ```bash
17
+ uv sync --extra dev
18
+ uv run pytest
19
+ ```
20
+
21
+ ## Frontend assets (optional)
22
+
23
+ If this module ships TSX pages:
24
+
25
+ 1. Drop them in `{{PACKAGE_NAME}}/pages/` (the scaffold created the
26
+ directory). One file per Inertia page — e.g. `Browse.tsx`,
27
+ `Detail.tsx`.
28
+ 2. Build them with your bundler of choice (Vite/esbuild/etc.) into
29
+ `{{PACKAGE_NAME}}/static/dist/`. This directory is **gitignored** —
30
+ only the wheel carries it.
31
+ 3. Before releasing (`uv build`), rebuild the assets. Your
32
+ `publish.yml` CI should run the bundler before `uv build`; add
33
+ a step after "Install project + dev deps" such as
34
+ `npm ci && npm run build`.
35
+
36
+ Hosts mount the bundle automatically at
37
+ `/modules/{{MODULE_SLUG}}/static` — the generated
38
+ `{{MODULE_NAME}}Module.static_mounts()` method returns the directory
39
+ when it exists, an empty dict otherwise (so dev without a build step
40
+ doesn't fail).
41
+
42
+ ## Continuous integration
43
+
44
+ Two workflows live under `.github/workflows/`:
45
+
46
+ - **`ci.yml`** — runs on every push to `main` and on every pull request.
47
+ Installs deps, runs `ruff check`, then `pytest`.
48
+ - **`publish.yml`** — runs only when a tag matching `v*` is pushed.
49
+ Re-runs tests, builds the wheel + sdist, and uploads to PyPI via
50
+ trusted publishing (no API token).
51
+
52
+ ## Publishing
53
+
54
+ One-time PyPI setup (do this before your first release):
55
+
56
+ 1. Create the project's Trusted Publisher entry at
57
+ <https://pypi.org/manage/account/publishing/>:
58
+ - PyPI project name: `simple_module_{{PACKAGE_NAME}}`
59
+ - Owner / Repository: your GitHub org + this repo
60
+ - Workflow filename: `publish.yml`
61
+ - Environment (recommended): `pypi`
62
+ 2. On GitHub → Settings → Environments, create `pypi`. Add required
63
+ reviewers if you want a manual approval gate before every release.
64
+
65
+ Every release after that is three commands:
66
+
67
+ ```bash
68
+ # 1) bump the version in pyproject.toml, commit
69
+ # 2) tag
70
+ git tag v0.2.0
71
+ git push --tags
72
+ # 3) done — the publish workflow uploads to PyPI
73
+ ```
74
+
75
+ No `PYPI_API_TOKEN` secret exists anywhere. Trusted publishing mints a
76
+ short-lived OIDC token scoped to this repo + workflow + environment.
77
+
78
+ ## API-version contract
79
+
80
+ The `Meta.requires_framework` field declares which `simple_module_core`
81
+ versions this module supports. Update the spec on each framework major
82
+ bump after verifying compatibility.
@@ -0,0 +1,11 @@
1
+ """API routes for the {{MODULE_NAME}} module."""
2
+
3
+ from fastapi import APIRouter
4
+
5
+ router = APIRouter()
6
+
7
+
8
+ @router.get("/")
9
+ async def list_items() -> dict:
10
+ """Placeholder endpoint. Replace with real resources."""
11
+ return {"module": "{{MODULE_NAME}}", "items": []}
@@ -0,0 +1,46 @@
1
+ """{{MODULE_NAME}} module — generated by `sm create-module`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.resources
6
+ from pathlib import Path
7
+
8
+ from fastapi import APIRouter, FastAPI
9
+ from simple_module_core import ModuleBase, ModuleMeta
10
+
11
+
12
+ class {{MODULE_NAME}}Module(ModuleBase):
13
+ meta = ModuleMeta(
14
+ name="{{MODULE_NAME}}",
15
+ route_prefix="/api/{{MODULE_SLUG}}",
16
+ view_prefix="/{{MODULE_SLUG}}",
17
+ depends_on=[],
18
+ version="0.1.0",
19
+ requires_framework=">=1.0,<2.0",
20
+ )
21
+
22
+ def register_routes(self, api_router: APIRouter, view_router: APIRouter) -> None:
23
+ from {{PACKAGE_NAME}}.endpoints.api import router as api
24
+
25
+ api_router.include_router(api)
26
+
27
+ def register_settings(self, app: FastAPI) -> None:
28
+ from {{PACKAGE_NAME}}.services import {{MODULE_NAME}}Services
29
+ from {{PACKAGE_NAME}}.settings import {{MODULE_NAME}}Settings
30
+
31
+ app.state.{{PACKAGE_NAME}} = {{MODULE_NAME}}Services(settings={{MODULE_NAME}}Settings())
32
+
33
+ def static_mounts(self) -> dict[str, Path]:
34
+ """Expose pre-built frontend assets when they exist.
35
+
36
+ Returns an empty dict when ``static/dist/`` hasn't been built yet,
37
+ so ``uv run pytest`` works during development. In production, the
38
+ wheel ships the built bundle via ``force-include`` in
39
+ ``pyproject.toml`` and the host mounts it at
40
+ ``/modules/{{MODULE_SLUG}}/static``.
41
+ """
42
+ pkg_root = Path(str(importlib.resources.files("{{PACKAGE_NAME}}")))
43
+ dist = pkg_root / "static" / "dist"
44
+ if not dist.is_dir():
45
+ return {}
46
+ return {"/modules/{{MODULE_SLUG}}/static": dist}
@@ -0,0 +1 @@
1
+ # Keep this directory; TSX pages go here (e.g. Browse.tsx, Detail.tsx).
@@ -0,0 +1,22 @@
1
+ """Module-scoped state container.
2
+
3
+ Stored as ``app.state.{{PACKAGE_NAME}}`` by
4
+ :meth:`{{MODULE_NAME}}Module.register_settings`.
5
+
6
+ Not frozen — ``on_startup`` may set fields that depend on the DB or
7
+ other framework services. Convention: set once during boot, treat as
8
+ read-only after.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+
15
+ from {{PACKAGE_NAME}}.settings import {{MODULE_NAME}}Settings
16
+
17
+
18
+ @dataclass
19
+ class {{MODULE_NAME}}Services:
20
+ """{{MODULE_NAME}} module singletons."""
21
+
22
+ settings: {{MODULE_NAME}}Settings
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@simple-module-py/{{MODULE_SLUG}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "Frontend assets for the {{MODULE_NAME}} module",
6
+ "peerDependencies": {
7
+ "react": "^19.0.0",
8
+ "react-dom": "^19.0.0",
9
+ "@inertiajs/react": "^2.0.0",
10
+ "@simple-module-py/ui": "*"
11
+ },
12
+ "devDependencies": {
13
+ "@simple-module-py/tsconfig": "*"
14
+ },
15
+ "dependencies": {}
16
+ }
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "simple_module_{{PACKAGE_NAME}}"
3
+ version = "0.1.0"
4
+ description = "{{MODULE_NAME}} module for SimpleModule hosts"
5
+ requires-python = ">=3.12"
6
+ dependencies = [
7
+ "simple_module_core>=1.0,<2.0",
8
+ "simple_module_db>=1.0,<2.0",
9
+ "simple_module_hosting>=1.0,<2.0",
10
+ "sqlalchemy>=2.0",
11
+ ]
12
+
13
+ [project.entry-points.simple_module]
14
+ {{PACKAGE_NAME}} = "{{PACKAGE_NAME}}.module:{{MODULE_NAME}}Module"
15
+
16
+ [project.optional-dependencies]
17
+ dev = [
18
+ "pytest>=8.0",
19
+ "pytest-asyncio>=0.24",
20
+ # Shared fixtures (fake_event_bus, build_test_app, etc.) for testing
21
+ # modules in isolation. The pytest11 entry_point auto-registers them.
22
+ "simple_module_testing>=0.1,<1.0",
23
+ ]
24
+
25
+ [build-system]
26
+ requires = ["hatchling"]
27
+ build-backend = "hatchling.build"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["{{PACKAGE_NAME}}"]
31
+
32
+ # Ship the built frontend bundle + per-module JS dep manifest inside the wheel.
33
+ # static/dist/ is gitignored, so force-include picks up the working-tree build
34
+ # during `uv build` — run your bundler (vite/esbuild) first. package.json lives
35
+ # at the module root so npm workspaces see it; copying it into <pkg>/ lets the
36
+ # host discover JS deps via importlib.resources after a pip install.
37
+ [tool.hatch.build.targets.wheel.force-include]
38
+ "{{PACKAGE_NAME}}/static/dist" = "{{PACKAGE_NAME}}/static/dist"
39
+ "package.json" = "{{PACKAGE_NAME}}/package.json"
@@ -0,0 +1,27 @@
1
+ """Smoke tests for the {{MODULE_NAME}} module.
2
+
3
+ The ``build_test_app`` and ``fake_event_bus`` fixtures come from the
4
+ ``simple_module_testing`` pytest plugin (registered via entry_points when
5
+ that package is installed). No conftest.py is required.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from {{PACKAGE_NAME}}.module import {{MODULE_NAME}}Module
11
+
12
+
13
+ class TestMeta:
14
+ def test_meta_name(self):
15
+ assert {{MODULE_NAME}}Module.meta.name == "{{MODULE_NAME}}"
16
+
17
+ def test_meta_requires_framework(self):
18
+ assert {{MODULE_NAME}}Module.meta.requires_framework is not None
19
+
20
+
21
+ class TestRoutes:
22
+ async def test_app_boots_with_module(self, build_test_app):
23
+ """Module registers cleanly into a minimal FastAPI host."""
24
+ app = build_test_app({{MODULE_NAME}}Module)
25
+ paths = {getattr(r, "path", None) for r in app.routes}
26
+ # The placeholder GET / endpoint lives under the route_prefix.
27
+ assert any(p and p.startswith("/api/{{MODULE_SLUG}}") for p in paths)
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "@simple-module-py/tsconfig/base.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["./{{PACKAGE_NAME}}/*"],
7
+ "@simple-module-py/ui/*": ["../../packages/ui/src/*"]
8
+ }
9
+ },
10
+ "include": ["{{PACKAGE_NAME}}/**/*.ts", "{{PACKAGE_NAME}}/**/*.tsx"]
11
+ }
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple_module_hosting
3
+ Version: 0.0.1
4
+ Summary: FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding
5
+ Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
+ Project-URL: Repository, https://github.com/antosubash/simple_module_python
7
+ Project-URL: Issues, https://github.com/antosubash/simple_module_python/issues
8
+ Project-URL: Changelog, https://github.com/antosubash/simple_module_python/blob/main/CHANGELOG.md
9
+ Author-email: Anto Subash <antosubash@live.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: fastapi,inertia,scaffolding,simple-module,starlette,uvicorn
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Framework :: FastAPI
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: click>=8.1
25
+ Requires-Dist: fastapi-inertia>=1.0
26
+ Requires-Dist: fastapi>=0.115
27
+ Requires-Dist: httpx>=0.27
28
+ Requires-Dist: jinja2>=3.1
29
+ Requires-Dist: simple-module-core==0.0.1
30
+ Requires-Dist: simple-module-db==0.0.1
31
+ Requires-Dist: starlette>=0.44
32
+ Requires-Dist: tomlkit>=0.13
33
+ Requires-Dist: uvicorn[standard]>=0.34
34
+ Description-Content-Type: text/markdown
35
+
36
+ # simple_module_hosting
37
+
38
+ FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, exposes the `sm` / `simple-module` CLI, and ships the project scaffolder.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install simple_module_hosting
44
+ ```
45
+
46
+ For a new project, most users run the generator instead:
47
+
48
+ ```bash
49
+ uvx simple-module new my-app
50
+ ```
51
+
52
+ ## What it provides
53
+
54
+ - `create_app(settings)` — returns a fully-wired `FastAPI` instance with all discovered modules registered.
55
+ - Middleware pipeline (execution order): CorrelationId → RequestLogging → SecurityHeaders → Session → `<module middleware>` → Tenant (opt-in) → Locale → InertiaLayoutData → app.
56
+ - Inertia wiring — shared props (`auth`, `menus`, `i18n`), `InertiaDep`, page-route lookup.
57
+ - CLI entry points: both `sm` and `simple-module` are installed and alias the same Click tree.
58
+ - Scaffolders — `sm create-host`, `sm create-module`, `sm new` (greenfield app with users + dashboard + permissions pre-wired), `sm gen-pages`.
59
+
60
+ ## Usage
61
+
62
+ Minimal `main.py`:
63
+
64
+ ```python
65
+ from simple_module_hosting import create_app
66
+ from simple_module_hosting.settings import Settings
67
+
68
+ settings = Settings() # reads SM_* env vars
69
+ app = create_app(settings) # discovers + registers every installed module
70
+
71
+ if __name__ == "__main__":
72
+ import uvicorn
73
+ uvicorn.run(app, host="0.0.0.0", port=8000)
74
+ ```
75
+
76
+ CLI:
77
+
78
+ ```bash
79
+ simple-module new my-app # scaffold a new project
80
+ simple-module doctor # diagnostic codes (SM001-SM017)
81
+ simple-module gen-pages # regenerate client_app/modules.generated.ts
82
+ ```
83
+
84
+ `sm` works identically to `simple-module`.
85
+
86
+ ## Depends on
87
+
88
+ - `simple_module_core`, `simple_module_db`
89
+ - `fastapi`, `fastapi-inertia`, `starlette`, `uvicorn`, `click`, `jinja2`, `httpx`
90
+
91
+ ## License
92
+
93
+ MIT — see [LICENSE](https://github.com/antosubash/simple_module_python/blob/main/LICENSE).