lex-app 2.2.0__tar.gz → 2.2.2__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.
- lex_app-2.2.2/MANIFEST.in +15 -0
- {lex_app-2.2.0/lex_app.egg-info → lex_app-2.2.2}/PKG-INFO +2 -1
- lex_app-2.2.2/lex/_version.py +1 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/serializers/base_serializers.py +50 -3
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/List.py +16 -1
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/models/CalculationLog.py +53 -0
- lex_app-2.2.2/lex/audit_logging/utils/latest_calculation.py +105 -0
- lex_app-2.2.2/lex/core/migrations/0001_bitemporal_activation.py +62 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/models/CalculationModel.py +11 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/services/MetaHistory.py +29 -0
- lex_app-2.2.2/lex/core/services/activation_applier.py +167 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/services/bitemporal_signals.py +40 -3
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/settings.py +52 -3
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/__init__.py +3 -1
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/embed.py +333 -15
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/sidebar.py +135 -64
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/proxy.py +41 -25
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/streamlit_app.py +22 -29
- {lex_app-2.2.0/lex/tests/e2e → lex_app-2.2.2/lex/test_project/tests}/_e2e_test_case.py +22 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/models.py +5 -0
- lex_app-2.2.2/lex/test_project/tests/calculation_logging/test_15i_latest_run_on_the_row.py +260 -0
- lex_app-2.2.2/lex/test_project/tests/calculation_logging/test_15j_failed_run_keeps_its_log.py +95 -0
- lex_app-2.2.2/lex/test_project/tests/history/test_5o_activation_applier.py +727 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ad_proxy_assets_and_session_durability.py +49 -9
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1aj_streamlit_sidebar_chrome.py +133 -63
- lex_app-2.2.2/lex/test_project/tests/init/test_1an_proxy_http2_safe_headers.py +331 -0
- lex_app-2.2.2/lex/test_project/tests/init/test_1ao_flow_composable_steps.py +359 -0
- lex_app-2.2.2/lex/test_project/tests/init/test_1ao_frontend_resolution.py +101 -0
- {lex_app-2.2.0 → lex_app-2.2.2/lex_app.egg-info}/PKG-INFO +2 -1
- {lex_app-2.2.0 → lex_app-2.2.2}/lex_app.egg-info/SOURCES.txt +9 -19
- {lex_app-2.2.0 → lex_app-2.2.2}/lex_app.egg-info/requires.txt +1 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/pyproject.toml +9 -1
- {lex_app-2.2.0 → lex_app-2.2.2}/requirements.txt +10 -0
- lex_app-2.2.0/MANIFEST.in +0 -7
- lex_app-2.2.0/lex/_version.py +0 -1
- lex_app-2.2.0/lex/react/build/.frontend-version.json +0 -6
- lex_app-2.2.0/lex/react/build/apple-touch-icon.png +0 -0
- lex_app-2.2.0/lex/react/build/assets/SessionAuthGate-BB6V9OL2.js +0 -647
- lex_app-2.2.0/lex/react/build/assets/SessionAuthGate-Cpfxv-bc.css +0 -1
- lex_app-2.2.0/lex/react/build/assets/embed-CCZwn8AA.js +0 -1
- lex_app-2.2.0/lex/react/build/assets/index.es-HIQvQaz0.js +0 -18
- lex_app-2.2.0/lex/react/build/assets/main-Cfrrc9ln.js +0 -730
- lex_app-2.2.0/lex/react/build/assets/main-D8qVAgsi.css +0 -1
- lex_app-2.2.0/lex/react/build/config.js +0 -8
- lex_app-2.2.0/lex/react/build/embed.html +0 -65
- lex_app-2.2.0/lex/react/build/favicon-16x16.png +0 -0
- lex_app-2.2.0/lex/react/build/favicon-32x32.png +0 -0
- lex_app-2.2.0/lex/react/build/favicon.ico +0 -0
- lex_app-2.2.0/lex/react/build/index.html +0 -291
- lex_app-2.2.0/lex/react/build/logo192.png +0 -0
- lex_app-2.2.0/lex/react/build/logo512.png +0 -0
- lex_app-2.2.0/lex/react/build/manifest.json +0 -25
- lex_app-2.2.0/lex/react/build/mockServiceWorker.js +0 -307
- lex_app-2.2.0/lex/react/build/robots.txt +0 -3
- {lex_app-2.2.0 → lex_app-2.2.2}/LICENSE.txt +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/README.md +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/generate_pycharm_configs.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/__main__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/BackendHealthConsumer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/CalculationLogConsumer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/CalculationsConsumer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/LogConsumer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/ModelDataUpdateConsumer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/UpdateCalculationStatusConsumer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/consumers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/filters/FilterTreeNode.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/filters/GenericFilters.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/filters/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/middleware/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/middleware/keycloak_permissions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/serializers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/serializers/sensitive_fields.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/Context.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/api_key_requests.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/collection_utils.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/converters.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/helpers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/utils/temporal.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/LexLoggerView/LexLoggerView.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/LexLoggerView/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/ModelStructureObtainView.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/authentication/KeycloakManager.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/authentication/UserAPIView.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/authentication/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/calculations/ActiveCalculations.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/calculations/CleanCalculations.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/calculations/DownloadMarkdownPdf.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/calculations/InitCalculationLogs.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/calculations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/file_operations/FileDownload.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/file_operations/ModelExport.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/file_operations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/global_search_for_models/Search.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/global_search_for_models/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/lex_api/LexAPI.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/lex_api/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/CalculationLogTreeView.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/History.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/Many.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/One.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/filter_backends.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/mixins/DestroyOneWithPayloadMixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/mixins/ModelEntryProviderMixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/mixins/PermissionAwareSerializerMixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/mixins/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/serializers/CalculationLogTreeSerializer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_entries/serializers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_info/Fields.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_info/Widgets.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/model_info/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/pagination.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/permissions/ModelPermissions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/permissions/UserPermission.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/permissions/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/process_flow/CreateOrUpdate.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/process_flow/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/project_info/ProjectInfo.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/project_info/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/rbac/RBACInfo.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/rbac/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/sharepoint/DeleteUnusedFiles.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/sharepoint/SharePointFileDownload.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/sharepoint/SharePointPreview.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/sharepoint/SharePointShareLink.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/sharepoint/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views/streamlit/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/api/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/dark-lex-logo.svg +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-bg.jpg +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-logo.png +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-logo.svg +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-test-report/email.html +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-test-report/lex-icon.png +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-test-report/lex-logo.png +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/assets/lex-test-report/pdf.html +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/handlers/ConsoleHandler.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/handlers/LexLogger.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/handlers/WebSocketHandler.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/handlers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0001_initial.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0002_auditlog_created_at_auditlog_edited_at_and_more.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0003_alter_auditlog_created_at_alter_auditlog_edited_at_and_more.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0004_remove_auditlog_audit_fields.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0005_calculationlog_calculationid_index.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0006_alter_auditlog_calculation_id.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/0007_calculationlog_heading.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/mixins/AuditLogMixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/mixins/BulkAuditLogMixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/mixins/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/models/AuditLog.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/models/AuditLogStatus.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/models/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/serializers/AuditLogMixinSerializer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/serializers/AuditLogSerializer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/serializers/CalculationLogSerializer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/serializers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/tests/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/CacheManager.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/ContextResolver.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/DataModels.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/InitialDataAuditLogger.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/ModelContext.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/WebSocketNotifier.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/calculation_audit.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/config.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/content_types.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/utils/legacy_audit_payload.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/audit_logging/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/authentication_backends/BearerMiddlewareAuthentication.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/authentication_backends/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/createprofiles.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/delete_keycloak_resources.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/keycloak_backup.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/keycloak_init_bak.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/keycloak_rollback_bak.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/management/commands/register_keycloak_resources.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/middleware.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/migrations/0001_initial.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/models/Profile.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/models/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/utils/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/utils/lex_authentication.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/views/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/views/permissions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/views/token_views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/authentication/views/user_api.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/backfill_history_sql.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/bin/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/bin/lex.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/calculated_updates/ObjectsToRecalculateStore.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/calculated_updates/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/calculated_updates/update_handler.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/cancellation/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/cancellation/cluster_cancel_index.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/config.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/exceptions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/fields/PDF_field.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/fields/XLSX_field.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/fields/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/management/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/management/commands/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/management/commands/bootstrap_callback_server.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/middleware/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/middleware/embed_token_auth.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/middleware/embed_xframe.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/mixins/CalculatedModelMixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/mixins/ModelModificationRestriction.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/mixins/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/models/HTMLReport.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/models/LexModel.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/models/Process.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/models/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/services/Bitemporal.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/services/StandardHistory.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/services/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/services/signal_registry.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/signals/ActiveCalculationStateStore.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/signals/CalculationSignals.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/signals/ModelMutationSignal.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/signals/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tasks/CeleryTaskDispatcher.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tasks/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tests/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tests/_bitemporal_test_case.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tests/bitemporal_test_settings.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tests/check_schema.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/tests/test_calculation_history_transitions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/transactions/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/transactions/transactions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/core/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/extract_legacy_logs.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/fix_v1_migration.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/full_migration_workflow.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/helpers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/helpers/cache_tables.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/admin/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/admin/legacy_calculation_id_admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/admin/legacy_calculation_log_admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/admin/legacy_log_admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/admin/legacy_user_change_log_admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/admin/read_only_admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/migrations/0001_initial.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/migrations/0002_alter_legacycalculationid_options_and_more.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/migrations/0003_legacylog.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/models/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/models/legacy_calculation_id.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/models/legacy_calculation_log.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/models/legacy_log.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/models/legacy_user_change_log.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/serializers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/legacy_data/serializers/legacy_data_serializers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/asgi.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/entrypoint.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/exceptions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/heartbeat.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/redis_keys.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/registry.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_recovery/supervisor.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/celery_tasks.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/design_system/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/design_system/lex_tokens.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/fast_health.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/keycloak_exclusions.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/Init2.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/backfill_audit_logging.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/backfill_bitemporal_history.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/bootstrap_keycloak.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/capture_db_tables.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/capture_migration_state.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/create_db.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/detect_model_changes.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/full_migration_workflow.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/generate_legacy_freeze_manifest.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/init.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/lex_migrate.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/normalize_is_calculated.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/rebase_incident_datetimes.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/rollback_migration_state.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/run_recovery_supervisor.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/sync_keycloak.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/test_backfill_audit_logging_command.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/test_backfill_audit_logging_payload.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/test_bootstrap_callback_server.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/test_init_command.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/test_lex_pytest_report_flags.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/management/commands/tests/test_migration_workflow_commands.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/migrations/0001_initial.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/migrations/0002_delete_asoftestmodel_and_more.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/routing.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/runtime_health.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/simple_history_config.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/Streamlit.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/_lex_view_component/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/_lex_view_component/frontend/index.html +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/_widget_host_component/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/_widget_host_component/frontend/index.html +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/eager_frames.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/examples/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/examples/_streamlit_structure.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/quackback.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/theme/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/theme/config_writer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/theme/mapping.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/theme/tokens.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/widgets/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/widgets/host.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/widgets/keys.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/streamlit/widgets/spec.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/tests/ProcessAdminTestCase.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/tests/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/tests/create_db.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/urls.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/utils.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/lex_app/wsgi.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/manage.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/mcp_server/tools/embed.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models/ModelCollection.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models/ModelContainer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models/ModelProcessAdmin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models/constants.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models/utils.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/settings/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/settings/process_admin_settings.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/sites/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/sites/process_admin_site.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/tests/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/tests/django_test_settings.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/tests/urls.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/utils/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/utils/bitemporal_sync.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/utils/local_scheduler.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/utils/model_registration.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/utils/model_structure.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/utils/model_structure_builder.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/views/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/views/model_relation_views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/process_admin/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/react/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/react/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/react/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/react/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/react/tests.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/react/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/restore_legacy_logs.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/runtime_config.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/sanitize_v2_migrations.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/streamlit_theme.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/lex_config.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/_authenticated_e2e_test_case.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10a_crud_endpoints.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10b_history_endpoint.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10c_many_endpoint.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10e_schema_introspection.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10f_global_search.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10g_one_endpoint_lifecycle.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10h_lex_api_sdk.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10i_fields_view_and_list_ui.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10j_filter_backends_and_storage.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10k_destroy_with_payload_mixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10l_file_download_view.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10m_calculation_log_tree.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/api_layer/test_10n_active_calculations_endpoint.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/extra_models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6a_api_audit.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6b_calculation_audit.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6c_actor_resolution.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6d_payload_and_gfk.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6e_bulk_audit.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6f_resilience.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6g_immutability.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6h_cache_manager.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6i_calculation_via_api.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6j_audit_utils.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6k_model_contracts.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6l_context_and_gfk_resolution.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6m_lexlogger_and_websocket.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6n_audit_log_serializers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6o_bulk_audit_normalize.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/audit_logging/test_6q_root_detection_with_headings.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15a_builder_api.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15b_child_node_creation.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15c_silent_and_mixed.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15d_three_level_chain.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15e_combinatorial_pipeline.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15f_save_boundary.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15g_heading_context.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculation_logging/test_15h_pdf_export.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7a_atomic.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7b_non_atomic.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7c_hierarchy.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7d_state_guards.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7e_persistence_internals.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7f_combination_engine.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7g_create_pipeline.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7h_celery_dispatch.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7i_atomicity_matrix.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7j_three_level_matrix.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7k_exceptions_restrictions_xlsx.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7l_recalc_queue.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7m_calc_signals.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7n_cancellation.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7o_fk_violation_abort.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7p_streaming_expansion.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7q_worker_default_dispatch.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/calculations/test_7s_calculation_audit_columns.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8a_sync_fallback.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8aa_postrun_shutdown_guard.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8ab_dispatcher_join_worker_safe.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8ac_sync_fallback_idempotent.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8ad_dispatched_task_cancel_marker.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8b_dispatch_context.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8c_calculation_audit_columns.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8g_task_infrastructure.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8h_eager_end_to_end.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8i_scope_contracts.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8j_task_bodies.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8k_redis_broker_integration.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8l_celery_dispatcher.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8m_undecorated_dispatch.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8u_cancel_revoke.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8v_cluster_cascade_cancel.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8w_recovery_terminal_guard.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8x_startup_reset_recovery_handoff.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/celery_async/test_8y_beat_recovery_driver.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2a_create.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2b_read.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2c_update.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2d_delete.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2e_bulk.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2f_missing_scenarios.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2f_ordering.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2g_filtering.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2i_cancel_endpoint.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/crud_api/test_2j_instance_api_key.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/design_system/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/design_system/test_lex_tokens.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13a_legacy_export.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13b_ag_grid_export.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13c_grouped_selected.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13d_auth_edge.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13e_streaming_fast_export.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13f_export_timezone.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13g_xlsx_field_timezone.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/exports/test_13h_xlsx_field_binding.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/fixtures/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/gate_selftest/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/gate_selftest/test_gate_selftest.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5_11_fallback_snapshot.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5a_basic_history.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5b_calculation_history.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5c_history_api.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5g_valid_to_chaining.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5h_suppression_toolkit.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5i_api_contract.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5j_snapshot_actor.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5k_metahistory.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5l_future_valid_from.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/history/test_5m_asof_edit_time.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/initial_data/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/initial_data/test_1c_initial_data.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/initial_data/test_1f_seed_idempotency.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/initial_data/test_1i_initial_data_journey.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1a_lex_setup.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1aa_embedded_token_renewal.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ab_ignored_role_policy_cleanup.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ac_streamlit_session_survival.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ac_widget_host_manifest.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ad_streamlit_theme_follower.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ae_streamlit_theme.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1af_theme_switch_preserves_brand.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ag_spa_asset_caching.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ah_metadata_cache_headers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ai_eager_component_frames.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1ak_widget_keys_and_flow.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1al_example_dashboard.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1am_quackback_launcher.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1b_default_authz.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1b_lex_init.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1d_init_helpers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1e_keycloak_sync_manager.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1f_keycloak_drift.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1g_sync_manager_admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1h_bootstrap_flow.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1i_rebase_incident_datetimes.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1j_client_preflight.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1k_client_preflight_integration.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1l_full_pipeline_integration.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1m_cli_commands.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1n_init_helper_paths.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1o_lazy_imports_and_helpers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1p_settings_urls_views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1q_migration_files_complete.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1r_fetched_datetime_zone.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1s_log_cleanup_and_lex_debug.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1t_disable_server_side_cursors.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1u_fast_health_asgi.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1v_scheduler_tz_invariant.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1w_recovery_default_deployment_target.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1x_runtime_health_metadata.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1y_ide_run_configs.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/init/test_1z_proxy_iframe_breakout.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/test_journey_a_invoice_lifecycle.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/test_journey_b_portfolio_calculation.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/test_journey_c_role_aware_visibility.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/test_journey_d_audit_trail.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/journeys/test_journey_e_validation_gate.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4a_field_level.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4b_action_level.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4c_keycloak_fallback.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4d_user_context.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4e_filter_backend.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4f_serializer_mixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4g_user_permissions_api.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4h_full_deny_matrix.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4i_permission_helpers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4j_filter_backends.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/permissions/test_4m_api_key_middleware.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/test_14a_query_params.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/test_14b_ag_post_filter_model.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/test_14c_ag_post_group_pivot.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/test_14e_secondary_branches.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/queries/test_14h_list_query_paths.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12a_history_meta_scopes.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12a_read_contract.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12b_type_roundtrip.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12c_list_and_many.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12d_audit_log_payload.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12e_factory.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12f_write_paths.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12g_datetime_tz_ambiguity.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12h_filefield_clear.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12i_fk_display_names.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12j_datetime_roundtrip_convention.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/serializers/test_12k_sensitive_field_denylist.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/test_9_7_suppression_guards.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/test_9a_state_and_broadcast.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/test_9c_cache_cleanup.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/test_9d_active_calculation_state_store.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/test_9e_model_mutation_broadcast.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/signals_ws/test_9f_core_consumers.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/_stress_test_case.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11a_seed_baseline.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11b_list.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11c_export.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11d_period_calc.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11e_bulk_api.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11f_growth_and_scale.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11g_fk_heavy_export.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11h_calc_log_overhead.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/stress/test_11i_save_fingerprint.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3a_pre_validation.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3b_post_validation.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3c_hook_ordering.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3d_recursion_guard.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3e_snapshot_lifecycle.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3f_lean_initial_state.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/test_project/tests/validation_hooks/test_3g_aware_datetime_assignment.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/e2e/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/e2e/_authenticated_e2e_test_case.py +0 -0
- {lex_app-2.2.0/lex/test_project/tests → lex_app-2.2.2/lex/tests/e2e}/_e2e_test_case.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/calculation/test_calculated_model_mixin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/calculation/test_calculation_wait_contexts.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/infra/test_ai_asset_mode_sync.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/infra/test_ai_update_bootstrap.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/infra/test_ai_windows_parity.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/infra/test_celery_recovery.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/infra/test_mcp_server_sdk_compat.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tests/unit/infra/test_worker_self_termination.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/ai_dashboard.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/ai_faq.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/ai_issue_report.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/ai_worktree.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/mcp_mode_invoke.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/project_root.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/setup_with_ai.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/test_groups.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/tools/verify_ai_assets.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/admin.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/apps.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/channel_layer.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/config/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/config/generic_app_config.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/decorators/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/decorators/singleton.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/import_system/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/import_system/import_utils.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/import_system/model_loader.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/import_system/module_finder.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/migrations/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/models.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/storage/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/storage/custom_storage.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/utils/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/utils/gcs_utils.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex/utilities/views.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex_app.egg-info/dependency_links.txt +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex_app.egg-info/entry_points.txt +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/lex_app.egg-info/top_level.txt +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/migrations_logic/__init__.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/migrations_logic/fix_migrations.py +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/setup.cfg +0 -0
- {lex_app-2.2.0 → lex_app-2.2.2}/setup.py +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
recursive-include lex/react *
|
|
2
|
+
# The compiled SPA is NOT shipped in the distribution. It arrives as the
|
|
3
|
+
# lex-app-frontend dependency, pinned in requirements.txt, and shipping a
|
|
4
|
+
# second copy meant every release carried a frozen bundle that nothing served
|
|
5
|
+
# — 57% of the wheel, stale from the day it was built.
|
|
6
|
+
#
|
|
7
|
+
# It stays in the source tree: settings.py falls back to it so a git checkout
|
|
8
|
+
# works without installing the frontend package.
|
|
9
|
+
recursive-exclude lex/react/build *
|
|
10
|
+
recursive-include lex/assets *
|
|
11
|
+
include pyproject.toml
|
|
12
|
+
include requirements*.txt
|
|
13
|
+
include generate_pycharm_configs.py
|
|
14
|
+
global-exclude __pycache__ *.py[cod] .DS_Store
|
|
15
|
+
recursive-exclude * __pycache__
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lex-app
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: A Python / Django library to create business applications easily with complex logic
|
|
5
5
|
Author-email: Melih Sünbül <m.sunbul@excellence-cloud.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -60,6 +60,7 @@ Requires-Dist: psycopg2-binary
|
|
|
60
60
|
Requires-Dist: django-celery-beat
|
|
61
61
|
Requires-Dist: pytest
|
|
62
62
|
Requires-Dist: cryptography
|
|
63
|
+
Requires-Dist: lex-app-frontend~=2.2.0
|
|
63
64
|
Dynamic: license-file
|
|
64
65
|
|
|
65
66
|
LEX App Documentation - Previously known as DPAG :)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.2"
|
|
@@ -7,6 +7,7 @@ from django.db.models import Model, ForeignKey
|
|
|
7
7
|
from django.db.models.fields import DateTimeField, DateField, TimeField
|
|
8
8
|
from lex.api.utils.helpers import can_read_with_default_permission_scope
|
|
9
9
|
from lex.audit_logging.utils.content_types import safe_get_content_type
|
|
10
|
+
from lex.audit_logging.utils.latest_calculation import is_calculation_model
|
|
10
11
|
from lex.core.models.LexModel import LexModel, UserContext
|
|
11
12
|
from rest_framework import serializers, viewsets
|
|
12
13
|
|
|
@@ -21,6 +22,10 @@ from lex.api.serializers.sensitive_fields import (
|
|
|
21
22
|
ID_FIELD_NAME = "id_field"
|
|
22
23
|
SHORT_DESCR_NAME = "short_description"
|
|
23
24
|
LEX_SCOPES_NAME = "lex_reserved_scopes"
|
|
25
|
+
# The newest run a calculation row started, and whether it has one. Declared
|
|
26
|
+
# only on serializers of CalculationModel subclasses; see _calculation_run_fields.
|
|
27
|
+
CALCULATION_ID_NAME = "lex_reserved_calculation_id"
|
|
28
|
+
HAS_CALCULATION_LOG_NAME = "lex_reserved_has_calculation_log"
|
|
24
29
|
|
|
25
30
|
# --- MODULE-LEVEL CACHES (populated lazily, persist for process lifetime) ---
|
|
26
31
|
|
|
@@ -39,6 +44,28 @@ def _get_lexmodel_fields() -> set:
|
|
|
39
44
|
return _lexmodel_fields
|
|
40
45
|
|
|
41
46
|
|
|
47
|
+
def _calculation_run_fields(model) -> dict:
|
|
48
|
+
"""The two latest-run fields, for calculation models only.
|
|
49
|
+
|
|
50
|
+
Values are read from attributes the list view sets for a whole page at
|
|
51
|
+
once (``annotate_latest_calculation``). Nothing here queries per row.
|
|
52
|
+
"""
|
|
53
|
+
if not is_calculation_model(model):
|
|
54
|
+
return {}
|
|
55
|
+
# Explicit method names, deliberately not the get_<field> defaults:
|
|
56
|
+
# _wrap_custom_serializer builds (LexSerializer, custom_cls), so a method
|
|
57
|
+
# named get_lex_reserved_has_calculation_log on LexSerializer would come
|
|
58
|
+
# first in the MRO and shadow AuditLogDefaultSerializer's own getter.
|
|
59
|
+
return {
|
|
60
|
+
CALCULATION_ID_NAME: serializers.SerializerMethodField(
|
|
61
|
+
method_name="_lex_latest_calculation_id"
|
|
62
|
+
),
|
|
63
|
+
HAS_CALCULATION_LOG_NAME: serializers.SerializerMethodField(
|
|
64
|
+
method_name="_lex_has_latest_calculation_log"
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
42
69
|
# Cache: model-name -> model-class lookup for _resolve_target_model
|
|
43
70
|
_model_lookup: dict | None = None
|
|
44
71
|
|
|
@@ -364,6 +391,17 @@ class LexSerializer(serializers.ModelSerializer):
|
|
|
364
391
|
|
|
365
392
|
return target
|
|
366
393
|
|
|
394
|
+
# ------------------------------------------------------------------
|
|
395
|
+
# Latest calculation run — read through method_name, see _calculation_run_fields
|
|
396
|
+
# ------------------------------------------------------------------
|
|
397
|
+
def _lex_latest_calculation_id(self, instance):
|
|
398
|
+
"""The newest run started from this row, when the list resolved it."""
|
|
399
|
+
return getattr(instance, "_lex_calculation_id", None)
|
|
400
|
+
|
|
401
|
+
def _lex_has_latest_calculation_log(self, instance):
|
|
402
|
+
"""Whether that run exists. Unannotated rows answer False, never a query."""
|
|
403
|
+
return bool(getattr(instance, "_has_calculation_log", False))
|
|
404
|
+
|
|
367
405
|
# ------------------------------------------------------------------
|
|
368
406
|
# Scopes computation
|
|
369
407
|
# ------------------------------------------------------------------
|
|
@@ -680,7 +718,10 @@ class LexSerializer(serializers.ModelSerializer):
|
|
|
680
718
|
_SYSTEM_FIELDS = frozenset({
|
|
681
719
|
'history_id', 'history_date', 'history_type', 'history_user', 'history_change_reason',
|
|
682
720
|
'valid_from', 'valid_to',
|
|
683
|
-
'calculation_record', 'lex_reserved_scopes', 'id', 'id_field', SHORT_DESCR_NAME
|
|
721
|
+
'calculation_record', 'lex_reserved_scopes', 'id', 'id_field', SHORT_DESCR_NAME,
|
|
722
|
+
# Without these two, to_representation's visibility filter strips the
|
|
723
|
+
# run fields silently and the log button never appears.
|
|
724
|
+
CALCULATION_ID_NAME, HAS_CALCULATION_LOG_NAME,
|
|
684
725
|
})
|
|
685
726
|
|
|
686
727
|
def to_representation(self, instance):
|
|
@@ -870,13 +911,17 @@ def model2serializer(model, fields=None, name_suffix=""):
|
|
|
870
911
|
pk_alias = serializers.ReadOnlyField(default=model._meta.pk.name)
|
|
871
912
|
|
|
872
913
|
# ensure our internal fields are always present
|
|
873
|
-
|
|
914
|
+
run_fields = _calculation_run_fields(model)
|
|
915
|
+
all_fields = list(fields) + [
|
|
916
|
+
ID_FIELD_NAME, SHORT_DESCR_NAME, "id", LEX_SCOPES_NAME, *run_fields,
|
|
917
|
+
]
|
|
874
918
|
|
|
875
919
|
return type(
|
|
876
920
|
class_name,
|
|
877
921
|
(RestApiModelSerializerTemplate,),
|
|
878
922
|
{
|
|
879
923
|
ID_FIELD_NAME: pk_alias,
|
|
924
|
+
**run_fields,
|
|
880
925
|
"Meta": type(
|
|
881
926
|
"Meta",
|
|
882
927
|
(RestApiModelSerializerTemplate.Meta,),
|
|
@@ -888,11 +933,12 @@ def model2serializer(model, fields=None, name_suffix=""):
|
|
|
888
933
|
|
|
889
934
|
def _wrap_custom_serializer(custom_cls, model_class):
|
|
890
935
|
meta = getattr(custom_cls, "Meta", type("Meta", (), {}))
|
|
936
|
+
run_fields = _calculation_run_fields(model_class)
|
|
891
937
|
existing_fields = getattr(meta, "fields", "__all__")
|
|
892
938
|
if existing_fields != "__all__":
|
|
893
939
|
existing = list(existing_fields)
|
|
894
940
|
# make sure all internal fields are present, including lex_reserved_scopes
|
|
895
|
-
for extra in (ID_FIELD_NAME, SHORT_DESCR_NAME, "id", LEX_SCOPES_NAME):
|
|
941
|
+
for extra in (ID_FIELD_NAME, SHORT_DESCR_NAME, "id", LEX_SCOPES_NAME, *run_fields):
|
|
896
942
|
if extra not in existing:
|
|
897
943
|
existing.append(extra)
|
|
898
944
|
new_fields = existing
|
|
@@ -935,6 +981,7 @@ def _wrap_custom_serializer(custom_cls, model_class):
|
|
|
935
981
|
ID_FIELD_NAME: serializers.ReadOnlyField(default=model_class._meta.pk.name),
|
|
936
982
|
SHORT_DESCR_NAME: serializers.SerializerMethodField(),
|
|
937
983
|
"get_short_description": lambda self, obj: str(obj),
|
|
984
|
+
**run_fields,
|
|
938
985
|
"Meta": NewMeta,
|
|
939
986
|
}
|
|
940
987
|
# Use a bare ``ReadOnlyField()`` (DRF defaults ``source`` to the field
|
|
@@ -892,7 +892,22 @@ class ListModelEntries(ModelEntryProviderMixin, ListAPIView):
|
|
|
892
892
|
|
|
893
893
|
row_count = qs.count()
|
|
894
894
|
page_qs = qs[start_row:end_row]
|
|
895
|
-
|
|
895
|
+
|
|
896
|
+
from lex.audit_logging.utils.latest_calculation import (
|
|
897
|
+
annotate_latest_calculation,
|
|
898
|
+
is_calculation_model,
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
if is_calculation_model(self._ag_model_class):
|
|
902
|
+
# A calculation row carries only `is_calculated`; the grid needs to
|
|
903
|
+
# know which rows have a finished log to open. Resolved for the
|
|
904
|
+
# whole page in one query and set on the instances the serializer
|
|
905
|
+
# reads — the same shape as the audit log branch above.
|
|
906
|
+
page_rows = list(page_qs)
|
|
907
|
+
annotate_latest_calculation(page_rows, self._ag_model_class)
|
|
908
|
+
serializer = self.get_serializer(page_rows, many=True)
|
|
909
|
+
else:
|
|
910
|
+
serializer = self.get_serializer(page_qs, many=True)
|
|
896
911
|
return {
|
|
897
912
|
"rowData": serializer.data,
|
|
898
913
|
"rowCount": row_count,
|
|
@@ -203,6 +203,59 @@ class CalculationLog(models.Model):
|
|
|
203
203
|
log_entry.calculation_log = (log_entry.calculation_log or "") + f"\n{message}"
|
|
204
204
|
log_entry.save(update_fields=["calculation_log"])
|
|
205
205
|
|
|
206
|
+
@classmethod
|
|
207
|
+
def keep_rolled_back_log(cls, calculation_id, root_record, root_instance) -> bool:
|
|
208
|
+
"""
|
|
209
|
+
Keep what a failed run logged when its transaction took the rows with it.
|
|
210
|
+
|
|
211
|
+
`log` persists on commit, so a calculation that fails inside its
|
|
212
|
+
transaction — every `is_atomic` model — is rolled back with every row it
|
|
213
|
+
wrote: the steps that succeeded vanish with the one that failed. The live
|
|
214
|
+
cache is not transactional. It holds the run's whole log, in order, under
|
|
215
|
+
the root's key until the run ends, so the failure paths call this just
|
|
216
|
+
before they purge it, and it writes that text back as the run's log: one
|
|
217
|
+
row on the root record, the same text the live stream showed.
|
|
218
|
+
|
|
219
|
+
Does nothing when rows for the run survived (a run outside a transaction
|
|
220
|
+
keeps them) or when the cache holds nothing for it. Never raises — a
|
|
221
|
+
failure path must not fail again on its way to reporting the first one.
|
|
222
|
+
"""
|
|
223
|
+
logger = logging.getLogger("lex.calclog")
|
|
224
|
+
try:
|
|
225
|
+
if not calculation_id or not root_record or root_instance is None:
|
|
226
|
+
return False
|
|
227
|
+
if cls.objects.filter(calculationId=calculation_id).exists():
|
|
228
|
+
return False
|
|
229
|
+
text = CacheManager.get_message(
|
|
230
|
+
CacheManager.build_cache_key(root_record, calculation_id)
|
|
231
|
+
)
|
|
232
|
+
if not text:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
from lex.audit_logging.models.AuditLog import AuditLog
|
|
236
|
+
|
|
237
|
+
# The run's first audit row was written by the request that started
|
|
238
|
+
# it, before the transaction, so it survived; rows the calculation
|
|
239
|
+
# wrote itself went with the rollback.
|
|
240
|
+
audit_log = (
|
|
241
|
+
AuditLog.objects.filter(calculation_id=calculation_id).order_by("id").first()
|
|
242
|
+
)
|
|
243
|
+
cls.objects.create(
|
|
244
|
+
calculationId=calculation_id,
|
|
245
|
+
audit_log=audit_log,
|
|
246
|
+
content_type=ContentType.objects.get_for_model(type(root_instance)),
|
|
247
|
+
object_id=root_instance.pk,
|
|
248
|
+
calculation_log=text,
|
|
249
|
+
)
|
|
250
|
+
return True
|
|
251
|
+
except Exception:
|
|
252
|
+
logger.warning(
|
|
253
|
+
"Could not keep the log of failed calculation %s",
|
|
254
|
+
calculation_id,
|
|
255
|
+
exc_info=True,
|
|
256
|
+
)
|
|
257
|
+
return False
|
|
258
|
+
|
|
206
259
|
@classmethod
|
|
207
260
|
def log(cls, message: str):
|
|
208
261
|
"""
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""The newest calculation run started from each record on a page.
|
|
2
|
+
|
|
3
|
+
A calculation row carries ``is_calculated`` and nothing else, so on its own the
|
|
4
|
+
grid cannot tell which rows have a finished log to open. This resolves, for a
|
|
5
|
+
whole page of rows in one query, the ``calculationId`` of the newest run each
|
|
6
|
+
row started.
|
|
7
|
+
|
|
8
|
+
The rule is the one ``useResolvedCalculationId`` applies in the frontend,
|
|
9
|
+
stated here in Python so the table and every widget open the same run for the
|
|
10
|
+
same row: a run started from record ``pk`` of model ``m`` has an id beginning
|
|
11
|
+
``f"{m}_{pk}_"`` (the frontend mints ``{m}_{pk}_update_{uuid}``), and the newest
|
|
12
|
+
is the one with the highest ``CalculationLog.id``.
|
|
13
|
+
|
|
14
|
+
The trailing underscore is load-bearing: without it, record 1 would match
|
|
15
|
+
record 11's runs.
|
|
16
|
+
|
|
17
|
+
Served by the index ``calculationId`` already has (migration 0005). On
|
|
18
|
+
PostgreSQL, Django adds a ``text_pattern_ops`` companion index for an indexed
|
|
19
|
+
text field, and that is what serves ``LIKE 'prefix%'``.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
from functools import reduce
|
|
25
|
+
from operator import or_
|
|
26
|
+
from typing import Dict, Iterable, Tuple
|
|
27
|
+
|
|
28
|
+
from django.db.models import Max, Q
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_calculation_model(model) -> bool:
|
|
34
|
+
"""Whether ``model`` is a ``CalculationModel`` subclass.
|
|
35
|
+
|
|
36
|
+
Imported lazily: ``lex.core.models.CalculationModel`` pulls in much of the
|
|
37
|
+
framework, and the serializer layer imports this module.
|
|
38
|
+
"""
|
|
39
|
+
from lex.core.models.CalculationModel import CalculationModel
|
|
40
|
+
|
|
41
|
+
return isinstance(model, type) and issubclass(model, CalculationModel)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_prefix(model_name: str, pk: object) -> str:
|
|
45
|
+
"""The id prefix every run started from this record carries."""
|
|
46
|
+
return f"{model_name}_{pk}_"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def latest_calculation_ids(model_name: str, pks: Iterable[object]) -> Dict[str, str]:
|
|
50
|
+
"""Map ``str(pk)`` to the ``calculationId`` of the newest run it started.
|
|
51
|
+
|
|
52
|
+
Records that never started a run are absent. One query whatever the number
|
|
53
|
+
of records: one ``startswith`` per record, ORed, collapsed to one row per
|
|
54
|
+
run before it leaves the database.
|
|
55
|
+
"""
|
|
56
|
+
from lex.audit_logging.models.CalculationLog import CalculationLog
|
|
57
|
+
|
|
58
|
+
prefixes = {str(pk): run_prefix(model_name, pk) for pk in pks}
|
|
59
|
+
if not prefixes:
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
runs = (
|
|
63
|
+
CalculationLog.objects.filter(
|
|
64
|
+
reduce(or_, (Q(calculationId__startswith=p) for p in prefixes.values()))
|
|
65
|
+
)
|
|
66
|
+
.values("calculationId")
|
|
67
|
+
.annotate(newest=Max("id"))
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Longest prefix first. For string keys containing "_", a run id can start
|
|
71
|
+
# with two of the page's prefixes ("m_a_" and "m_a_b_"); the longer one is
|
|
72
|
+
# the record that started it, because record "a"'s own runs begin
|
|
73
|
+
# "m_a_update_". Integer keys cannot collide.
|
|
74
|
+
by_length = sorted(prefixes.items(), key=lambda item: len(item[1]), reverse=True)
|
|
75
|
+
best: Dict[str, Tuple[int, str]] = {}
|
|
76
|
+
for run in runs:
|
|
77
|
+
calculation_id = run["calculationId"]
|
|
78
|
+
for pk, prefix in by_length:
|
|
79
|
+
if calculation_id.startswith(prefix):
|
|
80
|
+
if pk not in best or run["newest"] > best[pk][0]:
|
|
81
|
+
best[pk] = (run["newest"], calculation_id)
|
|
82
|
+
break
|
|
83
|
+
return {pk: calculation_id for pk, (_, calculation_id) in best.items()}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def annotate_latest_calculation(rows, model) -> None:
|
|
87
|
+
"""Set ``_lex_calculation_id`` and ``_has_calculation_log`` on each row.
|
|
88
|
+
|
|
89
|
+
Never raises. A failure costs the log button, not the page: every row is
|
|
90
|
+
marked as having no log and the grid still loads.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
ids = latest_calculation_ids(model._meta.model_name, [row.pk for row in rows])
|
|
94
|
+
except Exception:
|
|
95
|
+
logger.warning(
|
|
96
|
+
"Could not resolve the latest calculation runs for %s; "
|
|
97
|
+
"serving the page without them.",
|
|
98
|
+
model._meta.label,
|
|
99
|
+
exc_info=True,
|
|
100
|
+
)
|
|
101
|
+
ids = {}
|
|
102
|
+
for row in rows:
|
|
103
|
+
calculation_id = ids.get(str(row.pk))
|
|
104
|
+
row._lex_calculation_id = calculation_id
|
|
105
|
+
row._has_calculation_log = calculation_id is not None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
In-database activation of future-dated bitemporal records.
|
|
3
|
+
|
|
4
|
+
Installs ``lex/core/sql/bitemporal_activation.sql``: the applier's heartbeat table and
|
|
5
|
+
three pl/pgSQL functions:
|
|
6
|
+
|
|
7
|
+
lex_bitemporal_tables() discovery, from the catalog
|
|
8
|
+
lex_pending_activations() visibility: every SCHEDULED row, due or not
|
|
9
|
+
lex_apply_due_activations() the applier, called every minute by pg_cron
|
|
10
|
+
|
|
11
|
+
The SQL is executed through a raw cursor with no parameters: it is full of ``%I`` /
|
|
12
|
+
``%L`` ``format()`` placeholders that psycopg2 would otherwise try to interpolate.
|
|
13
|
+
|
|
14
|
+
Nothing here is a Django model on purpose. The framework registers every concrete model
|
|
15
|
+
it discovers under ``lex/`` for history tracking and for the model list, and an
|
|
16
|
+
operational heartbeat belongs in neither (see the SQL file). On a non-PostgreSQL backend
|
|
17
|
+
this migration is a no-op: the functions have no equivalent there and the in-process
|
|
18
|
+
fallback keeps working (design §8.13).
|
|
19
|
+
|
|
20
|
+
Design: docs/superpowers/specs/2026-09-16-bitemporal-activation-applier-design.md
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
from django.db import migrations
|
|
26
|
+
|
|
27
|
+
SQL_PATH = Path(__file__).resolve().parents[1] / "sql" / "bitemporal_activation.sql"
|
|
28
|
+
|
|
29
|
+
# Drop order: dependants first.
|
|
30
|
+
FUNCTIONS = (
|
|
31
|
+
"lex_apply_due_activations",
|
|
32
|
+
"lex_pending_activations",
|
|
33
|
+
"lex_bitemporal_tables",
|
|
34
|
+
)
|
|
35
|
+
TABLE = "lex_activation_applier_state"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def install(apps, schema_editor):
|
|
39
|
+
if schema_editor.connection.vendor != "postgresql":
|
|
40
|
+
return
|
|
41
|
+
with schema_editor.connection.cursor() as cursor:
|
|
42
|
+
cursor.execute(SQL_PATH.read_text(encoding="utf-8"))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def uninstall(apps, schema_editor):
|
|
46
|
+
if schema_editor.connection.vendor != "postgresql":
|
|
47
|
+
return
|
|
48
|
+
with schema_editor.connection.cursor() as cursor:
|
|
49
|
+
for name in FUNCTIONS:
|
|
50
|
+
cursor.execute(f"DROP FUNCTION IF EXISTS {name}()")
|
|
51
|
+
cursor.execute(f"DROP TABLE IF EXISTS {TABLE}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Migration(migrations.Migration):
|
|
55
|
+
|
|
56
|
+
initial = True
|
|
57
|
+
|
|
58
|
+
dependencies = []
|
|
59
|
+
|
|
60
|
+
operations = [
|
|
61
|
+
migrations.RunPython(install, uninstall),
|
|
62
|
+
]
|
|
@@ -1200,6 +1200,12 @@ class CalculationModel(LexModel):
|
|
|
1200
1200
|
|
|
1201
1201
|
if is_root:
|
|
1202
1202
|
calc_id = context.calculation_id
|
|
1203
|
+
# A run that did not succeed was rolled back with its log
|
|
1204
|
+
# rows; the cache about to be purged still holds the log.
|
|
1205
|
+
if self.is_calculated != self.SUCCESS:
|
|
1206
|
+
from lex.audit_logging.models.CalculationLog import CalculationLog
|
|
1207
|
+
|
|
1208
|
+
CalculationLog.keep_rolled_back_log(calc_id, context.root_record, self)
|
|
1203
1209
|
# If we are root, we can clean up everything for this calculation ID
|
|
1204
1210
|
# or just our specific key. Cleaning everything ensures no orphaned child keys.
|
|
1205
1211
|
cleanup_result = CacheManager.cleanup_calculation(calculation_id=calc_id)
|
|
@@ -1432,6 +1438,11 @@ class CalculationModel(LexModel):
|
|
|
1432
1438
|
|
|
1433
1439
|
if is_root:
|
|
1434
1440
|
calc_id = context.calculation_id
|
|
1441
|
+
# Same as in execute_calculation_sync, for a failure that
|
|
1442
|
+
# never reached its cleanup; a no-op once that kept the log.
|
|
1443
|
+
from lex.audit_logging.models.CalculationLog import CalculationLog
|
|
1444
|
+
|
|
1445
|
+
CalculationLog.keep_rolled_back_log(calc_id, context.root_record, self)
|
|
1435
1446
|
# Clean up all keys associated with this calculation ID
|
|
1436
1447
|
cleanup_result = CacheManager.cleanup_calculation(calculation_id=calc_id)
|
|
1437
1448
|
|
|
@@ -12,6 +12,7 @@ The control fields are prefixed with ``meta_`` to avoid collisions with
|
|
|
12
12
|
the Level 1 fields that are copied into the MetaHistory model.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
import hashlib
|
|
15
16
|
import logging
|
|
16
17
|
|
|
17
18
|
from django.db import models, transaction
|
|
@@ -74,6 +75,29 @@ def create_meta_history_record(
|
|
|
74
75
|
return history_instance
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def scheduled_activation_index(table_name: str) -> models.Index:
|
|
79
|
+
"""
|
|
80
|
+
The partial index both activation paths scan every minute, forever.
|
|
81
|
+
|
|
82
|
+
``SELECT ... WHERE meta_task_status = 'SCHEDULED'`` runs per meta table per tick —
|
|
83
|
+
from the in-database applier (``lex_apply_due_activations()``), from the pending
|
|
84
|
+
view, and from the reconcile floor. ``SCHEDULED`` rows are transient and rare, so a
|
|
85
|
+
partial index is tiny and serves the scan and the ``DONE`` flip alike. Declared on
|
|
86
|
+
the generated model, so every customer repository picks it up as one ``AddIndex``
|
|
87
|
+
migration per meta table on its next ``makemigrations``. Correctness never depends
|
|
88
|
+
on it; only the cost of the scan does.
|
|
89
|
+
|
|
90
|
+
The name is derived from the table name (Django caps index names at 30 characters
|
|
91
|
+
and they must be unique per database).
|
|
92
|
+
"""
|
|
93
|
+
digest = hashlib.md5(table_name.encode("utf-8")).hexdigest()[:12]
|
|
94
|
+
return models.Index(
|
|
95
|
+
fields=["history_object"],
|
|
96
|
+
condition=models.Q(meta_task_status="SCHEDULED"),
|
|
97
|
+
name=f"lexsched_{digest}",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
77
101
|
class MetaLevelHistoricalRecords(HistoricalRecords):
|
|
78
102
|
"""
|
|
79
103
|
History-on-History provider.
|
|
@@ -170,6 +194,11 @@ class MetaLevelHistoricalRecords(HistoricalRecords):
|
|
|
170
194
|
meta_fields = super().get_meta_options(model)
|
|
171
195
|
meta_fields["ordering"] = ("-sys_from", "-meta_history_id")
|
|
172
196
|
meta_fields["get_latest_by"] = ("sys_from", "meta_history_id")
|
|
197
|
+
table_name = self.table_name or f"{model._meta.db_table}_meta_history"
|
|
198
|
+
meta_fields["indexes"] = [
|
|
199
|
+
*meta_fields.get("indexes", ()),
|
|
200
|
+
scheduled_activation_index(table_name),
|
|
201
|
+
]
|
|
173
202
|
return meta_fields
|
|
174
203
|
|
|
175
204
|
# ------------------------------------------------------------------
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
lex-app's side of the in-database activation applier.
|
|
3
|
+
|
|
4
|
+
The applier itself is SQL (``lex/core/sql/bitemporal_activation.sql``), installed by the
|
|
5
|
+
``lex.core`` migration and called every minute by a pg_cron job the instance controller
|
|
6
|
+
registers. Nothing in this module runs at apply time. What lives here:
|
|
7
|
+
|
|
8
|
+
* :func:`applier_is_alive` — the **write-time liveness gate**. A future-dated save asks
|
|
9
|
+
whether the database applied something recently. If yes, the save leaves only the
|
|
10
|
+
``SCHEDULED`` meta row: the database will activate it. If no — SQLite, a PostgreSQL
|
|
11
|
+
without pg_cron, an instance whose job is not registered yet, a migration that has
|
|
12
|
+
not run — the save also arms the in-process timer it always has. The decision is
|
|
13
|
+
made from a heartbeat the applier writes on every run, so no configuration can
|
|
14
|
+
claim an applier that is not there, and the lex-app release is safe to deploy in
|
|
15
|
+
any order relative to the infrastructure change.
|
|
16
|
+
|
|
17
|
+
* Thin invokers for tests and operators (:func:`apply_due_activations`,
|
|
18
|
+
:func:`pending_activations`, :func:`bitemporal_tables`, :func:`install_functions`).
|
|
19
|
+
They issue one ``SELECT`` each; they add no logic.
|
|
20
|
+
|
|
21
|
+
Design: docs/superpowers/specs/2026-09-16-bitemporal-activation-applier-design.md (§5.8).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import os
|
|
28
|
+
from datetime import timedelta
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from django.db import DatabaseError, connection as default_connection, transaction
|
|
33
|
+
from django.utils import timezone
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
SQL_PATH = Path(__file__).resolve().parents[1] / "sql" / "bitemporal_activation.sql"
|
|
38
|
+
|
|
39
|
+
HEARTBEAT_TABLE = "lex_activation_applier_state"
|
|
40
|
+
|
|
41
|
+
FUNCTION_NAMES = (
|
|
42
|
+
"lex_bitemporal_tables",
|
|
43
|
+
"lex_pending_activations",
|
|
44
|
+
"lex_apply_due_activations",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
#: How recent the heartbeat must be for the database applier to count as alive.
|
|
48
|
+
#: pg_cron fires every minute; five minutes absorbs a slow tick or a failover without
|
|
49
|
+
#: arming redundant timers, while a dead applier is noticed within the same window.
|
|
50
|
+
DEFAULT_LIVENESS_WINDOW_SECONDS = 300
|
|
51
|
+
LIVENESS_WINDOW_ENV = "LEX_ACTIVATION_APPLIER_LIVENESS_SECONDS"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def liveness_window() -> timedelta:
|
|
55
|
+
"""The heartbeat freshness window, read from the environment on every call."""
|
|
56
|
+
raw = os.getenv(LIVENESS_WINDOW_ENV, "")
|
|
57
|
+
try:
|
|
58
|
+
seconds = int(raw) if raw else DEFAULT_LIVENESS_WINDOW_SECONDS
|
|
59
|
+
except ValueError:
|
|
60
|
+
logger.warning("%s=%r is not an integer; using %s", LIVENESS_WINDOW_ENV, raw,
|
|
61
|
+
DEFAULT_LIVENESS_WINDOW_SECONDS)
|
|
62
|
+
seconds = DEFAULT_LIVENESS_WINDOW_SECONDS
|
|
63
|
+
return timedelta(seconds=max(seconds, 0))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def applier_is_alive(*, now=None, using=None) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
True when the database applier has run within :func:`liveness_window`.
|
|
69
|
+
|
|
70
|
+
False on any non-PostgreSQL backend, when the heartbeat table is missing (the
|
|
71
|
+
migration has not run), when it is empty (the applier has never run), or when the
|
|
72
|
+
last run is older than the window. Never raises: the query runs in a savepoint so a
|
|
73
|
+
missing table cannot poison the caller's transaction, and a database error simply
|
|
74
|
+
means "not alive", which falls back to the behaviour lex-app has always had.
|
|
75
|
+
"""
|
|
76
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
77
|
+
if conn.vendor != "postgresql":
|
|
78
|
+
return False
|
|
79
|
+
try:
|
|
80
|
+
with transaction.atomic(using=conn.alias), conn.cursor() as cursor:
|
|
81
|
+
cursor.execute(f"SELECT last_run_at FROM {HEARTBEAT_TABLE} WHERE id = 1")
|
|
82
|
+
row = cursor.fetchone()
|
|
83
|
+
except DatabaseError as exc: # table not migrated yet, connection trouble, ...
|
|
84
|
+
logger.debug("Activation applier liveness unknown (%s); treating as not alive", exc)
|
|
85
|
+
return False
|
|
86
|
+
if row is None:
|
|
87
|
+
return False
|
|
88
|
+
now = now or timezone.now()
|
|
89
|
+
return (now - row[0]) <= liveness_window()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _rows(cursor) -> list[dict[str, Any]]:
|
|
93
|
+
columns = [col[0] for col in cursor.description]
|
|
94
|
+
return [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ── heartbeat access (operators and tests; the applier itself writes it in SQL) ──
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def read_heartbeat(using=None) -> dict[str, Any] | None:
|
|
101
|
+
"""The heartbeat row as a dict, or None when the applier has never run."""
|
|
102
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
103
|
+
with conn.cursor() as cursor:
|
|
104
|
+
cursor.execute(
|
|
105
|
+
f"SELECT last_run_at, last_run_applied, last_run_failed, last_run_duration_ms "
|
|
106
|
+
f"FROM {HEARTBEAT_TABLE} WHERE id = 1"
|
|
107
|
+
)
|
|
108
|
+
rows = _rows(cursor)
|
|
109
|
+
return rows[0] if rows else None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def record_heartbeat(*, last_run_at=None, applied: int = 0, failed: int = 0,
|
|
113
|
+
duration_ms: int = 0, using=None) -> None:
|
|
114
|
+
"""Write the heartbeat row as the applier would. Used to simulate a live applier."""
|
|
115
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
116
|
+
with conn.cursor() as cursor:
|
|
117
|
+
cursor.execute(
|
|
118
|
+
f"INSERT INTO {HEARTBEAT_TABLE} (id, last_run_at, last_run_applied, last_run_failed, last_run_duration_ms) "
|
|
119
|
+
f"VALUES (1, %s, %s, %s, %s) "
|
|
120
|
+
f"ON CONFLICT (id) DO UPDATE SET last_run_at = EXCLUDED.last_run_at, "
|
|
121
|
+
f"last_run_applied = EXCLUDED.last_run_applied, last_run_failed = EXCLUDED.last_run_failed, "
|
|
122
|
+
f"last_run_duration_ms = EXCLUDED.last_run_duration_ms",
|
|
123
|
+
[last_run_at or timezone.now(), applied, failed, duration_ms],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def clear_heartbeat(using=None) -> None:
|
|
128
|
+
"""Remove the heartbeat row: the state before the applier has ever run."""
|
|
129
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
130
|
+
with conn.cursor() as cursor:
|
|
131
|
+
cursor.execute(f"DELETE FROM {HEARTBEAT_TABLE} WHERE id = 1")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ── thin invokers ──────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def install_functions(using=None) -> None:
|
|
138
|
+
"""(Re)install the SQL functions from :data:`SQL_PATH`. PostgreSQL only."""
|
|
139
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
140
|
+
if conn.vendor != "postgresql":
|
|
141
|
+
raise RuntimeError("The activation applier is PostgreSQL-only")
|
|
142
|
+
with conn.cursor() as cursor:
|
|
143
|
+
cursor.execute(SQL_PATH.read_text(encoding="utf-8"))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def apply_due_activations(using=None) -> int:
|
|
147
|
+
"""Run one applier tick. Returns the number of records converged."""
|
|
148
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
149
|
+
with conn.cursor() as cursor:
|
|
150
|
+
cursor.execute("SELECT lex_apply_due_activations()")
|
|
151
|
+
return int(cursor.fetchone()[0])
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def pending_activations(using=None) -> list[dict[str, Any]]:
|
|
155
|
+
"""Every SCHEDULED meta row across every bitemporal table, due or not."""
|
|
156
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
157
|
+
with conn.cursor() as cursor:
|
|
158
|
+
cursor.execute("SELECT * FROM lex_pending_activations()")
|
|
159
|
+
return _rows(cursor)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def bitemporal_tables(using=None) -> list[dict[str, Any]]:
|
|
163
|
+
"""The (main, history, meta, pk) triples the applier discovers right now."""
|
|
164
|
+
conn = default_connection if using is None else transaction.get_connection(using)
|
|
165
|
+
with conn.cursor() as cursor:
|
|
166
|
+
cursor.execute("SELECT * FROM lex_bitemporal_tables()")
|
|
167
|
+
return _rows(cursor)
|