framework-m 0.5.2__tar.gz → 0.6.1__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 (194) hide show
  1. {framework_m-0.5.2 → framework_m-0.6.1}/.gitignore +7 -1
  2. {framework_m-0.5.2 → framework_m-0.6.1}/CHANGELOG.md +21 -0
  3. {framework_m-0.5.2 → framework_m-0.6.1}/PKG-INFO +4 -4
  4. {framework_m-0.5.2 → framework_m-0.6.1}/README.md +1 -1
  5. {framework_m-0.5.2 → framework_m-0.6.1}/alembic/env.py +1 -1
  6. {framework_m-0.5.2 → framework_m-0.6.1}/examples/run_server.py +4 -4
  7. {framework_m-0.5.2 → framework_m-0.6.1}/pyproject.toml +4 -4
  8. framework_m-0.6.1/tests/adapters/auth/test_oauth_adapter.py +137 -0
  9. {framework_m-0.5.2 → framework_m-0.6.1}/tests/adapters/read_model/test_projector.py +12 -13
  10. {framework_m-0.5.2 → framework_m-0.6.1}/tests/conftest.py +13 -4
  11. {framework_m-0.5.2 → framework_m-0.6.1}/tests/core/interfaces/test_read_model.py +2 -3
  12. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_api_endpoints.py +11 -12
  13. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_child_table_integration.py +1 -2
  14. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_crud_flow.py +6 -6
  15. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_jobs_events_webhooks.py +20 -18
  16. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_migration_flow.py +5 -5
  17. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_override_migration.py +2 -3
  18. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_postgres_flow.py +8 -8
  19. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_real_http_client.py +29 -9
  20. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_workflow_lifecycle.py +4 -5
  21. framework_m-0.6.1/tests/test_meta.py +29 -0
  22. framework_m-0.5.2/src/framework_m/adapters/__init__.py +0 -9
  23. framework_m-0.5.2/src/framework_m/adapters/audit/__init__.py +0 -5
  24. framework_m-0.5.2/src/framework_m/adapters/audit/database_audit.py +0 -7
  25. framework_m-0.5.2/src/framework_m/adapters/audit/file_audit.py +0 -5
  26. framework_m-0.5.2/src/framework_m/adapters/auth/__init__.py +0 -5
  27. framework_m-0.5.2/src/framework_m/adapters/auth/citadel_policy.py +0 -5
  28. framework_m-0.5.2/src/framework_m/adapters/auth/federated_identity.py +0 -7
  29. framework_m-0.5.2/src/framework_m/adapters/auth/local_identity.py +0 -5
  30. framework_m-0.5.2/src/framework_m/adapters/auth/rbac_permission.py +0 -7
  31. framework_m-0.5.2/src/framework_m/adapters/auth/session_store.py +0 -421
  32. framework_m-0.5.2/src/framework_m/adapters/auth/strategies.py +0 -5
  33. framework_m-0.5.2/src/framework_m/adapters/cache/__init__.py +0 -5
  34. framework_m-0.5.2/src/framework_m/adapters/cache/redis_cache.py +0 -5
  35. framework_m-0.5.2/src/framework_m/adapters/db/__init__.py +0 -2
  36. framework_m-0.5.2/src/framework_m/adapters/db/connection.py +0 -5
  37. framework_m-0.5.2/src/framework_m/adapters/db/field_registry.py +0 -5
  38. framework_m-0.5.2/src/framework_m/adapters/db/generic_repository.py +0 -7
  39. framework_m-0.5.2/src/framework_m/adapters/db/migration.py +0 -5
  40. framework_m-0.5.2/src/framework_m/adapters/db/outbox_repository.py +0 -7
  41. framework_m-0.5.2/src/framework_m/adapters/db/repository_factory.py +0 -7
  42. framework_m-0.5.2/src/framework_m/adapters/db/schema_mapper.py +0 -5
  43. framework_m-0.5.2/src/framework_m/adapters/db/session.py +0 -5
  44. framework_m-0.5.2/src/framework_m/adapters/db/sqlalchemy_adapter.py +0 -7
  45. framework_m-0.5.2/src/framework_m/adapters/db/table_registry.py +0 -5
  46. framework_m-0.5.2/src/framework_m/adapters/email/__init__.py +0 -5
  47. framework_m-0.5.2/src/framework_m/adapters/email/processor.py +0 -5
  48. framework_m-0.5.2/src/framework_m/adapters/email/queue_adapter.py +0 -5
  49. framework_m-0.5.2/src/framework_m/adapters/email/sender_adapter.py +0 -7
  50. framework_m-0.5.2/src/framework_m/adapters/events/__init__.py +0 -5
  51. framework_m-0.5.2/src/framework_m/adapters/events/inmemory_event_bus.py +0 -7
  52. framework_m-0.5.2/src/framework_m/adapters/events/nats_event_bus.py +0 -7
  53. framework_m-0.5.2/src/framework_m/adapters/factory.py +0 -5
  54. framework_m-0.5.2/src/framework_m/adapters/i18n/__init__.py +0 -5
  55. framework_m-0.5.2/src/framework_m/adapters/jobs/__init__.py +0 -5
  56. framework_m-0.5.2/src/framework_m/adapters/jobs/job_logger.py +0 -5
  57. framework_m-0.5.2/src/framework_m/adapters/jobs/outbox_worker.py +0 -5
  58. framework_m-0.5.2/src/framework_m/adapters/jobs/schedules.py +0 -5
  59. framework_m-0.5.2/src/framework_m/adapters/jobs/taskiq_adapter.py +0 -5
  60. framework_m-0.5.2/src/framework_m/adapters/print/__init__.py +0 -5
  61. framework_m-0.5.2/src/framework_m/adapters/print/gotenberg_adapter.py +0 -7
  62. framework_m-0.5.2/src/framework_m/adapters/print/jinja_adapter.py +0 -5
  63. framework_m-0.5.2/src/framework_m/adapters/read_model/__init__.py +0 -10
  64. framework_m-0.5.2/src/framework_m/adapters/read_model/projector.py +0 -132
  65. framework_m-0.5.2/src/framework_m/adapters/socket/__init__.py +0 -5
  66. framework_m-0.5.2/src/framework_m/adapters/socket/nats_socket.py +0 -5
  67. framework_m-0.5.2/src/framework_m/adapters/storage/__init__.py +0 -5
  68. framework_m-0.5.2/src/framework_m/adapters/storage/local.py +0 -5
  69. framework_m-0.5.2/src/framework_m/adapters/storage/memory.py +0 -5
  70. framework_m-0.5.2/src/framework_m/adapters/storage/s3.py +0 -5
  71. framework_m-0.5.2/src/framework_m/adapters/tenant.py +0 -5
  72. framework_m-0.5.2/src/framework_m/adapters/web/__init__.py +0 -4
  73. framework_m-0.5.2/src/framework_m/adapters/web/activity_routes.py +0 -5
  74. framework_m-0.5.2/src/framework_m/adapters/web/api_key_routes.py +0 -5
  75. framework_m-0.5.2/src/framework_m/adapters/web/app.py +0 -2
  76. framework_m-0.5.2/src/framework_m/adapters/web/auth_middleware.py +0 -5
  77. framework_m-0.5.2/src/framework_m/adapters/web/auth_router.py +0 -5
  78. framework_m-0.5.2/src/framework_m/adapters/web/auth_routes.py +0 -5
  79. framework_m-0.5.2/src/framework_m/adapters/web/dtos.py +0 -5
  80. framework_m-0.5.2/src/framework_m/adapters/web/error_handler.py +0 -5
  81. framework_m-0.5.2/src/framework_m/adapters/web/file_routes.py +0 -5
  82. framework_m-0.5.2/src/framework_m/adapters/web/job_router.py +0 -5
  83. framework_m-0.5.2/src/framework_m/adapters/web/magic_link_routes.py +0 -7
  84. framework_m-0.5.2/src/framework_m/adapters/web/meta_router.py +0 -5
  85. framework_m-0.5.2/src/framework_m/adapters/web/meta_router_extended.py +0 -6
  86. framework_m-0.5.2/src/framework_m/adapters/web/meta_routes.py +0 -5
  87. framework_m-0.5.2/src/framework_m/adapters/web/metadata_router.py +0 -5
  88. framework_m-0.5.2/src/framework_m/adapters/web/middleware/__init__.py +0 -5
  89. framework_m-0.5.2/src/framework_m/adapters/web/middleware/locale.py +0 -7
  90. framework_m-0.5.2/src/framework_m/adapters/web/middleware_registry.py +0 -7
  91. framework_m-0.5.2/src/framework_m/adapters/web/notification_routes.py +0 -7
  92. framework_m-0.5.2/src/framework_m/adapters/web/oauth_routes.py +0 -5
  93. framework_m-0.5.2/src/framework_m/adapters/web/print_routes.py +0 -5
  94. framework_m-0.5.2/src/framework_m/adapters/web/rpc_routes.py +0 -5
  95. framework_m-0.5.2/src/framework_m/adapters/web/session_routes.py +0 -5
  96. framework_m-0.5.2/src/framework_m/adapters/web/settings_routes.py +0 -5
  97. framework_m-0.5.2/src/framework_m/adapters/web/share_routes.py +0 -5
  98. framework_m-0.5.2/src/framework_m/adapters/web/socket.py +0 -5
  99. framework_m-0.5.2/src/framework_m/adapters/web/user_data_routes.py +0 -7
  100. framework_m-0.5.2/src/framework_m/adapters/web/workflow_router.py +0 -5
  101. framework_m-0.5.2/src/framework_m/adapters/webhooks/__init__.py +0 -5
  102. framework_m-0.5.2/src/framework_m/adapters/webhooks/delivery.py +0 -5
  103. framework_m-0.5.2/src/framework_m/adapters/webhooks/listener.py +0 -229
  104. framework_m-0.5.2/src/framework_m/adapters/workflow/__init__.py +0 -5
  105. framework_m-0.5.2/src/framework_m/adapters/workflow/internal_workflow.py +0 -7
  106. framework_m-0.5.2/src/framework_m/adapters/workflow/temporal_adapter.py +0 -7
  107. framework_m-0.5.2/src/framework_m/core/__init__.py +0 -3
  108. framework_m-0.5.2/src/framework_m/core/config.py +0 -17
  109. framework_m-0.5.2/src/framework_m/core/container.py +0 -5
  110. framework_m-0.5.2/src/framework_m/core/decorators.py +0 -5
  111. framework_m-0.5.2/src/framework_m/core/doctypes/__init__.py +0 -2
  112. framework_m-0.5.2/src/framework_m/core/doctypes/activity_log.py +0 -5
  113. framework_m-0.5.2/src/framework_m/core/doctypes/api_key.py +0 -5
  114. framework_m-0.5.2/src/framework_m/core/doctypes/custom_permission.py +0 -5
  115. framework_m-0.5.2/src/framework_m/core/doctypes/document_share.py +0 -5
  116. framework_m-0.5.2/src/framework_m/core/doctypes/email_queue.py +0 -5
  117. framework_m-0.5.2/src/framework_m/core/doctypes/error_log.py +0 -5
  118. framework_m-0.5.2/src/framework_m/core/doctypes/file.py +0 -5
  119. framework_m-0.5.2/src/framework_m/core/doctypes/job_log.py +0 -5
  120. framework_m-0.5.2/src/framework_m/core/doctypes/notification.py +0 -5
  121. framework_m-0.5.2/src/framework_m/core/doctypes/print_format.py +0 -5
  122. framework_m-0.5.2/src/framework_m/core/doctypes/recent_document.py +0 -5
  123. framework_m-0.5.2/src/framework_m/core/doctypes/scheduled_job.py +0 -5
  124. framework_m-0.5.2/src/framework_m/core/doctypes/session.py +0 -5
  125. framework_m-0.5.2/src/framework_m/core/doctypes/social_account.py +0 -5
  126. framework_m-0.5.2/src/framework_m/core/doctypes/system_settings.py +0 -5
  127. framework_m-0.5.2/src/framework_m/core/doctypes/tenant_translation.py +0 -5
  128. framework_m-0.5.2/src/framework_m/core/doctypes/todo.py +0 -5
  129. framework_m-0.5.2/src/framework_m/core/doctypes/translation.py +0 -5
  130. framework_m-0.5.2/src/framework_m/core/doctypes/user.py +0 -5
  131. framework_m-0.5.2/src/framework_m/core/doctypes/webhook.py +0 -5
  132. framework_m-0.5.2/src/framework_m/core/doctypes/webhook_log.py +0 -5
  133. framework_m-0.5.2/src/framework_m/core/doctypes/workflow.py +0 -5
  134. framework_m-0.5.2/src/framework_m/core/doctypes/workflow_state.py +0 -5
  135. framework_m-0.5.2/src/framework_m/core/doctypes/workflow_transition.py +0 -5
  136. framework_m-0.5.2/src/framework_m/core/domain/__init__.py +0 -3
  137. framework_m-0.5.2/src/framework_m/core/domain/base_controller.py +0 -3
  138. framework_m-0.5.2/src/framework_m/core/domain/base_doctype.py +0 -3
  139. framework_m-0.5.2/src/framework_m/core/domain/mixins.py +0 -17
  140. framework_m-0.5.2/src/framework_m/core/domain/naming_counter.py +0 -3
  141. framework_m-0.5.2/src/framework_m/core/domain/outbox.py +0 -3
  142. framework_m-0.5.2/src/framework_m/core/events.py +0 -5
  143. framework_m-0.5.2/src/framework_m/core/exceptions.py +0 -29
  144. framework_m-0.5.2/src/framework_m/core/interfaces/__init__.py +0 -110
  145. framework_m-0.5.2/src/framework_m/core/interfaces/audit.py +0 -5
  146. framework_m-0.5.2/src/framework_m/core/interfaces/auth_context.py +0 -5
  147. framework_m-0.5.2/src/framework_m/core/interfaces/authentication.py +0 -5
  148. framework_m-0.5.2/src/framework_m/core/interfaces/cache.py +0 -5
  149. framework_m-0.5.2/src/framework_m/core/interfaces/email_queue.py +0 -5
  150. framework_m-0.5.2/src/framework_m/core/interfaces/email_sender.py +0 -5
  151. framework_m-0.5.2/src/framework_m/core/interfaces/event_bus.py +0 -5
  152. framework_m-0.5.2/src/framework_m/core/interfaces/i18n.py +0 -5
  153. framework_m-0.5.2/src/framework_m/core/interfaces/identity.py +0 -5
  154. framework_m-0.5.2/src/framework_m/core/interfaces/job_queue.py +0 -5
  155. framework_m-0.5.2/src/framework_m/core/interfaces/notification.py +0 -5
  156. framework_m-0.5.2/src/framework_m/core/interfaces/oauth.py +0 -5
  157. framework_m-0.5.2/src/framework_m/core/interfaces/permission.py +0 -5
  158. framework_m-0.5.2/src/framework_m/core/interfaces/print.py +0 -5
  159. framework_m-0.5.2/src/framework_m/core/interfaces/repository.py +0 -5
  160. framework_m-0.5.2/src/framework_m/core/interfaces/search.py +0 -5
  161. framework_m-0.5.2/src/framework_m/core/interfaces/session.py +0 -5
  162. framework_m-0.5.2/src/framework_m/core/interfaces/socket.py +0 -5
  163. framework_m-0.5.2/src/framework_m/core/interfaces/storage.py +0 -5
  164. framework_m-0.5.2/src/framework_m/core/interfaces/tenant.py +0 -5
  165. framework_m-0.5.2/src/framework_m/core/interfaces/workflow.py +0 -5
  166. framework_m-0.5.2/src/framework_m/core/permission_lookup.py +0 -5
  167. framework_m-0.5.2/src/framework_m/core/permissions.py +0 -5
  168. framework_m-0.5.2/src/framework_m/core/pii.py +0 -5
  169. framework_m-0.5.2/src/framework_m/core/registry.py +0 -3
  170. framework_m-0.5.2/src/framework_m/core/rls.py +0 -5
  171. framework_m-0.5.2/src/framework_m/core/rpc_registry.py +0 -5
  172. framework_m-0.5.2/src/framework_m/core/security.py +0 -5
  173. framework_m-0.5.2/src/framework_m/core/services/__init__.py +0 -0
  174. framework_m-0.5.2/src/framework_m/core/services/user_manager.py +0 -5
  175. framework_m-0.5.2/src/framework_m/core/system_context.py +0 -5
  176. framework_m-0.5.2/src/framework_m/core/types/__init__.py +0 -2
  177. framework_m-0.5.2/src/framework_m/core/types/job_context.py +0 -5
  178. framework_m-0.5.2/src/framework_m/core/unit_of_work.py +0 -5
  179. {framework_m-0.5.2 → framework_m-0.6.1}/alembic/README +0 -0
  180. {framework_m-0.5.2 → framework_m-0.6.1}/alembic/script.py.mako +0 -0
  181. {framework_m-0.5.2 → framework_m-0.6.1}/alembic.ini +0 -0
  182. {framework_m-0.5.2 → framework_m-0.6.1}/docs/ports.md +0 -0
  183. {framework_m-0.5.2 → framework_m-0.6.1}/examples/api_example.py +0 -0
  184. {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/__init__.py +0 -0
  185. {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/cli/__init__.py +0 -0
  186. {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/cli/main.py +0 -0
  187. {framework_m-0.5.2 → framework_m-0.6.1}/src/framework_m/py.typed +0 -0
  188. {framework_m-0.5.2 → framework_m-0.6.1}/tests/architecture/fixtures/ci_dummy_index.html +0 -0
  189. {framework_m-0.5.2 → framework_m-0.6.1}/tests/architecture/test_frontend_bootstrap.py +0 -0
  190. {framework_m-0.5.2 → framework_m-0.6.1}/tests/architecture/test_package_separation.py +0 -0
  191. {framework_m-0.5.2 → framework_m-0.6.1}/tests/core/interfaces/test_report_engine.py +0 -0
  192. {framework_m-0.5.2 → framework_m-0.6.1}/tests/helpers/__init__.py +0 -0
  193. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/__init__.py +0 -0
  194. {framework_m-0.5.2 → framework_m-0.6.1}/tests/integration/test_app_ports.py +0 -0
@@ -113,8 +113,14 @@ yarn-error.log*
113
113
 
114
114
  # Generated Documentation
115
115
  docs/developer/generated/
116
- docs/developer/protocols/
116
+ docs/developer/generated-core/
117
+ docs/user/generated/
117
118
  docs/machine/
119
+ docs/adr/index.md
120
+ docs/adr/_sidebar.json
121
+ docs/rfcs/index.md
122
+ docs/rfcs/_sidebar.json
123
+ docs/rfcs/_category_.json
118
124
 
119
125
  # Website
120
126
  website/.docusaurus/
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## framework-m v0.6.1
4
+
5
+ ### Bug Fixes
6
+
7
+ - remove hardcoded publishConfig registry (b197c97)
8
+
9
+
10
+ ## framework-m v0.6.0
11
+
12
+ ### Features
13
+
14
+ - granular generation, flat sidebar, and monorepo sync (576fb81)
15
+ - add configurable basename and stabilize frontend tests (b57e6a4)
16
+
17
+ ### Bug Fixes
18
+
19
+ - shell integration gaps (6a31f82)
20
+ - resolve broken links in docusaurus build (a162fd1)
21
+ - correct hydration errors and refine routing logic (192dac2)
22
+
23
+
3
24
  ## framework-m v0.5.2
4
25
 
5
26
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: framework-m
3
- Version: 0.5.2
3
+ Version: 0.6.1
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
@@ -17,8 +17,8 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.12
20
- Requires-Dist: framework-m-core>=0.7.1
21
- Requires-Dist: framework-m-standard>=0.4.2
20
+ Requires-Dist: framework-m-core>=0.9.0
21
+ Requires-Dist: framework-m-standard>=0.6.0
22
22
  Description-Content-Type: text/markdown
23
23
 
24
24
  # Framework M
@@ -126,7 +126,7 @@ git clone https://gitlab.com/castlecraft/framework-m.git
126
126
  cd framework-m
127
127
 
128
128
  # Install dependencies
129
- uv sync --all-extras
129
+ uv sync --all-packages --all-extras --all-groups
130
130
 
131
131
  # Run tests
132
132
  uv run pytest
@@ -103,7 +103,7 @@ git clone https://gitlab.com/castlecraft/framework-m.git
103
103
  cd framework-m
104
104
 
105
105
  # Install dependencies
106
- uv sync --all-extras
106
+ uv sync --all-packages --all-extras --all-groups
107
107
 
108
108
  # Run tests
109
109
  uv run pytest
@@ -19,7 +19,7 @@ from alembic import context
19
19
  # Framework M imports for auto-generation support
20
20
  # Uncomment and customize based on your app:
21
21
  # from framework_m.core.registry import MetaRegistry
22
- # from framework_m.adapters.db import SchemaMapper
22
+ # from framework_m_standard.adapters.db import SchemaMapper
23
23
  # from your_app.doctypes import * # Import all DocTypes to register them
24
24
 
25
25
  # this is the Alembic Config object, which provides
@@ -12,13 +12,13 @@ Then test with:
12
12
  from typing import Any, ClassVar
13
13
 
14
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
15
18
  from litestar import Litestar, Response
16
19
  from litestar.status_codes import HTTP_403_FORBIDDEN
17
20
  from litestar.types import ASGIApp, Receive, Scope, Send
18
21
 
19
- from framework_m.adapters.web.meta_router import create_meta_router
20
- from framework_m.adapters.web.meta_routes import meta_routes_router
21
- from framework_m.adapters.web.rpc_routes import rpc_router
22
22
  from framework_m.core.domain.base_doctype import BaseDocType, Field
23
23
  from framework_m.core.exceptions import PermissionDeniedError
24
24
  from framework_m.core.interfaces.auth_context import UserContext
@@ -133,4 +133,4 @@ if __name__ == "__main__":
133
133
  print("")
134
134
 
135
135
  app = create_test_app()
136
- uvicorn.run(app, host="0.0.0.0", port=8000)
136
+ uvicorn.run(app, host="0.0.0.0", port=8000) # nosec B104
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "framework-m"
3
- version = "0.5.2"
3
+ version = "0.6.1"
4
4
  description = "A modular, metadata-driven business application framework"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -21,8 +21,8 @@ classifiers = [
21
21
  ]
22
22
 
23
23
  dependencies = [
24
- "framework-m-core>=0.7.1",
25
- "framework-m-standard>=0.4.2",
24
+ "framework-m-core>=0.9.0",
25
+ "framework-m-standard>=0.6.0",
26
26
  ]
27
27
 
28
28
  [tool.uv.sources]
@@ -39,7 +39,7 @@ dev = [
39
39
  "testcontainers>=3.7.0",
40
40
  "httpx>=0.27.0",
41
41
  "aiosqlite>=0.20.0",
42
- "framework-m-studio>=0.5.2",
42
+ "framework-m-studio>=0.7.0",
43
43
  # Release tools
44
44
  "python-semantic-release>=9.0.0",
45
45
  "build>=1.0.0",
@@ -0,0 +1,137 @@
1
+ """Tests for OAuthAdapter (Indie Mode OAuth2 via authlib).
2
+
3
+ TDD scope (work-item-20 / section 1):
4
+ - Authorization URL generation for common providers (Google/GitHub).
5
+ - Callback handling that hydrates framework models for linking/auth context.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from urllib.parse import parse_qs, urlparse
12
+
13
+ import pytest
14
+
15
+
16
+ @pytest.mark.asyncio
17
+ async def test_oauth_adapter_generates_google_authorization_url() -> None:
18
+ """OAuthAdapter should generate a valid Google authorization URL."""
19
+ from framework_m_standard.adapters.auth.oauth import OAuthAdapter
20
+
21
+ adapter = OAuthAdapter(
22
+ provider_name="google",
23
+ client_id="google-client-id",
24
+ client_secret="google-client-secret",
25
+ authorization_url="https://accounts.google.com/o/oauth2/v2/auth",
26
+ token_url="https://oauth2.googleapis.com/token",
27
+ userinfo_url="https://www.googleapis.com/oauth2/v3/userinfo",
28
+ scope="openid email profile",
29
+ )
30
+
31
+ url = await adapter.get_authorization_url(
32
+ state="state-123",
33
+ redirect_uri="https://app.example.com/api/v1/auth/oauth/google/callback",
34
+ )
35
+
36
+ parsed = urlparse(url)
37
+ query = parse_qs(parsed.query)
38
+
39
+ assert parsed.scheme == "https"
40
+ assert parsed.netloc == "accounts.google.com"
41
+ assert query["client_id"][0] == "google-client-id"
42
+ assert query["state"][0] == "state-123"
43
+ assert query["redirect_uri"][0].endswith("/oauth/google/callback")
44
+
45
+
46
+ @pytest.mark.asyncio
47
+ async def test_oauth_adapter_generates_github_authorization_url() -> None:
48
+ """OAuthAdapter should generate a valid GitHub authorization URL."""
49
+ from framework_m_standard.adapters.auth.oauth import OAuthAdapter
50
+
51
+ adapter = OAuthAdapter(
52
+ provider_name="github",
53
+ client_id="github-client-id",
54
+ client_secret="github-client-secret",
55
+ authorization_url="https://github.com/login/oauth/authorize",
56
+ token_url="https://github.com/login/oauth/access_token",
57
+ userinfo_url="https://api.github.com/user",
58
+ scope="read:user user:email",
59
+ emails_url="https://api.github.com/user/emails",
60
+ )
61
+
62
+ url = await adapter.get_authorization_url(
63
+ state="state-456",
64
+ redirect_uri="https://app.example.com/api/v1/auth/oauth/github/callback",
65
+ )
66
+
67
+ parsed = urlparse(url)
68
+ query = parse_qs(parsed.query)
69
+
70
+ assert parsed.scheme == "https"
71
+ assert parsed.netloc == "github.com"
72
+ assert query["client_id"][0] == "github-client-id"
73
+ assert query["state"][0] == "state-456"
74
+ assert query["scope"][0] == "read:user user:email"
75
+
76
+
77
+ @pytest.mark.asyncio
78
+ async def test_oauth_adapter_handle_callback_hydrates_social_account_and_user_context(
79
+ monkeypatch: pytest.MonkeyPatch,
80
+ ) -> None:
81
+ """handle_callback should exchange code, fetch profile, and hydrate models."""
82
+ from framework_m_core.interfaces.oauth import OAuth2Token, OAuth2UserInfo
83
+ from framework_m_standard.adapters.auth.oauth import OAuthAdapter
84
+
85
+ adapter = OAuthAdapter(
86
+ provider_name="google",
87
+ client_id="google-client-id",
88
+ client_secret="google-client-secret",
89
+ authorization_url="https://accounts.google.com/o/oauth2/v2/auth",
90
+ token_url="https://oauth2.googleapis.com/token",
91
+ userinfo_url="https://www.googleapis.com/oauth2/v3/userinfo",
92
+ scope="openid email profile",
93
+ )
94
+
95
+ async def fake_exchange_code(*, code: str, redirect_uri: str) -> OAuth2Token:
96
+ assert code == "oauth-code-123"
97
+ assert redirect_uri.endswith("/oauth/google/callback")
98
+ return await asyncio.sleep(
99
+ 0, result=OAuth2Token(access_token="token-abc", token_type="Bearer")
100
+ )
101
+
102
+ async def fake_get_user_info(token: OAuth2Token) -> OAuth2UserInfo:
103
+ assert token.access_token == "token-abc"
104
+ return await asyncio.sleep(
105
+ 0,
106
+ result=OAuth2UserInfo(
107
+ provider="google",
108
+ provider_user_id="provider-user-1",
109
+ email="jane@example.com",
110
+ display_name="Jane Doe",
111
+ avatar_url="https://cdn.example.com/avatar.png",
112
+ ),
113
+ )
114
+
115
+ monkeypatch.setattr(adapter, "exchange_code", fake_exchange_code)
116
+ monkeypatch.setattr(adapter, "get_user_info", fake_get_user_info)
117
+
118
+ result = await adapter.handle_callback(
119
+ code="oauth-code-123",
120
+ redirect_uri="https://app.example.com/api/v1/auth/oauth/google/callback",
121
+ local_user_id="user-123",
122
+ )
123
+
124
+ assert result.token.access_token == "token-abc"
125
+ assert result.user_info.provider_user_id == "provider-user-1"
126
+
127
+ social_account = result.social_account
128
+ assert social_account.provider == "google"
129
+ assert social_account.provider_user_id == "provider-user-1"
130
+ assert social_account.user_id == "user-123"
131
+ assert social_account.email == "jane@example.com"
132
+
133
+ user_context = result.user_context
134
+ assert user_context.id == "user-123"
135
+ assert user_context.email == "jane@example.com"
136
+ assert user_context.name == "Jane Doe"
137
+ assert "User" in user_context.roles
@@ -7,8 +7,7 @@ from typing import Any
7
7
  from unittest.mock import AsyncMock
8
8
 
9
9
  import pytest
10
-
11
- from framework_m.core.interfaces.event_bus import Event
10
+ from framework_m_core.interfaces.event_bus import Event
12
11
 
13
12
 
14
13
  class TestProjectorProtocolStructure:
@@ -16,19 +15,19 @@ class TestProjectorProtocolStructure:
16
15
 
17
16
  def test_projector_protocol_exists(self) -> None:
18
17
  """Projector should be importable from adapters."""
19
- from framework_m.adapters.read_model.projector import Projector
18
+ from framework_m_standard.adapters.read_model.projector import Projector
20
19
 
21
20
  assert Projector is not None
22
21
 
23
22
  def test_projector_has_handle_method(self) -> None:
24
23
  """Projector should define handle() method for events."""
25
- from framework_m.adapters.read_model.projector import Projector
24
+ from framework_m_standard.adapters.read_model.projector import Projector
26
25
 
27
26
  assert hasattr(Projector, "handle")
28
27
 
29
28
  def test_projector_has_subscribe_method(self) -> None:
30
29
  """Projector should define subscribe() for event bus registration."""
31
- from framework_m.adapters.read_model.projector import Projector
30
+ from framework_m_standard.adapters.read_model.projector import Projector
32
31
 
33
32
  assert hasattr(Projector, "subscribe")
34
33
 
@@ -36,7 +35,7 @@ class TestProjectorProtocolStructure:
36
35
  """Projector should define event_patterns for topics to subscribe to."""
37
36
  from unittest.mock import AsyncMock
38
37
 
39
- from framework_m.adapters.read_model.projector import Projector
38
+ from framework_m_standard.adapters.read_model.projector import Projector
40
39
 
41
40
  mock_read_model = AsyncMock()
42
41
  projector = Projector(read_model=mock_read_model)
@@ -66,7 +65,7 @@ class TestProjectorEventHandling:
66
65
  self, mock_read_model: Any
67
66
  ) -> None:
68
67
  """handle() should delegate to read_model.project()."""
69
- from framework_m.adapters.read_model.projector import Projector
68
+ from framework_m_standard.adapters.read_model.projector import Projector
70
69
 
71
70
  projector = Projector(read_model=mock_read_model)
72
71
  event = Event(type="doc.created", data={"doctype": "Invoice", "id": "123"})
@@ -80,7 +79,7 @@ class TestProjectorEventHandling:
80
79
  self, mock_read_model: Any, mock_event_bus: Any
81
80
  ) -> None:
82
81
  """subscribe() should register event patterns with event bus."""
83
- from framework_m.adapters.read_model.projector import Projector
82
+ from framework_m_standard.adapters.read_model.projector import Projector
84
83
 
85
84
  projector = Projector(
86
85
  read_model=mock_read_model,
@@ -97,7 +96,7 @@ class TestProjectorEventHandling:
97
96
  self, mock_read_model: Any, mock_event_bus: Any
98
97
  ) -> None:
99
98
  """subscribe() should store subscription IDs for later unsubscribe."""
100
- from framework_m.adapters.read_model.projector import Projector
99
+ from framework_m_standard.adapters.read_model.projector import Projector
101
100
 
102
101
  projector = Projector(
103
102
  read_model=mock_read_model,
@@ -114,7 +113,7 @@ class TestProjectorEventHandling:
114
113
  self, mock_read_model: Any, mock_event_bus: Any
115
114
  ) -> None:
116
115
  """unsubscribe() should remove all event bus subscriptions."""
117
- from framework_m.adapters.read_model.projector import Projector
116
+ from framework_m_standard.adapters.read_model.projector import Projector
118
117
 
119
118
  projector = Projector(
120
119
  read_model=mock_read_model,
@@ -141,7 +140,7 @@ class TestProjectorFiltering:
141
140
  @pytest.mark.asyncio
142
141
  async def test_handle_filters_by_doctype(self, mock_read_model: Any) -> None:
143
142
  """handle() should filter events by doctype if configured."""
144
- from framework_m.adapters.read_model.projector import Projector
143
+ from framework_m_standard.adapters.read_model.projector import Projector
145
144
 
146
145
  projector = Projector(
147
146
  read_model=mock_read_model,
@@ -165,7 +164,7 @@ class TestProjectorFiltering:
165
164
  self, mock_read_model: Any
166
165
  ) -> None:
167
166
  """handle() should process all events if no doctype_filter."""
168
- from framework_m.adapters.read_model.projector import Projector
167
+ from framework_m_standard.adapters.read_model.projector import Projector
169
168
 
170
169
  projector = Projector(read_model=mock_read_model)
171
170
 
@@ -183,6 +182,6 @@ class TestProjectorExports:
183
182
 
184
183
  def test_exported_from_adapters_read_model(self) -> None:
185
184
  """Projector should be importable from adapters.read_model."""
186
- from framework_m.adapters.read_model import Projector
185
+ from framework_m_standard.adapters.read_model import Projector
187
186
 
188
187
  assert Projector is not None
@@ -7,11 +7,15 @@ This module provides shared fixtures for testing Framework M:
7
7
  - Database fixtures for integration testing
8
8
  """
9
9
 
10
+ import os
10
11
  from collections.abc import AsyncGenerator
11
12
  from datetime import UTC, datetime
12
13
  from typing import Any
13
14
 
14
15
  import pytest
16
+ from framework_m_core.container import Container
17
+ from framework_m_core.domain.base_controller import BaseController
18
+ from framework_m_core.registry import MetaRegistry
15
19
  from sqlalchemy import MetaData
16
20
  from sqlalchemy.ext.asyncio import (
17
21
  AsyncEngine,
@@ -21,9 +25,14 @@ from sqlalchemy.ext.asyncio import (
21
25
  )
22
26
 
23
27
  from framework_m import DocType, Field
24
- from framework_m.core.container import Container
25
- from framework_m.core.domain.base_controller import BaseController
26
- from framework_m.core.registry import MetaRegistry
28
+
29
+
30
+ def pytest_configure(config: Any) -> None:
31
+ """Configure environment for testing."""
32
+ # Ensure a default JWT secret is available to satisfy fail-fast validation
33
+ if not os.environ.get("FRAMEWORK_M_JWT_SECRET"):
34
+ os.environ["FRAMEWORK_M_JWT_SECRET"] = "test-secret-min-32-chars-long-security"
35
+
27
36
 
28
37
  # =============================================================================
29
38
  # Sample DocTypes for Testing
@@ -210,7 +219,7 @@ async def clean_tables(
210
219
  ) -> AsyncGenerator[MetaData, None]:
211
220
  """Create a clean metadata and drop all tables after test."""
212
221
  # Reset table registry before each test
213
- from framework_m.adapters.db.table_registry import TableRegistry
222
+ from framework_m_standard.adapters.db.table_registry import TableRegistry
214
223
 
215
224
  table_registry = TableRegistry()
216
225
  table_registry.reset()
@@ -7,8 +7,7 @@ from typing import Any
7
7
  from unittest.mock import AsyncMock
8
8
 
9
9
  import pytest
10
-
11
- from framework_m.core.interfaces.event_bus import Event
10
+ from framework_m_core.interfaces.event_bus import Event
12
11
 
13
12
 
14
13
  class TestReadModelProtocolStructure:
@@ -107,7 +106,7 @@ class TestReadModelExports:
107
106
 
108
107
  def test_exported_from_interfaces_init(self) -> None:
109
108
  """ReadModelProtocol should be exported from core.interfaces."""
110
- from framework_m.core.interfaces import ReadModelProtocol
109
+ from framework_m_core.interfaces import ReadModelProtocol
111
110
 
112
111
  assert ReadModelProtocol is not None
113
112
 
@@ -7,16 +7,15 @@ Covers: validation, permissions, RLS filtering, immutable docs.
7
7
  from typing import Any, ClassVar
8
8
 
9
9
  import pytest
10
+ from framework_m_core.domain.base_doctype import BaseDocType, Field
11
+ from framework_m_core.exceptions import PermissionDeniedError
12
+ from framework_m_core.interfaces.auth_context import UserContext
13
+ from framework_m_core.registry import MetaRegistry
10
14
  from litestar import Litestar, Response
11
15
  from litestar.status_codes import HTTP_403_FORBIDDEN
12
16
  from litestar.testing import TestClient
13
17
  from litestar.types import ASGIApp, Receive, Scope, Send
14
18
 
15
- from framework_m.core.domain.base_doctype import BaseDocType, Field
16
- from framework_m.core.exceptions import PermissionDeniedError
17
- from framework_m.core.interfaces.auth_context import UserContext
18
- from framework_m.core.registry import MetaRegistry
19
-
20
19
  # =============================================================================
21
20
  # Test DocTypes
22
21
  # =============================================================================
@@ -104,7 +103,7 @@ class TestCrudEndpointValidation:
104
103
  @pytest.fixture
105
104
  def app(self) -> Litestar:
106
105
  """Create test app with CRUD routes."""
107
- from framework_m.adapters.web.meta_router import create_meta_router
106
+ from framework_m_standard.adapters.web.meta_router import create_meta_router
108
107
 
109
108
  router = create_meta_router()
110
109
  return Litestar(
@@ -154,7 +153,7 @@ class TestCrudEndpointPermissions:
154
153
  @pytest.fixture
155
154
  def app_employee(self) -> Litestar:
156
155
  """Create test app with Employee role."""
157
- from framework_m.adapters.web.meta_router import create_meta_router
156
+ from framework_m_standard.adapters.web.meta_router import create_meta_router
158
157
 
159
158
  router = create_meta_router()
160
159
  return Litestar(
@@ -168,7 +167,7 @@ class TestCrudEndpointPermissions:
168
167
  @pytest.fixture
169
168
  def app_admin(self) -> Litestar:
170
169
  """Create test app with Admin role."""
171
- from framework_m.adapters.web.meta_router import create_meta_router
170
+ from framework_m_standard.adapters.web.meta_router import create_meta_router
172
171
 
173
172
  router = create_meta_router()
174
173
  return Litestar(
@@ -216,7 +215,7 @@ class TestCrudRlsFiltering:
216
215
  @pytest.fixture
217
216
  def app_user1(self) -> Litestar:
218
217
  """Create test app for user 1."""
219
- from framework_m.adapters.web.meta_router import create_meta_router
218
+ from framework_m_standard.adapters.web.meta_router import create_meta_router
220
219
 
221
220
  router = create_meta_router()
222
221
  return Litestar(
@@ -250,7 +249,7 @@ class TestRpcEndpointIntegration:
250
249
  @pytest.fixture
251
250
  def app(self) -> Litestar:
252
251
  """Create test app with RPC routes."""
253
- from framework_m.adapters.web.rpc_routes import rpc_router
252
+ from framework_m_standard.adapters.web.rpc_routes import rpc_router
254
253
 
255
254
  return Litestar(
256
255
  route_handlers=[rpc_router],
@@ -265,8 +264,8 @@ class TestRpcEndpointIntegration:
265
264
 
266
265
  def test_rpc_function_callable(self, client: TestClient[Litestar]) -> None:
267
266
  """Should be able to call registered RPC function."""
268
- from framework_m.core.decorators import rpc
269
- from framework_m.core.rpc_registry import RpcRegistry
267
+ from framework_m_core.decorators import rpc
268
+ from framework_m_core.rpc_registry import RpcRegistry
270
269
 
271
270
  registry = RpcRegistry.get_instance()
272
271
 
@@ -13,10 +13,9 @@ from typing import ClassVar
13
13
  from uuid import uuid4
14
14
 
15
15
  import pytest
16
+ from framework_m_core.domain.base_doctype import BaseDocType
16
17
  from pydantic import Field
17
18
 
18
- from framework_m.core.domain.base_doctype import BaseDocType
19
-
20
19
  # ============================================================================
21
20
  # Test DocTypes
22
21
  # ============================================================================
@@ -8,18 +8,18 @@ from __future__ import annotations
8
8
  from collections.abc import AsyncGenerator
9
9
 
10
10
  import pytest
11
- from sqlalchemy import MetaData
12
-
13
- from framework_m import DocType, Field
14
- from framework_m.adapters.db import (
11
+ from framework_m_core.registry import MetaRegistry
12
+ from framework_m_standard.adapters.db import (
15
13
  ConnectionFactory,
16
14
  RepositoryFactory,
17
15
  SessionFactory,
18
16
  TableRegistry,
19
17
  initialize_database,
20
18
  )
21
- from framework_m.adapters.db.schema_mapper import SchemaMapper
22
- from framework_m.core.registry import MetaRegistry
19
+ from framework_m_standard.adapters.db.schema_mapper import SchemaMapper
20
+ from sqlalchemy import MetaData
21
+
22
+ from framework_m import DocType, Field
23
23
 
24
24
  # =============================================================================
25
25
  # Test DocType