framework-m 0.6.9__tar.gz → 0.7.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.
- {framework_m-0.6.9 → framework_m-0.7.0}/.gitignore +7 -8
- {framework_m-0.6.9 → framework_m-0.7.0}/CHANGELOG.md +11 -1
- {framework_m-0.6.9 → framework_m-0.7.0}/PKG-INFO +2 -2
- {framework_m-0.6.9 → framework_m-0.7.0}/pyproject.toml +2 -2
- framework_m-0.6.9/examples/api_example.py +0 -73
- framework_m-0.6.9/examples/run_server.py +0 -136
- {framework_m-0.6.9 → framework_m-0.7.0}/LICENSE +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/README.md +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/alembic/README +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/alembic/env.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/alembic/script.py.mako +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/alembic.ini +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/docs/ports.md +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/src/framework_m/__init__.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/src/framework_m/cli/__init__.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/src/framework_m/cli/main.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/src/framework_m/py.typed +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/adapters/auth/test_oauth_adapter.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/adapters/read_model/test_projector.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/architecture/fixtures/ci_dummy_index.html +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/architecture/test_frontend_bootstrap.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/architecture/test_package_separation.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/conftest.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/core/interfaces/test_read_model.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/core/interfaces/test_report_engine.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/helpers/__init__.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/__init__.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_api_endpoints.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_app_ports.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_child_table_integration.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_crud_flow.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_jobs_events_webhooks.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_migration_flow.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_override_migration.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_postgres_flow.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_real_http_client.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/integration/test_workflow_lifecycle.py +0 -0
- {framework_m-0.6.9 → framework_m-0.7.0}/tests/test_meta.py +0 -0
|
@@ -114,15 +114,14 @@ yarn-error.log*
|
|
|
114
114
|
*.sublime-workspace
|
|
115
115
|
|
|
116
116
|
# Generated Documentation
|
|
117
|
-
docs/
|
|
118
|
-
docs/
|
|
119
|
-
docs/user/generated/
|
|
117
|
+
docs/reference/generated/
|
|
118
|
+
docs/reference/generated-core/
|
|
120
119
|
docs/machine/
|
|
121
|
-
docs/
|
|
122
|
-
docs/
|
|
123
|
-
docs/
|
|
124
|
-
docs/
|
|
125
|
-
docs/
|
|
120
|
+
docs/architecture/09_architecture_decisions/index.md
|
|
121
|
+
docs/architecture/09_architecture_decisions/_sidebar.json
|
|
122
|
+
docs/governance/index.md
|
|
123
|
+
docs/governance/_sidebar.json
|
|
124
|
+
docs/governance/_category_.json
|
|
126
125
|
|
|
127
126
|
# Website
|
|
128
127
|
website/.docusaurus/
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
## framework-m v0.7.0
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- implement phase 14 (ca41d3a)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- update docs and build script (181f55b)
|
|
10
|
+
|
|
11
|
+
**framework-m** v0.6.10: internal dependency bump due to **framework-m-studio** update**framework-m** v0.6.9: internal dependency bump due to **framework-m-standard** update## framework-m v0.6.8
|
|
2
12
|
|
|
3
13
|
### Bug Fixes
|
|
4
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: framework-m
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A modular, metadata-driven business application framework
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/castlecraft/framework-m
|
|
6
6
|
Project-URL: Documentation, https://gitlab.com/castlecraft/framework-m#readme
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
19
|
Classifier: Typing :: Typed
|
|
20
20
|
Requires-Python: >=3.12
|
|
21
21
|
Requires-Dist: framework-m-core>=0.11.4
|
|
22
|
-
Requires-Dist: framework-m-standard>=0.
|
|
22
|
+
Requires-Dist: framework-m-standard>=0.8.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
25
25
|
# Framework M
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "framework-m"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "A modular, metadata-driven business application framework"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Apache-2.0" }
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
|
|
23
23
|
dependencies = [
|
|
24
24
|
"framework-m-core>=0.11.4",
|
|
25
|
-
"framework-m-standard>=0.
|
|
25
|
+
"framework-m-standard>=0.8.0",
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[tool.uv.sources]
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""Test script to call the API with httpx.
|
|
2
|
-
|
|
3
|
-
Usage:
|
|
4
|
-
1. First start the server:
|
|
5
|
-
cd /Users/ansh/Desktop/CastleCraft/m/libs/framework-m
|
|
6
|
-
uv run python scripts/run_server.py
|
|
7
|
-
|
|
8
|
-
2. Then in another terminal, run this script:
|
|
9
|
-
cd /Users/ansh/Desktop/CastleCraft/m/libs/framework-m
|
|
10
|
-
uv run python scripts/test_api.py
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import asyncio
|
|
14
|
-
|
|
15
|
-
import httpx
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def test_api() -> None:
|
|
19
|
-
"""Test the API endpoints."""
|
|
20
|
-
base_url = "http://localhost:8000"
|
|
21
|
-
|
|
22
|
-
async with httpx.AsyncClient() as client:
|
|
23
|
-
print("=" * 60)
|
|
24
|
-
print("Testing Framework M API")
|
|
25
|
-
print("=" * 60)
|
|
26
|
-
|
|
27
|
-
# Test 1: List todos (should be empty)
|
|
28
|
-
print("\n1. GET /api/v1/Todo (list)")
|
|
29
|
-
response = await client.get(f"{base_url}/api/v1/Todo")
|
|
30
|
-
print(f" Status: {response.status_code}")
|
|
31
|
-
print(f" Response: {response.json()}")
|
|
32
|
-
|
|
33
|
-
# Test 2: Create a todo (as Employee)
|
|
34
|
-
print("\n2. POST /api/v1/Todo (create as Employee)")
|
|
35
|
-
response = await client.post(
|
|
36
|
-
f"{base_url}/api/v1/Todo",
|
|
37
|
-
json={"title": "Test Todo"},
|
|
38
|
-
headers={"x-user-id": "user123", "x-roles": "Employee"},
|
|
39
|
-
)
|
|
40
|
-
print(f" Status: {response.status_code}")
|
|
41
|
-
if response.status_code < 500:
|
|
42
|
-
print(f" Response: {response.json()}")
|
|
43
|
-
else:
|
|
44
|
-
print(f" Response: {response.text[:200]}")
|
|
45
|
-
|
|
46
|
-
# Test 3: Create a todo without permission (as Guest)
|
|
47
|
-
print("\n3. POST /api/v1/Todo (create as Guest - should fail)")
|
|
48
|
-
response = await client.post(
|
|
49
|
-
f"{base_url}/api/v1/Todo",
|
|
50
|
-
json={"title": "Test Todo"},
|
|
51
|
-
headers={"x-user-id": "guest", "x-roles": "Guest"},
|
|
52
|
-
)
|
|
53
|
-
print(f" Status: {response.status_code}")
|
|
54
|
-
if response.status_code < 500:
|
|
55
|
-
print(f" Response: {response.json()}")
|
|
56
|
-
|
|
57
|
-
# Test 4: Get metadata
|
|
58
|
-
print("\n4. GET /api/meta/Todo (metadata)")
|
|
59
|
-
response = await client.get(f"{base_url}/api/meta/Todo")
|
|
60
|
-
print(f" Status: {response.status_code}")
|
|
61
|
-
data = response.json()
|
|
62
|
-
print(f" DocType: {data.get('doctype')}")
|
|
63
|
-
print(f" Has schema: {'schema' in data}")
|
|
64
|
-
print(f" Has layout: {'layout' in data}")
|
|
65
|
-
print(f" Has permissions: {'permissions' in data}")
|
|
66
|
-
|
|
67
|
-
print("\n" + "=" * 60)
|
|
68
|
-
print("API tests complete!")
|
|
69
|
-
print("=" * 60)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if __name__ == "__main__":
|
|
73
|
-
asyncio.run(test_api())
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
"""Run the Framework M API server for manual testing.
|
|
2
|
-
|
|
3
|
-
Usage:
|
|
4
|
-
cd /Users/ansh/Desktop/CastleCraft/m/libs/framework-m
|
|
5
|
-
uv run python scripts/run_server.py
|
|
6
|
-
|
|
7
|
-
Then test with:
|
|
8
|
-
curl http://localhost:8000/api/v1/Todo
|
|
9
|
-
OR use the test_api.py script
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from typing import Any, ClassVar
|
|
13
|
-
|
|
14
|
-
import uvicorn
|
|
15
|
-
from framework_m_standard.adapters.web.meta_router import create_meta_router
|
|
16
|
-
from framework_m_standard.adapters.web.meta_routes import meta_routes_router
|
|
17
|
-
from framework_m_standard.adapters.web.rpc_routes import rpc_router
|
|
18
|
-
from litestar import Litestar, Response
|
|
19
|
-
from litestar.status_codes import HTTP_403_FORBIDDEN
|
|
20
|
-
from litestar.types import ASGIApp, Receive, Scope, Send
|
|
21
|
-
|
|
22
|
-
from framework_m.core.domain.base_doctype import BaseDocType, Field
|
|
23
|
-
from framework_m.core.exceptions import PermissionDeniedError
|
|
24
|
-
from framework_m.core.interfaces.auth_context import UserContext
|
|
25
|
-
from framework_m.core.registry import MetaRegistry
|
|
26
|
-
|
|
27
|
-
# =============================================================================
|
|
28
|
-
# Test DocType for Manual Testing
|
|
29
|
-
# =============================================================================
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class Todo(BaseDocType):
|
|
33
|
-
"""Todo DocType for testing."""
|
|
34
|
-
|
|
35
|
-
title: str = Field(title="Title", description="Todo title")
|
|
36
|
-
completed: bool = Field(default=False)
|
|
37
|
-
|
|
38
|
-
class Meta:
|
|
39
|
-
api_resource: ClassVar[bool] = True
|
|
40
|
-
requires_auth: ClassVar[bool] = False # Allow guest for easy testing
|
|
41
|
-
apply_rls: ClassVar[bool] = False
|
|
42
|
-
permissions: ClassVar[dict[str, list[str]]] = {
|
|
43
|
-
"read": ["Guest", "Employee", "Manager", "Admin"],
|
|
44
|
-
"write": ["Employee", "Manager", "Admin"],
|
|
45
|
-
"create": ["Employee", "Manager", "Admin"],
|
|
46
|
-
"delete": ["Admin"],
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# =============================================================================
|
|
51
|
-
# Auth Middleware
|
|
52
|
-
# =============================================================================
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def create_auth_middleware(app: ASGIApp) -> ASGIApp:
|
|
56
|
-
"""Create middleware that parses x-user-id and x-roles headers."""
|
|
57
|
-
|
|
58
|
-
async def middleware(scope: Scope, receive: Receive, send: Send) -> None:
|
|
59
|
-
if scope["type"] in ("http", "websocket"):
|
|
60
|
-
headers = dict(scope.get("headers", []))
|
|
61
|
-
user_id = headers.get(b"x-user-id", b"anonymous").decode()
|
|
62
|
-
roles_str = headers.get(b"x-roles", b"Guest").decode()
|
|
63
|
-
roles = [r.strip() for r in roles_str.split(",")]
|
|
64
|
-
|
|
65
|
-
# Set both the UserContext AND the individual attributes
|
|
66
|
-
# The meta_router looks for user_id, user_roles, user_teams
|
|
67
|
-
scope["state"]["user"] = UserContext(
|
|
68
|
-
id=user_id,
|
|
69
|
-
email=f"{user_id}@example.com",
|
|
70
|
-
roles=roles,
|
|
71
|
-
)
|
|
72
|
-
scope["state"]["user_id"] = user_id
|
|
73
|
-
scope["state"]["user_roles"] = roles
|
|
74
|
-
scope["state"]["user_teams"] = []
|
|
75
|
-
await app(scope, receive, send)
|
|
76
|
-
|
|
77
|
-
return middleware
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def permission_denied_handler(
|
|
81
|
-
request: Any, exc: PermissionDeniedError
|
|
82
|
-
) -> Response[dict[str, str]]:
|
|
83
|
-
"""Handle permission denied errors."""
|
|
84
|
-
return Response(
|
|
85
|
-
content={"error": "PermissionDenied", "message": str(exc)},
|
|
86
|
-
status_code=HTTP_403_FORBIDDEN,
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# =============================================================================
|
|
91
|
-
# Server Setup
|
|
92
|
-
# =============================================================================
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def create_test_app() -> Litestar:
|
|
96
|
-
"""Create the test application."""
|
|
97
|
-
# Register DocType
|
|
98
|
-
registry = MetaRegistry.get_instance()
|
|
99
|
-
registry.clear()
|
|
100
|
-
registry.register_doctype(Todo)
|
|
101
|
-
|
|
102
|
-
# Create routers
|
|
103
|
-
crud_router = create_meta_router()
|
|
104
|
-
|
|
105
|
-
# Create app with middleware and exception handlers
|
|
106
|
-
app = Litestar(
|
|
107
|
-
route_handlers=[crud_router, rpc_router, meta_routes_router],
|
|
108
|
-
middleware=[create_auth_middleware],
|
|
109
|
-
exception_handlers={PermissionDeniedError: permission_denied_handler},
|
|
110
|
-
debug=True,
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
return app
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if __name__ == "__main__":
|
|
117
|
-
print("Starting Framework M API server...")
|
|
118
|
-
print("Server running at: http://localhost:8000")
|
|
119
|
-
print("")
|
|
120
|
-
print("Available endpoints:")
|
|
121
|
-
print(" GET /api/v1/Todo - List todos")
|
|
122
|
-
print(" POST /api/v1/Todo - Create todo")
|
|
123
|
-
print(" GET /api/v1/Todo/{id} - Get todo")
|
|
124
|
-
print(" PUT /api/v1/Todo/{id} - Update todo")
|
|
125
|
-
print(" DELETE /api/v1/Todo/{id} - Delete todo")
|
|
126
|
-
print(" GET /api/meta/Todo - Get Todo metadata")
|
|
127
|
-
print("")
|
|
128
|
-
print("Authentication headers:")
|
|
129
|
-
print(" x-user-id: user123")
|
|
130
|
-
print(" x-roles: Employee,Manager")
|
|
131
|
-
print("")
|
|
132
|
-
print("Press Ctrl+C to stop the server")
|
|
133
|
-
print("")
|
|
134
|
-
|
|
135
|
-
app = create_test_app()
|
|
136
|
-
uvicorn.run(app, host="0.0.0.0", port=8000) # nosec B104
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|