simple-module-hosting 0.0.40__tar.gz → 0.0.42__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.
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/PKG-INFO +5 -5
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/pyproject.toml +5 -5
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/__init__.py +2 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_deferred_startup.py +7 -1
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_lifespan.py +11 -0
- simple_module_hosting-0.0.42/simple_module_hosting/data_compatibility.py +37 -0
- simple_module_hosting-0.0.42/simple_module_hosting/errors.py +28 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_gate.py +10 -1
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/migrate.py +9 -1
- simple_module_hosting-0.0.42/tests/test_hosting_data_compatibility.py +165 -0
- simple_module_hosting-0.0.42/tests/test_hosting_public_error_page.py +36 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_lifespan_deferred_startup.py +4 -1
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/.gitignore +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/LICENSE +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/README.md +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/__main__.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_anonymous.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_api_version.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_app_boot.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_body_limit.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_boot_checks.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_cors.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_csp_dynamic.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_db_health.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_dev_boot.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_error_handlers.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_error_negotiation.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_error_page.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_favicon.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_forwarded_host.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_host_services.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_host_settings_support.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_hydrate_step.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_i18n_build.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_i18n_target.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_inertia_cache.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_inertia_setup.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_inertia_shared.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_insecure_transport.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_lease_redis.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_middleware_pipeline.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_module_routes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_observability.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_openapi.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_phase_helpers.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_preapp_config.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_process_services.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_project_root.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_rate_limit.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_rate_limit_keys.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_rate_limit_store.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_registrations.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_request_guard_responses.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_route_access.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_route_guard.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_route_paths.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_secret_key.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_settings_registration.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_spa_mount.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_tenant.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/app_builder.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/assets.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/bootstrap_settings.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/csrf.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/db_options.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/health.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/host_cli.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/host_settings.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_deps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_manifest.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_middleware.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_routes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/inertia_deps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/inertia_utils.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/leases.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/locales/de.json +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/locales/en.json +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/locales/es.json +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/log_sinks.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/logging.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/maintenance.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/manifest.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/middleware.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/migrations.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/page_globs.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/pagination.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/permissions.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/public_access.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/py.typed +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/rate_limit_deps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/redirects.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/resource_deps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/sentry.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/sentry_scrub.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/sentry_scrub_text.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/session.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/settings.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/__init__.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/_migration_lock.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/components/ConnectionList.tsx +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/components/StepForm.tsx +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/pages/Wizard.tsx +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/payloads.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_wizard/routes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/shared_props.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/static_files.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/tenancy.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/token_guard.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/ui_locales/de.json +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/ui_locales/en.json +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/ui_locales/es.json +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/_hosting_settings_db.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/_resacc_models.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/_sentry_transport.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/conftest.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_api_error_negotiation.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_app.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_auth_provider_setting.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_boot_config_hardening.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_boot_drift_check.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_branding_head.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_build_time_settings_marked.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_check_migrations.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_check_migrations_expression_note.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_csp_module_sources.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_csp_providers.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_db_health_check.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_design_packs_wiring.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_diagnostics_state.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_error_page_shared_props.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_error_status_coverage.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_favicon_default.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_forwarded_host.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_grant_sources.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_health.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_home_url_default.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_host_cli.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_host_settings_fields.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_asset_version.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_cors_api.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_cors_unset.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_csrf.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_csrf_shared_prop.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_error_handling_policy.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_error_json_and_session_cookie.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_lease_backends.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_lease_selection.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_lifespan_cleanup.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_log_sinks.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_openapi.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_openapi_security.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_pagination_deps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_permissions.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_public_access.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_sentry.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_sentry_scrub.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_hosting_sentry_scrub_gaps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_i18n_build_overrides.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_i18n_manifest.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_i18n_manifest_published_target.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_i18n_routes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_i18n_types_cover_installed_modules.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_i18n_ui_namespace.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_inertia_cache.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_inertia_i18n_shared_props.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_inertia_shared_providers.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_insecure_transport_warning.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_invalidation_wiring.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_lifespan_order.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_locale_middleware.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_logging.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_maintenance_mode.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_manifest.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_menu_providers_wiring.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_menu_urls_are_canonical.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_merge_host_settings_strict.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_middleware_order.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_migration_listing.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_migration_multihead.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_native_link_prefixes_strip.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_preapp_config.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_preapp_invalid_values.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_preapp_read_failures.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_precompressed_static.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_principal_permissions_claim.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_proxy_headers.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_rate_limit_rules_redis.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_rate_limit_rules_registry.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_rate_limit_store_shared.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_redirects.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_request_guard_auth_parity.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_request_guard_body_limit.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_request_guard_body_limit_middleware_read.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_request_guard_body_limit_started.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_request_guard_rate_limit.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_resource_deps.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_resource_policy_owned.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_resource_policy_parity.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_resource_types_wiring.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_response_compression.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_route_guard.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_route_guard_fastapi_contract.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_route_paths.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_secret_key_bootstrap.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_server_rendered_head.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_session_cookie_secure.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_session_cookie_security.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_session_cookie_window.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_session_vary_dedupe.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_settings_i18n.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_settings_secrets.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_gate.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_gate_single_flight.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_migrate_error_redaction.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_migrate_worker_lock.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_public_routes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_registry.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_setup_routes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_spa_mounts_541.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_sqlite_path_anchor.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_static_caching.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_strict_discovery_wiring.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_tenancy_api.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_tenant_middleware.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_tenant_resolver_diagnostic.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_tenant_resolver_types.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_tenant_scope_helpers.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_tenant_source.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_token_guard_effective_route.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_token_route_guard.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_translator_dep.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests/test_view_prefixes.py +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tests-js/hosting_SetupWizard.test.tsx +0 -0
- {simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_hosting
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.42
|
|
4
4
|
Summary: FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -22,13 +22,13 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.12
|
|
24
24
|
Requires-Dist: click<9,>=8.1
|
|
25
|
-
Requires-Dist: fastapi<0.
|
|
25
|
+
Requires-Dist: fastapi<0.160,>=0.140
|
|
26
26
|
Requires-Dist: httpx<0.29,>=0.27
|
|
27
27
|
Requires-Dist: itsdangerous<3,>=2.0
|
|
28
28
|
Requires-Dist: jinja2<4,>=3.1
|
|
29
|
-
Requires-Dist: simple-module-core==0.0.
|
|
30
|
-
Requires-Dist: simple-module-db==0.0.
|
|
31
|
-
Requires-Dist: simple-module-inertia==0.0.
|
|
29
|
+
Requires-Dist: simple-module-core==0.0.42
|
|
30
|
+
Requires-Dist: simple-module-db==0.0.42
|
|
31
|
+
Requires-Dist: simple-module-inertia==0.0.42
|
|
32
32
|
Requires-Dist: starlette<2,>=0.44
|
|
33
33
|
Requires-Dist: tomlkit<0.16,>=0.13
|
|
34
34
|
Requires-Dist: uvicorn[standard]<0.55,>=0.34
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_hosting"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.42"
|
|
4
4
|
description = "FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -22,16 +22,16 @@ classifiers = [
|
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
24
|
"click>=8.1,<9",
|
|
25
|
-
"fastapi>=0.140,<0.
|
|
25
|
+
"fastapi>=0.140,<0.160",
|
|
26
26
|
"httpx>=0.27,<0.29",
|
|
27
27
|
# SessionMiddleware (starlette.middleware.sessions) imports itsdangerous at
|
|
28
28
|
# module import time; starlette treats it as an optional extra, so declare
|
|
29
29
|
# it directly — out-of-tree hosts have nothing else pulling it in.
|
|
30
30
|
"itsdangerous>=2.0,<3",
|
|
31
31
|
"jinja2>=3.1,<4",
|
|
32
|
-
"simple_module_core==0.0.
|
|
33
|
-
"simple_module_db==0.0.
|
|
34
|
-
"simple_module_inertia==0.0.
|
|
32
|
+
"simple_module_core==0.0.42",
|
|
33
|
+
"simple_module_db==0.0.42",
|
|
34
|
+
"simple_module_inertia==0.0.42",
|
|
35
35
|
"starlette>=0.44,<2",
|
|
36
36
|
"tomlkit>=0.13,<0.16",
|
|
37
37
|
"uvicorn[standard]>=0.34,<0.55",
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/__init__.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from simple_module_hosting._preapp_config import merge_host_settings
|
|
4
4
|
from simple_module_hosting.app_builder import create_app
|
|
5
|
+
from simple_module_hosting.errors import render_error_page
|
|
5
6
|
from simple_module_hosting.logging import correlation_id, setup_logging
|
|
6
7
|
from simple_module_hosting.settings import Settings
|
|
7
8
|
from simple_module_hosting.shared_props import (
|
|
@@ -16,5 +17,6 @@ __all__ = [
|
|
|
16
17
|
"create_app",
|
|
17
18
|
"merge_host_settings",
|
|
18
19
|
"register_inertia_shared_provider",
|
|
20
|
+
"render_error_page",
|
|
19
21
|
"setup_logging",
|
|
20
22
|
]
|
|
@@ -11,6 +11,7 @@ from simple_module_core.module_settings import get_module_settings_registry
|
|
|
11
11
|
from simple_module_db.stored_settings import SessionOverrideSource, settings_table_exists
|
|
12
12
|
|
|
13
13
|
from simple_module_hosting._hydrate_step import hydrate_all
|
|
14
|
+
from simple_module_hosting.data_compatibility import check_data_compatibility
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
|
|
@@ -45,8 +46,13 @@ async def run_deferred_startup(app: FastAPI) -> None:
|
|
|
45
46
|
operator's out-of-band ``make migrate``) still finishes starting its
|
|
46
47
|
modules. Serialised per app; a hook that fails again is logged and dropped
|
|
47
48
|
rather than raised, because this runs after the migrations have already
|
|
48
|
-
committed
|
|
49
|
+
committed. Compatibility checks run first and still raise: an incompatible
|
|
50
|
+
security state must keep the setup gate closed.
|
|
49
51
|
"""
|
|
52
|
+
pending_checks = getattr(app.state, "deferred_data_compatibility", None)
|
|
53
|
+
if pending_checks:
|
|
54
|
+
await check_data_compatibility(app, pending_checks)
|
|
55
|
+
app.state.deferred_data_compatibility = []
|
|
50
56
|
deferred = get_deferred_startup(app)
|
|
51
57
|
if not deferred:
|
|
52
58
|
return
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_lifespan.py
RENAMED
|
@@ -22,6 +22,7 @@ from simple_module_core.app_state import get_migration_status
|
|
|
22
22
|
|
|
23
23
|
from simple_module_hosting._deferred_startup import hydrate_settings_from_db
|
|
24
24
|
from simple_module_hosting._route_guard import guard_included_routers
|
|
25
|
+
from simple_module_hosting.data_compatibility import check_data_compatibility
|
|
25
26
|
from simple_module_hosting.migrations import migration_status
|
|
26
27
|
from simple_module_hosting.setup_gate import STEP_MIGRATIONS
|
|
27
28
|
|
|
@@ -114,6 +115,16 @@ def build_lifespan(modules: Sequence) -> Callable:
|
|
|
114
115
|
f"{app.state.migration['head_revision']!r}). Run: make migrate"
|
|
115
116
|
)
|
|
116
117
|
|
|
118
|
+
# Only a schema at head can be judged: a first run that stopped part-way
|
|
119
|
+
# still has pre-upgrade columns, and refusing it would hide the wizard
|
|
120
|
+
# that finishes the migration. The setup gate runs deferred checks
|
|
121
|
+
# once the schema is current.
|
|
122
|
+
migration = app.state.migration
|
|
123
|
+
if migration["is_current"] and migration.get("current_revision") is not None:
|
|
124
|
+
await check_data_compatibility(app, modules)
|
|
125
|
+
else:
|
|
126
|
+
app.state.deferred_data_compatibility = list(modules)
|
|
127
|
+
|
|
117
128
|
await hydrate_settings_from_db(app)
|
|
118
129
|
|
|
119
130
|
# Behind head on a first run the tables a module's on_startup reads do
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Enforce module data compatibility before hydrated settings and startup."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from simple_module_core.data_compatibility import checking_modules, collect_data_compatibility
|
|
8
|
+
from simple_module_core.module import ModuleBase
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataCompatibilityError(RuntimeError):
|
|
14
|
+
"""An incompatible security state must also keep the setup gate closed."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def check_data_compatibility(app: FastAPI, modules: Sequence[ModuleBase]) -> None:
|
|
18
|
+
modules = checking_modules(modules)
|
|
19
|
+
if not modules:
|
|
20
|
+
return
|
|
21
|
+
try:
|
|
22
|
+
async with app.state.sm.db.session_factory() as db:
|
|
23
|
+
issues = await collect_data_compatibility(modules, db)
|
|
24
|
+
except Exception as exc:
|
|
25
|
+
# Log the cause here: callers such as the setup gate only see the verdict.
|
|
26
|
+
logger.exception("Module data compatibility check failed")
|
|
27
|
+
if app.state.sm.settings.environment == "development":
|
|
28
|
+
return
|
|
29
|
+
raise DataCompatibilityError("Could not check module data compatibility") from exc
|
|
30
|
+
errors = []
|
|
31
|
+
for issue in issues:
|
|
32
|
+
message = f"{issue.upgrade_id}: {issue.message} Prepare: {issue.remediation()}"
|
|
33
|
+
logger.warning(message)
|
|
34
|
+
if issue.severity == "error":
|
|
35
|
+
errors.append(message)
|
|
36
|
+
if errors and app.state.sm.settings.environment != "development":
|
|
37
|
+
raise DataCompatibilityError("Data compatibility upgrade required:\n" + "\n".join(errors))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Public error rendering for module exception handlers (#684)."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
|
|
5
|
+
from starlette.requests import Request
|
|
6
|
+
from starlette.responses import JSONResponse, Response
|
|
7
|
+
|
|
8
|
+
from simple_module_hosting._error_negotiation import _wants_json
|
|
9
|
+
from simple_module_hosting._error_page import render_error_page as _render_error_page
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def render_error_page(
|
|
13
|
+
request: Request,
|
|
14
|
+
status_code: int,
|
|
15
|
+
detail: str,
|
|
16
|
+
headers: Mapping[str, str] | None = None,
|
|
17
|
+
*,
|
|
18
|
+
required_permission: str | None = None,
|
|
19
|
+
) -> Response:
|
|
20
|
+
"""Return standard JSON for API clients or the Inertia Error page for browsers.
|
|
21
|
+
|
|
22
|
+
Uses the framework's content negotiation, shared layout props and rendering
|
|
23
|
+
fallback. Status and supplied headers survive either response format.
|
|
24
|
+
``required_permission`` optionally identifies the permission a 403 lacked.
|
|
25
|
+
"""
|
|
26
|
+
if _wants_json(request):
|
|
27
|
+
return JSONResponse(status_code=status_code, content={"detail": detail}, headers=headers)
|
|
28
|
+
return await _render_error_page(request, status_code, detail, headers, required_permission)
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/setup_gate.py
RENAMED
|
@@ -23,6 +23,7 @@ from starlette.types import ASGIApp, Receive, Scope, Send
|
|
|
23
23
|
|
|
24
24
|
from simple_module_hosting import migrations
|
|
25
25
|
from simple_module_hosting._deferred_startup import run_deferred_startup
|
|
26
|
+
from simple_module_hosting.data_compatibility import DataCompatibilityError
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
28
29
|
_migration_failure_warned = False
|
|
@@ -74,6 +75,11 @@ async def _database_migrated(app) -> bool:
|
|
|
74
75
|
if not status:
|
|
75
76
|
return True
|
|
76
77
|
if status.get("is_current", True):
|
|
78
|
+
if getattr(app.state, "deferred_data_compatibility", None):
|
|
79
|
+
try:
|
|
80
|
+
await run_deferred_startup(app)
|
|
81
|
+
except DataCompatibilityError:
|
|
82
|
+
return False
|
|
77
83
|
return True
|
|
78
84
|
|
|
79
85
|
try:
|
|
@@ -90,9 +96,12 @@ async def _database_migrated(app) -> bool:
|
|
|
90
96
|
if status["is_current"]:
|
|
91
97
|
# Finish starting the modules whose on_startup needed these tables —
|
|
92
98
|
# this worker may not be the one that ran the migrations. Never lets
|
|
93
|
-
#
|
|
99
|
+
# an ordinary startup-hook failure change the schema verdict. A failed
|
|
100
|
+
# data compatibility check must keep the gate closed.
|
|
94
101
|
try:
|
|
95
102
|
await run_deferred_startup(app)
|
|
103
|
+
except DataCompatibilityError:
|
|
104
|
+
return False
|
|
96
105
|
except Exception:
|
|
97
106
|
logger.exception("Replaying deferred on_startup hooks failed")
|
|
98
107
|
return bool(status["is_current"])
|
|
@@ -16,6 +16,7 @@ from collections.abc import Generator
|
|
|
16
16
|
from fastapi import HTTPException, Request
|
|
17
17
|
|
|
18
18
|
from simple_module_hosting._deferred_startup import run_deferred_startup
|
|
19
|
+
from simple_module_hosting.data_compatibility import DataCompatibilityError
|
|
19
20
|
from simple_module_hosting.setup_wizard._migration_lock import migration_lock
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
@@ -87,6 +88,13 @@ async def apply_migrations(request: Request, _data: dict) -> dict:
|
|
|
87
88
|
|
|
88
89
|
request.app.state.migration = await migration_status(request.app.state.sm.db.engine)
|
|
89
90
|
# Modules whose on_startup could not run before the tables existed.
|
|
90
|
-
|
|
91
|
+
try:
|
|
92
|
+
await run_deferred_startup(request.app)
|
|
93
|
+
except DataCompatibilityError as exc:
|
|
94
|
+
# The detail names upgrade ids and the remediation command, never data.
|
|
95
|
+
logger.error("Setup: %s", exc)
|
|
96
|
+
raise HTTPException(
|
|
97
|
+
status_code=409, detail="A module data upgrade is required; see the server log."
|
|
98
|
+
) from exc
|
|
91
99
|
logger.info("Setup: migrations applied")
|
|
92
100
|
return {"migration": request.app.state.migration}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Compatibility errors gate established hosts before settings or startup (#711)."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from simple_module_core.module import ModuleBase, ModuleMeta
|
|
8
|
+
from simple_module_hosting._lifespan import build_lifespan
|
|
9
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize(
|
|
13
|
+
("environment", "severity", "never_migrated", "refuses"),
|
|
14
|
+
[
|
|
15
|
+
("production", "error", False, True),
|
|
16
|
+
("testing", "error", False, True),
|
|
17
|
+
("development", "error", False, False),
|
|
18
|
+
("production", "warning", False, False),
|
|
19
|
+
("production", "error", True, False),
|
|
20
|
+
],
|
|
21
|
+
)
|
|
22
|
+
async def test_hosting_compatibility_before_hydration(
|
|
23
|
+
monkeypatch, environment, severity, never_migrated, refuses
|
|
24
|
+
):
|
|
25
|
+
from simple_module_core.data_compatibility import DataCompatibilityIssue
|
|
26
|
+
|
|
27
|
+
events = []
|
|
28
|
+
|
|
29
|
+
class Module(ModuleBase):
|
|
30
|
+
meta = ModuleMeta(name="Example")
|
|
31
|
+
|
|
32
|
+
async def check_data_compatibility(self, db):
|
|
33
|
+
events.append("check")
|
|
34
|
+
return [
|
|
35
|
+
DataCompatibilityIssue(
|
|
36
|
+
upgrade_id="example.v1", message="Upgrade", severity=severity
|
|
37
|
+
)
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
async def on_startup(self, app):
|
|
41
|
+
events.append("startup")
|
|
42
|
+
|
|
43
|
+
async def status(engine):
|
|
44
|
+
return {"is_current": True, "current_revision": None if never_migrated else "local"}
|
|
45
|
+
|
|
46
|
+
async def hydrate(app):
|
|
47
|
+
events.append("hydrate")
|
|
48
|
+
|
|
49
|
+
monkeypatch.setattr("simple_module_hosting._lifespan.migration_status", status)
|
|
50
|
+
monkeypatch.setattr("simple_module_hosting._lifespan.hydrate_settings_from_db", hydrate)
|
|
51
|
+
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
52
|
+
app = FastAPI()
|
|
53
|
+
app.state.sm = SimpleNamespace(
|
|
54
|
+
db=SimpleNamespace(engine=engine, session_factory=async_sessionmaker(engine)),
|
|
55
|
+
settings=SimpleNamespace(environment=environment),
|
|
56
|
+
)
|
|
57
|
+
if refuses:
|
|
58
|
+
with pytest.raises(RuntimeError, match=r"example\.v1"):
|
|
59
|
+
async with build_lifespan([Module()])(app):
|
|
60
|
+
pass
|
|
61
|
+
assert events == ["check"]
|
|
62
|
+
else:
|
|
63
|
+
async with build_lifespan([Module()])(app):
|
|
64
|
+
assert events == (
|
|
65
|
+
["hydrate", "startup"] if never_migrated else ["check", "hydrate", "startup"]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def test_partly_migrated_first_run_defers_the_check(monkeypatch):
|
|
70
|
+
# A first run that stopped part-way has a revision but pre-upgrade columns:
|
|
71
|
+
# judging it would refuse the boot and hide the wizard that finishes it.
|
|
72
|
+
events = []
|
|
73
|
+
|
|
74
|
+
class Module(ModuleBase):
|
|
75
|
+
meta = ModuleMeta(name="Example")
|
|
76
|
+
|
|
77
|
+
async def check_data_compatibility(self, db):
|
|
78
|
+
events.append("check")
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
async def status(engine):
|
|
82
|
+
return {"is_current": False, "current_revision": "partial", "pending_count": 3}
|
|
83
|
+
|
|
84
|
+
async def first_run(app):
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
async def hydrate(app):
|
|
88
|
+
events.append("hydrate")
|
|
89
|
+
|
|
90
|
+
monkeypatch.setattr("simple_module_hosting._lifespan.migration_status", status)
|
|
91
|
+
monkeypatch.setattr("simple_module_hosting._lifespan._is_first_run", first_run)
|
|
92
|
+
monkeypatch.setattr("simple_module_hosting._lifespan.hydrate_settings_from_db", hydrate)
|
|
93
|
+
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
94
|
+
app = FastAPI()
|
|
95
|
+
app.state.sm = SimpleNamespace(
|
|
96
|
+
db=SimpleNamespace(engine=engine, session_factory=async_sessionmaker(engine)),
|
|
97
|
+
settings=SimpleNamespace(environment="production"),
|
|
98
|
+
)
|
|
99
|
+
async with build_lifespan([Module()])(app):
|
|
100
|
+
assert "check" not in events
|
|
101
|
+
assert app.state.deferred_data_compatibility
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def test_hosting_deferred_checks_gate_replay_before_hydration(monkeypatch):
|
|
105
|
+
from simple_module_core.data_compatibility import DataCompatibilityIssue
|
|
106
|
+
from simple_module_hosting._deferred_startup import run_deferred_startup
|
|
107
|
+
|
|
108
|
+
class Module(ModuleBase):
|
|
109
|
+
meta = ModuleMeta(name="Example")
|
|
110
|
+
|
|
111
|
+
async def check_data_compatibility(self, db):
|
|
112
|
+
return [
|
|
113
|
+
DataCompatibilityIssue(upgrade_id="example.v1", message="Upgrade", severity="error")
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
app = FastAPI()
|
|
117
|
+
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
118
|
+
app.state.sm = SimpleNamespace(
|
|
119
|
+
db=SimpleNamespace(engine=engine, session_factory=async_sessionmaker(engine)),
|
|
120
|
+
settings=SimpleNamespace(environment="production"),
|
|
121
|
+
)
|
|
122
|
+
app.state.deferred_data_compatibility = [Module()]
|
|
123
|
+
app.state.deferred_startup = []
|
|
124
|
+
try:
|
|
125
|
+
with pytest.raises(RuntimeError, match=r"example\.v1"):
|
|
126
|
+
await run_deferred_startup(app)
|
|
127
|
+
assert app.state.deferred_data_compatibility
|
|
128
|
+
finally:
|
|
129
|
+
await engine.dispose()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.parametrize("check_raises", [False, True])
|
|
133
|
+
async def test_hosting_setup_gate_keeps_incompatible_replay_closed(monkeypatch, check_raises):
|
|
134
|
+
from simple_module_core.data_compatibility import DataCompatibilityIssue
|
|
135
|
+
from simple_module_hosting.setup_gate import _database_migrated
|
|
136
|
+
|
|
137
|
+
class Module(ModuleBase):
|
|
138
|
+
meta = ModuleMeta(name="Example")
|
|
139
|
+
|
|
140
|
+
async def check_data_compatibility(self, db):
|
|
141
|
+
if check_raises:
|
|
142
|
+
raise RuntimeError("database unavailable")
|
|
143
|
+
return [
|
|
144
|
+
DataCompatibilityIssue(upgrade_id="example.v1", message="Upgrade", severity="error")
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
app = FastAPI()
|
|
148
|
+
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
149
|
+
app.state.sm = SimpleNamespace(
|
|
150
|
+
db=SimpleNamespace(engine=engine, session_factory=async_sessionmaker(engine)),
|
|
151
|
+
settings=SimpleNamespace(environment="production"),
|
|
152
|
+
)
|
|
153
|
+
app.state.migration = {"is_current": False, "current_revision": None}
|
|
154
|
+
app.state.deferred_data_compatibility = [Module()]
|
|
155
|
+
app.state.deferred_startup = []
|
|
156
|
+
|
|
157
|
+
async def status(engine):
|
|
158
|
+
return {"is_current": True, "current_revision": "local"}
|
|
159
|
+
|
|
160
|
+
monkeypatch.setattr("simple_module_hosting.setup_gate.migrations.migration_status", status)
|
|
161
|
+
try:
|
|
162
|
+
assert await _database_migrated(app) is False
|
|
163
|
+
assert await _database_migrated(app) is False
|
|
164
|
+
finally:
|
|
165
|
+
await engine.dispose()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Modules can use the public renderer with standard content negotiation (#684)."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from fastapi import Request
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize(
|
|
8
|
+
"headers, json_response",
|
|
9
|
+
[
|
|
10
|
+
({"Accept": "application/json"}, True),
|
|
11
|
+
({"Accept": "text/html"}, False),
|
|
12
|
+
({"Accept": "application/json", "X-Inertia": "true"}, False),
|
|
13
|
+
],
|
|
14
|
+
)
|
|
15
|
+
async def test_public_renderer_negotiates_and_preserves_status_and_headers(
|
|
16
|
+
app, authenticated_client, headers, json_response
|
|
17
|
+
):
|
|
18
|
+
from simple_module_hosting import render_error_page
|
|
19
|
+
|
|
20
|
+
async def custom_error(request: Request):
|
|
21
|
+
return await render_error_page(
|
|
22
|
+
request, 429, detail="Try later", headers={"Retry-After": "30"}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
app.add_api_route("/custom-error", custom_error)
|
|
26
|
+
response = await authenticated_client.get("/custom-error", headers=headers)
|
|
27
|
+
assert response.status_code == 429
|
|
28
|
+
assert response.headers["Retry-After"] == "30"
|
|
29
|
+
if json_response:
|
|
30
|
+
assert response.json() == {"detail": "Try later"}
|
|
31
|
+
elif "X-Inertia" in headers:
|
|
32
|
+
assert response.json()["component"] == "Error"
|
|
33
|
+
assert response.json()["props"]["message"] == "Try later"
|
|
34
|
+
else:
|
|
35
|
+
assert "data-page" in response.text
|
|
36
|
+
assert "Try later" in response.text
|
|
@@ -53,7 +53,10 @@ async def test_failing_hook_is_deferred_then_replayed_on_unmigrated_first_run():
|
|
|
53
53
|
async def capture(app_):
|
|
54
54
|
seen["deferred"] = [m.meta.name for m in app_.state.deferred_startup]
|
|
55
55
|
|
|
56
|
-
with
|
|
56
|
+
with (
|
|
57
|
+
patch("simple_module_hosting._deferred_startup.hydrate_settings_from_db", AsyncMock()),
|
|
58
|
+
patch("simple_module_hosting._deferred_startup.check_data_compatibility", AsyncMock()),
|
|
59
|
+
):
|
|
57
60
|
status = {"is_current": False, "pending_count": 1}
|
|
58
61
|
with (
|
|
59
62
|
patch(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/__main__.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_anonymous.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_api_version.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_app_boot.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_body_limit.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_boot_checks.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_cors.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_csp_dynamic.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_db_health.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_dev_boot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_error_page.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_favicon.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_hydrate_step.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_i18n_build.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_i18n_target.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_lease_redis.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_openapi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_project_root.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_rate_limit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_route_access.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_route_guard.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_route_paths.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_secret_key.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_spa_mount.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/_tenant.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/app_builder.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/assets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/db_options.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/health.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/host_cli.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/host_settings.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_deps.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_manifest.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/i18n_routes.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/inertia_deps.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/inertia_utils.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/leases.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/locales/de.json
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/locales/en.json
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/locales/es.json
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/log_sinks.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.40 → simple_module_hosting-0.0.42}/simple_module_hosting/logging.py
RENAMED
|
File without changes
|