framework-m-core 0.30.2__tar.gz → 0.32.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_core-0.30.2 → framework_m_core-0.32.0}/CHANGELOG.md +25 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/PKG-INFO +7 -7
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/README.md +3 -3
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/pyproject.toml +3 -3
- framework_m_core-0.32.0/src/framework_m_core/constants.py +12 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/container.py +8 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/user.py +17 -15
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow.py +4 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_state_definition.py +7 -1
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/__init__.py +14 -1
- framework_m_core-0.32.0/src/framework_m_core/interfaces/payload_crypto.py +62 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/read_model.py +15 -9
- framework_m_core-0.32.0/src/framework_m_core/interfaces/search.py +252 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/workflow.py +3 -0
- {framework_m_core-0.30.2/src/framework_m_core/locales/fr → framework_m_core-0.32.0/src/framework_m_core/locales/en}/common.json +3 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/locales/es/common.json +3 -0
- {framework_m_core-0.30.2/src/framework_m_core/locales/hi → framework_m_core-0.32.0/src/framework_m_core/locales/fr}/common.json +3 -0
- {framework_m_core-0.30.2/src/framework_m_core/locales/en → framework_m_core-0.32.0/src/framework_m_core/locales/hi}/common.json +3 -0
- framework_m_core-0.32.0/src/framework_m_core/locales/mr/common.json +335 -0
- framework_m_core-0.32.0/src/framework_m_core/locales/ta/common.json +335 -0
- framework_m_core-0.32.0/src/framework_m_core/locales/ur/common.json +335 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_child_table_permissions.py +14 -7
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_exports.py +1 -1
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/events/test_auth_events.py +27 -7
- framework_m_core-0.32.0/tests/unit/core/interfaces/test_search.py +164 -0
- framework_m_core-0.30.2/src/framework_m_core/interfaces/search.py +0 -132
- framework_m_core-0.30.2/src/framework_m_core/locales/mr/common.json +0 -332
- framework_m_core-0.30.2/src/framework_m_core/locales/ta/common.json +0 -332
- framework_m_core-0.30.2/src/framework_m_core/locales/ur/common.json +0 -332
- framework_m_core-0.30.2/tests/unit/core/interfaces/test_search.py +0 -73
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/.gitignore +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/LICENSE +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/__init__.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/__init__.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/config.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/main.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/plugin_loader.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/cli/utility.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/config.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/decorators.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/di.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/__init__.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/activity_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/api_key.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/auto_report.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/custom_permission.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/document_share.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/email_queue.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/error_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/file.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/idempotency_key.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/job_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/landing_candidate.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/letterhead.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/notification.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/print_format.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/recent_document.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/report.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/saved_view.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/scheduled_job.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/session.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/social_account.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/system_settings.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/tenant_translation.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/todo.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/translation.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/webhook.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/webhook_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_state.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_transition.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow_transition_definition.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/__init__.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/base_controller.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/base_doctype.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/diff.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/landing.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/meta.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/migration_engine.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/mixins.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/naming_counter.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/navigation.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/outbox.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/domain/redactor.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/events/__init__.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/events/handlers.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/exceptions.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/audit.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/auth_context.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/authentication.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/base_doctype.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/bootstrap.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/cache.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/composition.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/controller.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/db_resolver.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/defaults.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/discovery.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/email_queue.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/email_sender.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/event_bus.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/feature_entitlement.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/i18n.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/idempotency.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/identity.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/introspection.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/job_logger.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/job_queue.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/landing.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/logging.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/metering.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/navigation.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/notification.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/oauth.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/permission.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/print.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/redactor.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/report_engine.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/repository.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/schema_mapper.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/secrets.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/session.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/sms_gateway.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/socket.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/storage.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/tenant.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/ticker.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/webhook.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/worker.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/jobs_registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/metadata_decorator.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/permission_lookup.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/permissions.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/pii.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/py.typed +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/rls.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/rpc_registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/schema_enrichment.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/security.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/__init__.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/defaults_service.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/document_id.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/health_server.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/landing.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/secrets_service.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/user_manager.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/services/webhook_dispatcher.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/system_context.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/types/job_context.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/unit_of_work.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_cli_commands.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_cli_init.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_cli_main.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_config.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_info_plugins.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_plugin_loader.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/cli/test_utility.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/conftest.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_api_key.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_job_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_report.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_scheduled_job.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_social_account.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_user.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_webhook.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_webhook_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/doctypes/test_workflow_ui.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/domain/test_meta_discovery.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/domain/test_naming_counter.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/events/test_doc_events.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_cache.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_email_queue_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_idempotency.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_identity.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_repository_temporal.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_sms_gateway.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_socket.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/interfaces/test_workflow.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/services/test_user_manager.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/services/test_webhook_dispatcher.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_activity_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_container.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_container_async.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_custom_permission.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_custom_protocols.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_di.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_document_share.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_email_queue.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_error_log.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_exceptions.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_file.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_job_decorator.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_landing_candidate.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_link_field_leakage.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_meta_registry_baking.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_meta_registry_overrides.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_metadata_decorator.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_metadata_runtime_patch.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_notification.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_object_level_permissions.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_outbox.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permission_config.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permission_conveniences.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permission_lookup.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_permissions.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_pii.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_print_format.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_rls.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_rpc_decorator.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_schema_extension.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_security.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_session.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_share_lookup.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_singles.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_system_context.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_system_settings.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_table_alteration.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_team_based_access.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_tenant.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_ui_context_registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_unit_of_work.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_user_preferences_locale.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/test_whitelist_decorator.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/core/types/test_job_context.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/domain/test_landing_models.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/domain/test_navigation_models.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/domain/test_registry_roles.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_activity_registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_authentication.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_discovery_port.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_logging_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_redactor_port.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/interfaces/test_worker_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/services/test_health_server.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/doctypes/test_auto_report.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/doctypes/test_saved_view.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_controller.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_controller_audit.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_controller_submitted.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_base_doctype_implements_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_diff.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_migration_engine.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_mixins.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/domain/test_redactor_logic.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/events/test_read_model_projector.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_audit.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_auth_context.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_base_doctype_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_bootstrap_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_controller_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_core_report_engine.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_db_resolver.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_defaults.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_event_bus.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_feature_entitlement.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_i18n.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_introspection.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_job_queue.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_landing.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_metering.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_navigation.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_permission.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_print.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_repository.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_schema_mapper_protocol.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_smoke_interfaces.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_storage.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/interfaces/test_ticker.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_defaults_service.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_document_id.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_landing_service.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/services/test_secrets_service.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_config_expansion.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_config_schema.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_registry.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_registry_discovery.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_schema_enrichment.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_selective_apps.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/core/test_webhooks.py +0 -0
- {framework_m_core-0.30.2 → framework_m_core-0.32.0}/tests/unit/test_base_doctype.py +0 -0
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## framework-m-core v0.32.0
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add PayloadCryptoProtocol interface for payload encryption (3a036b6)
|
|
6
|
+
- add global search command palette (360fff6)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- unify admin roles into a centralized constant (e557428)
|
|
11
|
+
- enforce user field guard blocks and correct admin roles (184736f)
|
|
12
|
+
|
|
13
|
+
## framework-m-core v0.31.0
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- add bounded count threshold for list totals (db01634)
|
|
18
|
+
- support dynamic workflow state field configuration (1ae69e1)
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
- support dynamic workflow state field configuration (cc88877)
|
|
23
|
+
- update package export assertions (e19c1b1)
|
|
24
|
+
- ensure container protocol defaults and type mappings (41df6cb)
|
|
25
|
+
|
|
1
26
|
## framework-m-core v0.30.2
|
|
2
27
|
|
|
3
28
|
### Bug Fixes
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: framework-m-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.32.0
|
|
4
4
|
Summary: Core protocols and dependency injection for Framework M
|
|
5
5
|
Author: Framework M Contributors
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.12
|
|
9
|
-
Requires-Dist: cyclopts>=4.22.
|
|
10
|
-
Requires-Dist: pydantic-settings>=2.
|
|
9
|
+
Requires-Dist: cyclopts>=4.22.5
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.15.0
|
|
11
11
|
Requires-Dist: pydantic>=2.13.4
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
[](https://badge.fury.io/py/framework-m-core)
|
|
17
17
|
[](https://www.python.org/downloads/)
|
|
18
18
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
19
|
-
[](https://gitlab.com/framework-m/framework-m/-/pipelines)
|
|
20
20
|
|
|
21
21
|
Core protocols and foundation for Framework M.
|
|
22
22
|
|
|
@@ -58,9 +58,9 @@ def my_command():
|
|
|
58
58
|
|
|
59
59
|
## Usage
|
|
60
60
|
|
|
61
|
-
This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
|
|
61
|
+
This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
|
|
62
62
|
For most applications, install the full `framework-m` package instead.
|
|
63
63
|
|
|
64
64
|
## License
|
|
65
65
|
|
|
66
|
-
Apache 2.0 License - see [LICENSE](https://gitlab.com/
|
|
66
|
+
Apache 2.0 License - see [LICENSE](https://gitlab.com/framework-m/framework-m/blob/main/LICENSE) for details.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/py/framework-m-core)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
5
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
-
[](https://gitlab.com/framework-m/framework-m/-/pipelines)
|
|
7
7
|
|
|
8
8
|
Core protocols and foundation for Framework M.
|
|
9
9
|
|
|
@@ -45,9 +45,9 @@ def my_command():
|
|
|
45
45
|
|
|
46
46
|
## Usage
|
|
47
47
|
|
|
48
|
-
This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
|
|
48
|
+
This package is typically used as a dependency of `framework-m` or `framework-m-standard`.
|
|
49
49
|
For most applications, install the full `framework-m` package instead.
|
|
50
50
|
|
|
51
51
|
## License
|
|
52
52
|
|
|
53
|
-
Apache 2.0 License - see [LICENSE](https://gitlab.com/
|
|
53
|
+
Apache 2.0 License - see [LICENSE](https://gitlab.com/framework-m/framework-m/blob/main/LICENSE) for details.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "framework-m-core"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.32.0"
|
|
4
4
|
description = "Core protocols and dependency injection for Framework M"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "Apache-2.0" }
|
|
@@ -10,8 +10,8 @@ authors = [
|
|
|
10
10
|
]
|
|
11
11
|
dependencies = [
|
|
12
12
|
"pydantic>=2.13.4",
|
|
13
|
-
"pydantic-settings>=2.
|
|
14
|
-
"cyclopts>=4.22.
|
|
13
|
+
"pydantic-settings>=2.15.0",
|
|
14
|
+
"cyclopts>=4.22.5",
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[build-system]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Core constants used across the framework."""
|
|
2
|
+
|
|
3
|
+
# Canonical set of roles that grant system-wide administrative privileges.
|
|
4
|
+
# These roles bypass Row-Level Security (RLS) and DocType permissions.
|
|
5
|
+
ADMIN_ROLES: frozenset[str] = frozenset(
|
|
6
|
+
{
|
|
7
|
+
"Administrator",
|
|
8
|
+
"SystemManager",
|
|
9
|
+
"System",
|
|
10
|
+
"Admin",
|
|
11
|
+
}
|
|
12
|
+
)
|
|
@@ -178,6 +178,10 @@ class Container(DeclarativeContainer):
|
|
|
178
178
|
# Usage: container.repository.override(GenericRepository)
|
|
179
179
|
repository: _Provider[Any] = providers.Dependency()
|
|
180
180
|
|
|
181
|
+
# Optional request-scoped database search provider. Standard web wiring
|
|
182
|
+
# composes it with application entry-point providers per request.
|
|
183
|
+
search_provider: _Provider[Any] = providers.Dependency()
|
|
184
|
+
|
|
181
185
|
# Repository Factory - dynamically instantiates repositories for DocTypes
|
|
182
186
|
# Usage: container.repository_factory()
|
|
183
187
|
repository_factory: _Provider[Any] = providers.Dependency()
|
|
@@ -259,6 +263,9 @@ class Container(DeclarativeContainer):
|
|
|
259
263
|
# Composition Service - handles federated data aggregation
|
|
260
264
|
composition_service: _Provider[Any] = providers.Dependency()
|
|
261
265
|
|
|
266
|
+
# Workflow Service - manages document workflow states and transitions
|
|
267
|
+
workflow_service: _Provider[Any] = providers.Dependency()
|
|
268
|
+
|
|
262
269
|
|
|
263
270
|
def load_overrides(container: Container, group: str = "framework_m.overrides") -> int:
|
|
264
271
|
"""
|
|
@@ -698,6 +705,7 @@ def get_app_container_names(main_container: Container) -> list[str]:
|
|
|
698
705
|
"migration_manager",
|
|
699
706
|
"discovery_service",
|
|
700
707
|
"composition_service",
|
|
708
|
+
"workflow_service",
|
|
701
709
|
"wiring_config",
|
|
702
710
|
}
|
|
703
711
|
|
|
@@ -22,6 +22,7 @@ from typing import Any
|
|
|
22
22
|
|
|
23
23
|
from pydantic import Field, field_validator, model_validator
|
|
24
24
|
|
|
25
|
+
from framework_m_core.constants import ADMIN_ROLES
|
|
25
26
|
from framework_m_core.domain.base_controller import BaseController
|
|
26
27
|
from framework_m_core.domain.base_doctype import BaseDocType
|
|
27
28
|
from framework_m_core.exceptions import PermissionDeniedError
|
|
@@ -287,6 +288,7 @@ class LocalUserController(BaseController[LocalUser]):
|
|
|
287
288
|
|
|
288
289
|
ADMIN_FIELDS: frozenset[str] = frozenset({"roles", "tenants", "is_active"})
|
|
289
290
|
IDENTITY_FIELDS: frozenset[str] = frozenset({"email", "phone_number"})
|
|
291
|
+
ADMIN_ROLES = ADMIN_ROLES
|
|
290
292
|
|
|
291
293
|
async def before_save(self, context: Any = None) -> None:
|
|
292
294
|
"""Enforce self-service identifier/admin-field protection.
|
|
@@ -305,18 +307,16 @@ class LocalUserController(BaseController[LocalUser]):
|
|
|
305
307
|
if isinstance(context, dict) and context.get("system_bypass") is True:
|
|
306
308
|
return
|
|
307
309
|
|
|
308
|
-
if self._is_admin_context(context):
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
).format(fields=", ".join(sorted(changed_admin)))
|
|
319
|
-
)
|
|
310
|
+
if not self._is_admin_context(context):
|
|
311
|
+
changed_admin = {
|
|
312
|
+
field for field in self.ADMIN_FIELDS if self.has_changed(field)
|
|
313
|
+
}
|
|
314
|
+
if changed_admin:
|
|
315
|
+
raise PermissionDeniedError(
|
|
316
|
+
t(
|
|
317
|
+
"Self-service users cannot modify administrative fields: {fields}"
|
|
318
|
+
).format(fields=", ".join(sorted(changed_admin)))
|
|
319
|
+
)
|
|
320
320
|
|
|
321
321
|
changed_identity = {
|
|
322
322
|
field for field in self.IDENTITY_FIELDS if self.has_changed(field)
|
|
@@ -369,14 +369,16 @@ class LocalUserController(BaseController[LocalUser]):
|
|
|
369
369
|
|
|
370
370
|
await super().after_save(context)
|
|
371
371
|
|
|
372
|
-
@
|
|
373
|
-
def _is_admin_context(context: Any) -> bool:
|
|
372
|
+
@classmethod
|
|
373
|
+
def _is_admin_context(cls, context: Any) -> bool:
|
|
374
374
|
"""Return True when the save is being performed by an admin/system principal."""
|
|
375
375
|
if isinstance(context, dict):
|
|
376
376
|
if context.get("is_admin") is True:
|
|
377
377
|
return True
|
|
378
378
|
user = context.get("user")
|
|
379
|
-
if isinstance(user, UserContext) and
|
|
379
|
+
if isinstance(user, UserContext) and any(
|
|
380
|
+
r in cls.ADMIN_ROLES for r in (user.roles or [])
|
|
381
|
+
):
|
|
380
382
|
return True
|
|
381
383
|
return False
|
|
382
384
|
|
{framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/doctypes/workflow.py
RENAMED
|
@@ -37,6 +37,10 @@ class Workflow(BaseDocType):
|
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
)
|
|
40
|
+
workflow_state_field: str = Field(
|
|
41
|
+
default="workflow_state",
|
|
42
|
+
description="Field on target DocType that stores the workflow state name",
|
|
43
|
+
)
|
|
40
44
|
initial_state: str = Field(description="Initial state for new documents")
|
|
41
45
|
states: list[WorkflowStateDefinition] = Field(
|
|
42
46
|
default_factory=list,
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import ClassVar
|
|
6
6
|
|
|
7
7
|
from framework_m_core.domain.base_doctype import BaseDocType, Field
|
|
8
|
+
from framework_m_core.domain.mixins import DocStatus
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class WorkflowStateDefinition(BaseDocType):
|
|
@@ -13,12 +14,17 @@ class WorkflowStateDefinition(BaseDocType):
|
|
|
13
14
|
This is a child table for the Workflow DocType.
|
|
14
15
|
|
|
15
16
|
Attributes:
|
|
16
|
-
|
|
17
|
+
current_state: Name of the state (e.g., Draft, Approved)
|
|
18
|
+
doc_status: Target document status mapping (0: Draft, 1: Submitted, 2: Cancelled)
|
|
17
19
|
allow_edit: Role allowed to edit documents when in this state
|
|
18
20
|
is_terminal: Whether this is a final state (no further transitions)
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
23
|
current_state: str = Field(description="Name of the state (e.g., Draft, Approved)")
|
|
24
|
+
doc_status: DocStatus | None = Field(
|
|
25
|
+
default=None,
|
|
26
|
+
description="Target document status mapping (0: Draft, 1: Submitted, 2: Cancelled)",
|
|
27
|
+
)
|
|
22
28
|
allow_edit: str | None = Field(
|
|
23
29
|
default=None,
|
|
24
30
|
description="Role allowed to edit in this state",
|
{framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/__init__.py
RENAMED
|
@@ -51,6 +51,7 @@ from framework_m_core.interfaces.oauth import (
|
|
|
51
51
|
OAuth2Token,
|
|
52
52
|
OAuth2UserInfo,
|
|
53
53
|
)
|
|
54
|
+
from framework_m_core.interfaces.payload_crypto import PayloadCryptoProtocol
|
|
54
55
|
from framework_m_core.interfaces.permission import (
|
|
55
56
|
DecisionSource,
|
|
56
57
|
PermissionAction,
|
|
@@ -74,7 +75,14 @@ from framework_m_core.interfaces.repository import (
|
|
|
74
75
|
RepositoryProtocol,
|
|
75
76
|
)
|
|
76
77
|
from framework_m_core.interfaces.schema_mapper import SchemaMapperProtocol
|
|
77
|
-
from framework_m_core.interfaces.search import
|
|
78
|
+
from framework_m_core.interfaces.search import (
|
|
79
|
+
GlobalSearchQuery,
|
|
80
|
+
GlobalSearchResponse,
|
|
81
|
+
SearchCategory,
|
|
82
|
+
SearchProtocol,
|
|
83
|
+
SearchResult,
|
|
84
|
+
SearchSuggestion,
|
|
85
|
+
)
|
|
78
86
|
from framework_m_core.interfaces.secrets import SecretProtocol
|
|
79
87
|
from framework_m_core.interfaces.session import SessionData, SessionProtocol
|
|
80
88
|
from framework_m_core.interfaces.socket import SocketProtocol
|
|
@@ -144,6 +152,7 @@ __all__ = [
|
|
|
144
152
|
"OrderSpec",
|
|
145
153
|
"PaginatedResult",
|
|
146
154
|
"PasswordCredentials",
|
|
155
|
+
"PayloadCryptoProtocol",
|
|
147
156
|
"PermissionAction",
|
|
148
157
|
"PermissionProtocol",
|
|
149
158
|
"PolicyEvaluateRequest",
|
|
@@ -158,8 +167,12 @@ __all__ = [
|
|
|
158
167
|
"ReportResult",
|
|
159
168
|
"RepositoryProtocol",
|
|
160
169
|
"SchemaMapperProtocol",
|
|
170
|
+
"GlobalSearchQuery",
|
|
171
|
+
"GlobalSearchResponse",
|
|
172
|
+
"SearchCategory",
|
|
161
173
|
"SearchProtocol",
|
|
162
174
|
"SearchResult",
|
|
175
|
+
"SearchSuggestion",
|
|
163
176
|
"SecretProtocol",
|
|
164
177
|
"SendResult",
|
|
165
178
|
"SessionData",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Payload Encryption Protocol (Port).
|
|
2
|
+
|
|
3
|
+
Defines the interface that payload encryption adapters must implement.
|
|
4
|
+
The framework uses this port to decrypt incoming request bodies and to
|
|
5
|
+
expose public-key material to clients.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Protocol, runtime_checkable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@runtime_checkable
|
|
14
|
+
class PayloadCryptoProtocol(Protocol):
|
|
15
|
+
"""Protocol for application-layer payload encryption/decryption.
|
|
16
|
+
|
|
17
|
+
Implementations are responsible for:
|
|
18
|
+
1. Encrypting plaintext request bodies into an opaque envelope.
|
|
19
|
+
2. Decrypting incoming envelopes back into plaintext bytes.
|
|
20
|
+
3. Exposing the server's public key in JWK-compatible JSON so the
|
|
21
|
+
frontend can encrypt payloads without an extra handshake.
|
|
22
|
+
|
|
23
|
+
The middleware layer decides *when* to decrypt based on configuration;
|
|
24
|
+
this provider focuses purely on the cryptographic primitives.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def encrypt_payload(self, plaintext: bytes | str) -> bytes:
|
|
28
|
+
"""Encrypt a plaintext payload.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
plaintext: The raw request body as bytes or text.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
The encrypted envelope as bytes. The concrete format is left to
|
|
35
|
+
the adapter (e.g. JWE compact serialization, JSON envelope).
|
|
36
|
+
"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
def decrypt_payload(self, payload: bytes | str) -> bytes:
|
|
40
|
+
"""Decrypt an encrypted payload.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
payload: The encrypted envelope as bytes or text.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
The decrypted plaintext as bytes.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: If the payload is malformed or cannot be decrypted.
|
|
50
|
+
"""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def get_public_key(self) -> dict[str, Any]:
|
|
54
|
+
"""Return the public key in a JWK-compatible dictionary.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A JSON-serializable mapping (e.g. ``{"kty": "RSA", "n": "...", ...}``).
|
|
58
|
+
"""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = ["PayloadCryptoProtocol"]
|
{framework_m_core-0.30.2 → framework_m_core-0.32.0}/src/framework_m_core/interfaces/read_model.py
RENAMED
|
@@ -18,6 +18,7 @@ from typing import Any, Protocol
|
|
|
18
18
|
from pydantic import BaseModel
|
|
19
19
|
|
|
20
20
|
from framework_m_core.interfaces.event_bus import Event
|
|
21
|
+
from framework_m_core.interfaces.repository import FilterSpec
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class ReadQueryResult(BaseModel):
|
|
@@ -59,9 +60,12 @@ class ReadModelProtocol(Protocol):
|
|
|
59
60
|
if event.type == "invoice.created":
|
|
60
61
|
await self._update_monthly_totals(event.data)
|
|
61
62
|
|
|
62
|
-
async def query(self, filters=None, **kwargs) ->
|
|
63
|
-
return
|
|
64
|
-
|
|
63
|
+
async def query(self, filters=None, **kwargs) -> ReadQueryResult:
|
|
64
|
+
return ReadQueryResult(
|
|
65
|
+
items=await self.clickhouse.execute(
|
|
66
|
+
"SELECT month, SUM(total) FROM invoices GROUP BY month"
|
|
67
|
+
),
|
|
68
|
+
total=12,
|
|
65
69
|
)
|
|
66
70
|
|
|
67
71
|
async def rebuild(self) -> None:
|
|
@@ -87,11 +91,11 @@ class ReadModelProtocol(Protocol):
|
|
|
87
91
|
|
|
88
92
|
async def query(
|
|
89
93
|
self,
|
|
90
|
-
filters:
|
|
94
|
+
filters: list[FilterSpec] | None = None,
|
|
91
95
|
order_by: list[str] | None = None,
|
|
92
96
|
limit: int = 100,
|
|
93
97
|
offset: int = 0,
|
|
94
|
-
) ->
|
|
98
|
+
) -> ReadQueryResult:
|
|
95
99
|
"""Query the read model with optional filtering and pagination.
|
|
96
100
|
|
|
97
101
|
Read model queries are optimized for fast retrieval and
|
|
@@ -99,17 +103,19 @@ class ReadModelProtocol(Protocol):
|
|
|
99
103
|
(eventual consistency).
|
|
100
104
|
|
|
101
105
|
Args:
|
|
102
|
-
filters: Optional
|
|
106
|
+
filters: Optional list of filter specs to apply (operators
|
|
107
|
+
preserved, e.g. EQ/LT/LIKE/IN)
|
|
103
108
|
order_by: List of fields to sort by (prefix with - for desc)
|
|
104
109
|
limit: Maximum number of results (default: 100)
|
|
105
110
|
offset: Number of records to skip for pagination
|
|
106
111
|
|
|
107
112
|
Returns:
|
|
108
|
-
|
|
113
|
+
ReadQueryResult wrapping the matching items plus a total
|
|
114
|
+
count (for pagination).
|
|
109
115
|
|
|
110
116
|
Example:
|
|
111
|
-
|
|
112
|
-
filters=
|
|
117
|
+
result = await read_model.query(
|
|
118
|
+
filters=[FilterSpec(field="status", operator=FilterOperator.EQ, value="paid")],
|
|
113
119
|
order_by=["-total", "customer"],
|
|
114
120
|
limit=50
|
|
115
121
|
)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Search Protocol - Core interface for full-text search.
|
|
2
|
+
|
|
3
|
+
This module defines the SearchProtocol for full-text search
|
|
4
|
+
and indexing operations.
|
|
5
|
+
|
|
6
|
+
Supports various backends: Meilisearch, Elasticsearch, Whoosh, etc.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from typing import Any, Protocol
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, Field, computed_field, field_validator, model_validator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SearchCategory(StrEnum):
|
|
17
|
+
"""Public categories understood by global-search providers and clients."""
|
|
18
|
+
|
|
19
|
+
DOCUMENTS = "documents"
|
|
20
|
+
DOCTYPES = "doctypes"
|
|
21
|
+
COMMANDS = "commands"
|
|
22
|
+
NAVIGATION = "navigation"
|
|
23
|
+
REPORTS = "reports"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SearchSuggestion(BaseModel):
|
|
27
|
+
"""Normalized item returned by a global-search provider.
|
|
28
|
+
|
|
29
|
+
``id`` is the globally stable deduplication key. Providers must use the
|
|
30
|
+
same ID for the same logical item across queries (for example,
|
|
31
|
+
``documents:Customer:CUST-0001``). ``metadata`` is provider metadata for
|
|
32
|
+
backend composition only; the web adapter must explicitly select the
|
|
33
|
+
fields it exposes to a browser.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
id: str = Field(min_length=1, description="Globally stable result identifier")
|
|
37
|
+
category: SearchCategory
|
|
38
|
+
label: str = Field(min_length=1)
|
|
39
|
+
subtitle: str | None = None
|
|
40
|
+
icon: str | None = None
|
|
41
|
+
url: str | None = None
|
|
42
|
+
score: float | None = None
|
|
43
|
+
created_at: datetime | None = None
|
|
44
|
+
updated_at: datetime | None = None
|
|
45
|
+
highlights: dict[str, list[str]] | None = None
|
|
46
|
+
provider: str | None = Field(default=None, exclude=True)
|
|
47
|
+
metadata: dict[str, Any] = Field(default_factory=dict, exclude=True)
|
|
48
|
+
|
|
49
|
+
@field_validator("id", "label")
|
|
50
|
+
@classmethod
|
|
51
|
+
def _strip_required_text(cls, value: str) -> str:
|
|
52
|
+
"""Reject whitespace-only stable IDs and labels."""
|
|
53
|
+
stripped = value.strip()
|
|
54
|
+
if not stripped:
|
|
55
|
+
raise ValueError("must not be blank")
|
|
56
|
+
return stripped
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class GlobalSearchQuery(BaseModel):
|
|
60
|
+
"""Normalized request options for the ``/search/global`` contract.
|
|
61
|
+
|
|
62
|
+
The route owns the blank-query response (an empty ``suggestions`` list),
|
|
63
|
+
while providers only receive non-empty :attr:`normalized_query` values.
|
|
64
|
+
``categories`` is intentionally a list so its query-string representation
|
|
65
|
+
has a stable order when clients need shareable URLs.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
query: str
|
|
69
|
+
typeahead: bool = False
|
|
70
|
+
limit: int = Field(default=20, ge=1, le=100)
|
|
71
|
+
offset: int = Field(default=0, ge=0)
|
|
72
|
+
categories: list[SearchCategory] = Field(default_factory=list)
|
|
73
|
+
created_after: datetime | None = None
|
|
74
|
+
created_before: datetime | None = None
|
|
75
|
+
|
|
76
|
+
@field_validator("categories")
|
|
77
|
+
@classmethod
|
|
78
|
+
def _deduplicate_categories(
|
|
79
|
+
cls, categories: list[SearchCategory]
|
|
80
|
+
) -> list[SearchCategory]:
|
|
81
|
+
"""Preserve the first requested category while removing duplicates."""
|
|
82
|
+
return list(dict.fromkeys(categories))
|
|
83
|
+
|
|
84
|
+
@model_validator(mode="after")
|
|
85
|
+
def _validate_date_range(self) -> "GlobalSearchQuery":
|
|
86
|
+
if (
|
|
87
|
+
self.created_after is not None
|
|
88
|
+
and self.created_before is not None
|
|
89
|
+
and self.created_after > self.created_before
|
|
90
|
+
):
|
|
91
|
+
raise ValueError("created_after must be before or equal to created_before")
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def normalized_query(self) -> str:
|
|
96
|
+
"""Return query text with leading and trailing whitespace removed."""
|
|
97
|
+
return self.query.strip()
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def is_blank(self) -> bool:
|
|
101
|
+
"""Whether the route must return an empty response without searching."""
|
|
102
|
+
return not self.normalized_query
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class GlobalSearchResponse(BaseModel):
|
|
106
|
+
"""Public response shape shared by typeahead and full global search."""
|
|
107
|
+
|
|
108
|
+
query: str
|
|
109
|
+
is_typeahead: bool
|
|
110
|
+
suggestions: list[SearchSuggestion] = Field(default_factory=list)
|
|
111
|
+
total: int = Field(default=0, ge=0)
|
|
112
|
+
limit: int = Field(default=20, ge=1)
|
|
113
|
+
offset: int = Field(default=0, ge=0)
|
|
114
|
+
facets: dict[str, dict[str, int]] | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class SearchResult(BaseModel):
|
|
118
|
+
"""Result from a search query.
|
|
119
|
+
|
|
120
|
+
Contains matching items with pagination metadata.
|
|
121
|
+
|
|
122
|
+
Attributes:
|
|
123
|
+
items: List of matching documents
|
|
124
|
+
total: Total count of matches
|
|
125
|
+
limit: Maximum results per page
|
|
126
|
+
offset: Number of results skipped
|
|
127
|
+
facets: Optional facet/aggregation results
|
|
128
|
+
highlights: Optional highlighted snippets per doc ID
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
items: list[dict[str, Any]]
|
|
132
|
+
total: int
|
|
133
|
+
limit: int
|
|
134
|
+
offset: int
|
|
135
|
+
facets: dict[str, dict[str, int]] | None = None
|
|
136
|
+
highlights: dict[str, dict[str, str]] | None = None
|
|
137
|
+
|
|
138
|
+
@computed_field # type: ignore[prop-decorator]
|
|
139
|
+
@property
|
|
140
|
+
def has_more(self) -> bool:
|
|
141
|
+
"""Check if more results exist beyond current page."""
|
|
142
|
+
return self.offset + len(self.items) < self.total
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class SearchProtocol(Protocol):
|
|
146
|
+
"""Protocol defining the contract for full-text search.
|
|
147
|
+
|
|
148
|
+
This is the primary port for search operations in the hexagonal
|
|
149
|
+
architecture. Implementations handle different search engines.
|
|
150
|
+
|
|
151
|
+
Global-search convention:
|
|
152
|
+
|
|
153
|
+
* ``doctype="*"`` searches every provider-supported DocType. Providers
|
|
154
|
+
that cannot search a DocType return an empty result rather than raising.
|
|
155
|
+
* Global results use :class:`SearchSuggestion` dictionaries in
|
|
156
|
+
:attr:`SearchResult.items`; legacy document dictionaries remain valid for
|
|
157
|
+
backwards compatibility.
|
|
158
|
+
* A composite provider invokes providers concurrently, preserves configured
|
|
159
|
+
provider order for equal scores, deduplicates by suggestion ``id``, and
|
|
160
|
+
applies ``offset``/``limit`` only after merging and deduplication.
|
|
161
|
+
* ``SearchResult.total`` for a composite is the post-deduplication count
|
|
162
|
+
before paging. Individual provider failures are isolated and observable.
|
|
163
|
+
|
|
164
|
+
Implementations include:
|
|
165
|
+
- MeilisearchAdapter: Meilisearch search engine
|
|
166
|
+
- ElasticsearchAdapter: Elasticsearch/OpenSearch
|
|
167
|
+
- WhooshAdapter: Pure Python search (for development)
|
|
168
|
+
|
|
169
|
+
Example usage:
|
|
170
|
+
search: SearchProtocol = container.get(SearchProtocol)
|
|
171
|
+
|
|
172
|
+
# Index a document
|
|
173
|
+
await search.index("Todo", "todo-001", {"title": "Buy milk"})
|
|
174
|
+
|
|
175
|
+
# Search
|
|
176
|
+
result = await search.search("Todo", "milk", limit=10)
|
|
177
|
+
# Process results
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
async def index(
|
|
181
|
+
self,
|
|
182
|
+
doctype: str,
|
|
183
|
+
doc_id: str,
|
|
184
|
+
doc: dict[str, Any],
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Index a document for searching.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
doctype: DocType name (used as index name)
|
|
190
|
+
doc_id: Document identifier
|
|
191
|
+
doc: Document data to index
|
|
192
|
+
"""
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
async def delete_index(
|
|
196
|
+
self,
|
|
197
|
+
doctype: str,
|
|
198
|
+
doc_id: str,
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Remove a document from the search index.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
doctype: DocType name
|
|
204
|
+
doc_id: Document identifier
|
|
205
|
+
"""
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
async def search(
|
|
209
|
+
self,
|
|
210
|
+
doctype: str,
|
|
211
|
+
query: str,
|
|
212
|
+
*,
|
|
213
|
+
filters: dict[str, Any] | None = None,
|
|
214
|
+
limit: int = 20,
|
|
215
|
+
offset: int = 0,
|
|
216
|
+
) -> SearchResult:
|
|
217
|
+
"""Search for documents.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
doctype: DocType name to search in
|
|
221
|
+
query: Search query string
|
|
222
|
+
filters: Optional field filters
|
|
223
|
+
limit: Maximum results to return
|
|
224
|
+
offset: Number of results to skip
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
SearchResult with matching documents
|
|
228
|
+
"""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
async def reindex(self, doctype: str) -> int:
|
|
232
|
+
"""Rebuild the entire index for a DocType.
|
|
233
|
+
|
|
234
|
+
Fetches all documents from the repository and re-indexes them.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
doctype: DocType name to reindex
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Number of documents indexed
|
|
241
|
+
"""
|
|
242
|
+
...
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
__all__ = [
|
|
246
|
+
"GlobalSearchQuery",
|
|
247
|
+
"GlobalSearchResponse",
|
|
248
|
+
"SearchCategory",
|
|
249
|
+
"SearchProtocol",
|
|
250
|
+
"SearchResult",
|
|
251
|
+
"SearchSuggestion",
|
|
252
|
+
]
|