topicgate 1.1.2__tar.gz → 1.1.3__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.
- {topicgate-1.1.2/src/topicgate.egg-info → topicgate-1.1.3}/PKG-INFO +1 -1
- {topicgate-1.1.2 → topicgate-1.1.3}/pyproject.toml +10 -2
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/README +1 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/env.py +76 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/script.py.mako +28 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/7c3e9f1a2b4d_add_observation_retention_policy.py +96 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/93fa5748f4b5_initial_revision.py +86 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/b84d61a9c2e7_rename_retention_database_bytes.py +34 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/d2a4c6e8f010_add_control_operation_lease.py +44 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic/versions/f1490ca3b908_persist_latest_observed_mqtt_state.py +45 -0
- topicgate-1.1.3/src/topicgate/infrastructure/database/alembic.ini +42 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/migrations.py +6 -3
- {topicgate-1.1.2 → topicgate-1.1.3/src/topicgate.egg-info}/PKG-INFO +1 -1
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/SOURCES.txt +10 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_database_migrations.py +15 -0
- topicgate-1.1.3/tests/test_migrate_app_versions.py +55 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/LICENCE +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/README.md +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/setup.cfg +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/__main__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/app_dependencies.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/broker_runtime_state.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/models/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/models/broker_snapshot.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/models/mcp_setup.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/broker_profile_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/broker_resolver.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/broker_snapshot_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/control_operation_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/mcp_setup_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/observation_cache_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/observation_retention_policy_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/persistence_lifecycle.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/service_container.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/services/service_item.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/app/topicgate_runtime.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/assets/icon.png +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/assets/icon.svg +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/config/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/config/app_config.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/config/mqtt_config.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/broker_profile_store.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/mqtt_message_processor.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/observer_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/interfaces/topic_message_store.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/broker_profile.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/broker_profile_identity.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/broker_summary.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/connection_status.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/message_filter.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/mqtt_message.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/mqtt_observation.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/mqtt_state.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observation_cache_administration.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observation_deletion_preview.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observation_retention_policy.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observer_model.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/observer_workspace.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/subscription.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/models/topic_message.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/mqtt_topics.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/observer_limits.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/core/payload_limits.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/app.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/broker_state_reader.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/about_dialog.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/add_subscription_dialog.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/application_header.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/broker_settings_dialog.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/connection_controls.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/connection_status.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/log_console.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/mcp_setup_dialog.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/observer_tree.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/onboarding_panel.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/publish_pane.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/snapshot_panel.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/stored_observations_dialog.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/subscription_settings.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/topic_details.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/topic_metadata.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/components/workspace_pane.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/gui.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/main_view_model.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/main_window.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/observer_state_reader.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/settings_migration.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/gui/theme.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/credentials/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/credentials/credential_store.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/credentials/os_credential_store.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/base.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/database_context.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/broker_profile_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/config_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/mapper_helper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/observation_retention_policy_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/observer_workspace_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/subscription_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/mappers/topic_message_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/app_config_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/broker_profile_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/mqtt_config_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/mqtt_message_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/observation_retention_policy_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/observer_workspace_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/database/models/subscription_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/async_callback_bridge.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/callbacks/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/callbacks/basic_callbacks.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/callbacks/observer_repository_callbacks.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/mqtt_callbacks.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/mqtt_client.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/mqtt/mqtt_gate.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/broker_config_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/broker_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/observation_retention_policy_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/observer_mqtt_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/subscription_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/infrastructure/repository/topic_message_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/broker_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/connection_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/dashboard_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/dashboard_snapshot.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/mcp_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/preview/dashboard_preview.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/publish_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/snapshot_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/subscription_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/api/topic_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/capabilities.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/instructions.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/middleware/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/middleware/error_handling_middleware.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/middleware/logging_middleware.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/models.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/mcp/server.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/paths.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/retention_presentation.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/snapshot_presentation.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/presentation/topic_presentation.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/__init__.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/observation_retention_processor.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/observer_model_mqtt_message_processor.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/observer_model_processor.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate/processors/subscription_manager.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/dependency_links.txt +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/entry_points.txt +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/requires.txt +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/src/topicgate.egg-info/top_level.txt +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_app.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_app_dependencies.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_async_callback_bridge.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_config_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_repository_persistence.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_resolver.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_broker_snapshot_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_control_operation_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_dashboard_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_dashboard_dependencies.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_dashboard_preview.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_desktop_snapshot_states.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_gui.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_main_view_model.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_apis.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_disconnected_startup.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_middleware.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_server_instructions.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mcp_setup_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mqtt_callbacks.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mqtt_client.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_mqtt_message_row.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_cache_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_retention_policy.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_retention_policy_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observation_retention_policy_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_model_hydration.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_model_processor.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_model_service.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_observer_workspace_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_os_credential_store.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_paths.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_persistence_lifecycle.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_plugin_bundle.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_retention_administration.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_service_container.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_settings_migration.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_snapshot_mcp_api.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_snapshot_presentation.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription_manager.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription_mapper.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_subscription_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_topic_message_repository.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_topic_presentation.py +0 -0
- {topicgate-1.1.2 → topicgate-1.1.3}/tests/test_topicgate_runtime.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "topicgate"
|
|
7
|
-
version = "1.1.
|
|
7
|
+
version = "1.1.3"
|
|
8
8
|
description = "Secure local access to your MQTT topics"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -44,4 +44,12 @@ topicgate-gui = "topicgate.gui.app:run"
|
|
|
44
44
|
where = ["src"]
|
|
45
45
|
|
|
46
46
|
[tool.setuptools.package-data]
|
|
47
|
-
topicgate = [
|
|
47
|
+
topicgate = [
|
|
48
|
+
"assets/*.png",
|
|
49
|
+
"assets/*.svg",
|
|
50
|
+
"infrastructure/database/alembic.ini",
|
|
51
|
+
"infrastructure/database/alembic/*.py",
|
|
52
|
+
"infrastructure/database/alembic/*.mako",
|
|
53
|
+
"infrastructure/database/alembic/README",
|
|
54
|
+
"infrastructure/database/alembic/versions/*.py",
|
|
55
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Generic single-database configuration.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from sqlalchemy import engine_from_config
|
|
2
|
+
from sqlalchemy import pool
|
|
3
|
+
from alembic import context
|
|
4
|
+
from topicgate.infrastructure.database.base import Base
|
|
5
|
+
from topicgate.paths import prepare_database_path, sqlite_url
|
|
6
|
+
import topicgate.infrastructure.database.models # noqa: F401
|
|
7
|
+
|
|
8
|
+
config = context.config
|
|
9
|
+
|
|
10
|
+
if config.attributes.get("connection") is None:
|
|
11
|
+
database_url = sqlite_url(prepare_database_path())
|
|
12
|
+
config.set_main_option("sqlalchemy.url", database_url.replace("%", "%%"))
|
|
13
|
+
|
|
14
|
+
target_metadata = Base.metadata
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_migrations_offline() -> None:
|
|
18
|
+
"""Run migrations in 'offline' mode.
|
|
19
|
+
|
|
20
|
+
This configures the context with just a URL
|
|
21
|
+
and not an Engine, though an Engine is acceptable
|
|
22
|
+
here as well. By skipping the Engine creation
|
|
23
|
+
we don't even need a DBAPI to be available.
|
|
24
|
+
|
|
25
|
+
Calls to context.execute() here emit the given string to the
|
|
26
|
+
script output.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
30
|
+
context.configure(
|
|
31
|
+
url=url,
|
|
32
|
+
target_metadata=target_metadata,
|
|
33
|
+
literal_binds=True,
|
|
34
|
+
dialect_opts={"paramstyle": "named"},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
with context.begin_transaction():
|
|
38
|
+
context.run_migrations()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_migrations_online() -> None:
|
|
42
|
+
"""Run migrations in 'online' mode.
|
|
43
|
+
|
|
44
|
+
In this scenario we need to create an Engine
|
|
45
|
+
and associate a connection with the context.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
supplied_connection = config.attributes.get("connection")
|
|
49
|
+
if supplied_connection is not None:
|
|
50
|
+
context.configure(
|
|
51
|
+
connection=supplied_connection,
|
|
52
|
+
target_metadata=target_metadata,
|
|
53
|
+
)
|
|
54
|
+
with context.begin_transaction():
|
|
55
|
+
context.run_migrations()
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
connectable = engine_from_config(
|
|
59
|
+
config.get_section(config.config_ini_section, {}),
|
|
60
|
+
prefix="sqlalchemy.",
|
|
61
|
+
poolclass=pool.NullPool,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
with connectable.connect() as connection:
|
|
65
|
+
context.configure(
|
|
66
|
+
connection=connection, target_metadata=target_metadata
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
with context.begin_transaction():
|
|
70
|
+
context.run_migrations()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if context.is_offline_mode():
|
|
74
|
+
run_migrations_offline()
|
|
75
|
+
else:
|
|
76
|
+
run_migrations_online()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
${imports if imports else ""}
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = ${repr(up_revision)}
|
|
16
|
+
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
|
|
17
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
18
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
"""Upgrade schema."""
|
|
23
|
+
${upgrades if upgrades else "pass"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def downgrade() -> None:
|
|
27
|
+
"""Downgrade schema."""
|
|
28
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Add observation retention policy
|
|
2
|
+
|
|
3
|
+
Revision ID: 7c3e9f1a2b4d
|
|
4
|
+
Revises: f1490ca3b908
|
|
5
|
+
Create Date: 2026-08-17 00:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
revision: str = "7c3e9f1a2b4d"
|
|
15
|
+
down_revision: Union[str, Sequence[str], None] = "f1490ca3b908"
|
|
16
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
17
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
"""Create and seed the application-wide retention policy."""
|
|
22
|
+
policy = op.create_table(
|
|
23
|
+
"observation_retention_policy",
|
|
24
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
25
|
+
sa.Column("max_entries_per_broker", sa.Integer(), nullable=False),
|
|
26
|
+
sa.Column("max_entries_total", sa.Integer(), nullable=False),
|
|
27
|
+
sa.Column("warning_threshold", sa.Float(), nullable=False),
|
|
28
|
+
sa.Column("max_payload_bytes_per_topic", sa.Integer(), nullable=False),
|
|
29
|
+
sa.Column("max_payload_bytes_per_broker", sa.Integer(), nullable=False),
|
|
30
|
+
sa.Column("max_database_bytes", sa.Integer(), nullable=False),
|
|
31
|
+
sa.Column("max_age_seconds", sa.Integer(), nullable=True),
|
|
32
|
+
sa.Column("auto_remove_expired", sa.Boolean(), nullable=False),
|
|
33
|
+
sa.Column("auto_remove_excess", sa.Boolean(), nullable=False),
|
|
34
|
+
sa.Column("auto_remove_unsubscribed", sa.Boolean(), nullable=False),
|
|
35
|
+
sa.CheckConstraint("id = 1", name="ck_retention_policy_singleton"),
|
|
36
|
+
sa.CheckConstraint(
|
|
37
|
+
"max_entries_per_broker > 0",
|
|
38
|
+
name="ck_retention_policy_broker_entries_positive",
|
|
39
|
+
),
|
|
40
|
+
sa.CheckConstraint(
|
|
41
|
+
"max_entries_total > 0",
|
|
42
|
+
name="ck_retention_policy_total_entries_positive",
|
|
43
|
+
),
|
|
44
|
+
sa.CheckConstraint(
|
|
45
|
+
"max_entries_per_broker <= max_entries_total",
|
|
46
|
+
name="ck_retention_policy_entry_limits_ordered",
|
|
47
|
+
),
|
|
48
|
+
sa.CheckConstraint(
|
|
49
|
+
"warning_threshold > 0 AND warning_threshold <= 1",
|
|
50
|
+
name="ck_retention_policy_warning_threshold",
|
|
51
|
+
),
|
|
52
|
+
sa.CheckConstraint(
|
|
53
|
+
"max_payload_bytes_per_topic > 0",
|
|
54
|
+
name="ck_retention_policy_topic_payload_positive",
|
|
55
|
+
),
|
|
56
|
+
sa.CheckConstraint(
|
|
57
|
+
"max_payload_bytes_per_broker > 0",
|
|
58
|
+
name="ck_retention_policy_broker_payload_positive",
|
|
59
|
+
),
|
|
60
|
+
sa.CheckConstraint(
|
|
61
|
+
"max_payload_bytes_per_topic <= max_payload_bytes_per_broker",
|
|
62
|
+
name="ck_retention_policy_payload_limits_ordered",
|
|
63
|
+
),
|
|
64
|
+
sa.CheckConstraint(
|
|
65
|
+
"max_database_bytes > 0",
|
|
66
|
+
name="ck_retention_policy_database_bytes_positive",
|
|
67
|
+
),
|
|
68
|
+
sa.CheckConstraint(
|
|
69
|
+
"max_age_seconds IS NULL OR max_age_seconds > 0",
|
|
70
|
+
name="ck_retention_policy_age_positive",
|
|
71
|
+
),
|
|
72
|
+
sa.PrimaryKeyConstraint("id"),
|
|
73
|
+
)
|
|
74
|
+
op.bulk_insert(
|
|
75
|
+
policy,
|
|
76
|
+
[
|
|
77
|
+
{
|
|
78
|
+
"id": 1,
|
|
79
|
+
"max_entries_per_broker": 1_000,
|
|
80
|
+
"max_entries_total": 10_000,
|
|
81
|
+
"warning_threshold": 0.80,
|
|
82
|
+
"max_payload_bytes_per_topic": 64 * 1024,
|
|
83
|
+
"max_payload_bytes_per_broker": 8 * 1024 * 1024,
|
|
84
|
+
"max_database_bytes": 256 * 1024 * 1024,
|
|
85
|
+
"max_age_seconds": None,
|
|
86
|
+
"auto_remove_expired": True,
|
|
87
|
+
"auto_remove_excess": True,
|
|
88
|
+
"auto_remove_unsubscribed": False,
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def downgrade() -> None:
|
|
95
|
+
"""Remove the observation retention policy."""
|
|
96
|
+
op.drop_table("observation_retention_policy")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Initial revision
|
|
2
|
+
|
|
3
|
+
Revision ID: 93fa5748f4b5
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-08-15 10:42:50.777383
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = "93fa5748f4b5"
|
|
16
|
+
down_revision: Union[str, Sequence[str], None] = None
|
|
17
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
18
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
"""Upgrade schema."""
|
|
23
|
+
op.create_table(
|
|
24
|
+
"mqtt_config",
|
|
25
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
26
|
+
sa.Column("host", sa.String(), nullable=False),
|
|
27
|
+
sa.Column("port", sa.Integer(), nullable=False),
|
|
28
|
+
sa.Column("username", sa.String(), nullable=False),
|
|
29
|
+
sa.Column("use_tls", sa.Boolean(), nullable=False),
|
|
30
|
+
sa.PrimaryKeyConstraint("id"),
|
|
31
|
+
)
|
|
32
|
+
op.create_table(
|
|
33
|
+
"subscription",
|
|
34
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
35
|
+
sa.Column("topic_filter", sa.String(), nullable=False),
|
|
36
|
+
sa.Column("qos", sa.Integer(), nullable=False),
|
|
37
|
+
sa.Column("retain_as_published", sa.Boolean(), nullable=False),
|
|
38
|
+
sa.Column("retain_handling", sa.Integer(), nullable=False),
|
|
39
|
+
sa.PrimaryKeyConstraint("id"),
|
|
40
|
+
)
|
|
41
|
+
op.create_table(
|
|
42
|
+
"app_config",
|
|
43
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
44
|
+
sa.Column("mqtt_config_id", sa.Integer(), nullable=False),
|
|
45
|
+
sa.ForeignKeyConstraint(["mqtt_config_id"], ["mqtt_config.id"]),
|
|
46
|
+
sa.PrimaryKeyConstraint("id"),
|
|
47
|
+
sa.UniqueConstraint("mqtt_config_id"),
|
|
48
|
+
)
|
|
49
|
+
op.create_table(
|
|
50
|
+
"broker_profile",
|
|
51
|
+
sa.Column("id", sa.Uuid(), nullable=False),
|
|
52
|
+
sa.Column("name", sa.String(), nullable=False),
|
|
53
|
+
sa.Column("position", sa.Integer(), nullable=False),
|
|
54
|
+
sa.Column("is_active", sa.Boolean(), nullable=False),
|
|
55
|
+
sa.Column("mqtt_config_id", sa.Integer(), nullable=False),
|
|
56
|
+
sa.ForeignKeyConstraint(["mqtt_config_id"], ["mqtt_config.id"]),
|
|
57
|
+
sa.PrimaryKeyConstraint("id"),
|
|
58
|
+
sa.UniqueConstraint("mqtt_config_id"),
|
|
59
|
+
sa.UniqueConstraint("name"),
|
|
60
|
+
)
|
|
61
|
+
op.create_table(
|
|
62
|
+
"observer_workspace",
|
|
63
|
+
sa.Column("id", sa.Uuid(), nullable=False),
|
|
64
|
+
sa.Column("profile_id", sa.Uuid(), nullable=False),
|
|
65
|
+
sa.ForeignKeyConstraint(["profile_id"], ["broker_profile.id"]),
|
|
66
|
+
sa.PrimaryKeyConstraint("id"),
|
|
67
|
+
sa.UniqueConstraint("profile_id"),
|
|
68
|
+
)
|
|
69
|
+
op.create_table(
|
|
70
|
+
"workspace_subscription",
|
|
71
|
+
sa.Column("workspace_id", sa.Uuid(), nullable=False),
|
|
72
|
+
sa.Column("subscription_id", sa.Integer(), nullable=False),
|
|
73
|
+
sa.ForeignKeyConstraint(["subscription_id"], ["subscription.id"]),
|
|
74
|
+
sa.ForeignKeyConstraint(["workspace_id"], ["observer_workspace.id"]),
|
|
75
|
+
sa.PrimaryKeyConstraint("workspace_id", "subscription_id"),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def downgrade() -> None:
|
|
80
|
+
"""Downgrade schema."""
|
|
81
|
+
op.drop_table("workspace_subscription")
|
|
82
|
+
op.drop_table("observer_workspace")
|
|
83
|
+
op.drop_table("broker_profile")
|
|
84
|
+
op.drop_table("app_config")
|
|
85
|
+
op.drop_table("subscription")
|
|
86
|
+
op.drop_table("mqtt_config")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Rename the logical persisted payload byte limit.
|
|
2
|
+
|
|
3
|
+
Revision ID: b84d61a9c2e7
|
|
4
|
+
Revises: 7c3e9f1a2b4d
|
|
5
|
+
Create Date: 2026-08-17 00:00:00.000000
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
revision: str = "b84d61a9c2e7"
|
|
14
|
+
down_revision: Union[str, Sequence[str], None] = "7c3e9f1a2b4d"
|
|
15
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
16
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
"""Describe the limit as logical persisted payload bytes."""
|
|
21
|
+
op.execute(
|
|
22
|
+
"ALTER TABLE observation_retention_policy "
|
|
23
|
+
"RENAME COLUMN max_database_bytes "
|
|
24
|
+
"TO max_persisted_payload_database_bytes_total"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def downgrade() -> None:
|
|
29
|
+
"""Restore the previous retention-policy column name."""
|
|
30
|
+
op.execute(
|
|
31
|
+
"ALTER TABLE observation_retention_policy "
|
|
32
|
+
"RENAME COLUMN max_persisted_payload_database_bytes_total "
|
|
33
|
+
"TO max_database_bytes"
|
|
34
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Add cross-process control operation lease.
|
|
2
|
+
|
|
3
|
+
Revision ID: d2a4c6e8f010
|
|
4
|
+
Revises: b84d61a9c2e7
|
|
5
|
+
Create Date: 2026-08-20 00:00:00.000000
|
|
6
|
+
"""
|
|
7
|
+
from typing import Sequence, Union
|
|
8
|
+
|
|
9
|
+
from alembic import op
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
revision: str = "d2a4c6e8f010"
|
|
14
|
+
down_revision: Union[str, Sequence[str], None] = "b84d61a9c2e7"
|
|
15
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
16
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
"""Create the singleton lease used by desktop and MCP control mode."""
|
|
21
|
+
state = op.create_table(
|
|
22
|
+
"control_operation_state",
|
|
23
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
24
|
+
sa.Column("generation", sa.Integer(), nullable=False),
|
|
25
|
+
sa.CheckConstraint("id = 1", name="ck_control_operation_state_singleton"),
|
|
26
|
+
sa.PrimaryKeyConstraint("id"),
|
|
27
|
+
)
|
|
28
|
+
op.bulk_insert(state, [{"id": 1, "generation": 0}])
|
|
29
|
+
op.create_table(
|
|
30
|
+
"control_operation_lease",
|
|
31
|
+
sa.Column("id", sa.Integer(), nullable=False),
|
|
32
|
+
sa.Column("owner", sa.String(length=32), nullable=False),
|
|
33
|
+
sa.Column("operation", sa.String(length=128), nullable=False),
|
|
34
|
+
sa.Column("token", sa.String(length=36), nullable=False),
|
|
35
|
+
sa.Column("expires_at", sa.Float(), nullable=False),
|
|
36
|
+
sa.CheckConstraint("id = 1", name="ck_control_operation_lease_singleton"),
|
|
37
|
+
sa.PrimaryKeyConstraint("id"),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def downgrade() -> None:
|
|
42
|
+
"""Remove the cross-process control lease."""
|
|
43
|
+
op.drop_table("control_operation_lease")
|
|
44
|
+
op.drop_table("control_operation_state")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Persist latest observed MQTT state
|
|
2
|
+
|
|
3
|
+
Revision ID: f1490ca3b908
|
|
4
|
+
Revises: 93fa5748f4b5
|
|
5
|
+
Create Date: 2026-08-15 11:08:02.781757
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = "f1490ca3b908"
|
|
16
|
+
down_revision: Union[str, Sequence[str], None] = "93fa5748f4b5"
|
|
17
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
18
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
"""Upgrade schema."""
|
|
23
|
+
op.create_table(
|
|
24
|
+
"mqtt_message",
|
|
25
|
+
sa.Column("broker_id", sa.Uuid(), nullable=False),
|
|
26
|
+
sa.Column("topic", sa.String(), nullable=False),
|
|
27
|
+
sa.Column("payload", sa.LargeBinary(), nullable=False),
|
|
28
|
+
sa.Column("qos", sa.Integer(), nullable=False),
|
|
29
|
+
sa.Column("retain", sa.Boolean(), nullable=False),
|
|
30
|
+
sa.Column("received_at", sa.DateTime(timezone=True), nullable=False),
|
|
31
|
+
sa.Column("payload_size", sa.Integer(), nullable=False),
|
|
32
|
+
sa.Column("message_count", sa.Integer(), nullable=False),
|
|
33
|
+
sa.Column("observation_id", sa.Uuid(), nullable=False),
|
|
34
|
+
sa.ForeignKeyConstraint(
|
|
35
|
+
["broker_id"],
|
|
36
|
+
["broker_profile.id"],
|
|
37
|
+
ondelete="CASCADE",
|
|
38
|
+
),
|
|
39
|
+
sa.PrimaryKeyConstraint("broker_id", "topic"),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def downgrade() -> None:
|
|
44
|
+
"""Downgrade schema."""
|
|
45
|
+
op.drop_table("mqtt_message")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# A generic, single database configuration.
|
|
2
|
+
|
|
3
|
+
[alembic]
|
|
4
|
+
script_location = %(here)s/alembic
|
|
5
|
+
|
|
6
|
+
path_separator = os
|
|
7
|
+
|
|
8
|
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
|
9
|
+
|
|
10
|
+
[loggers]
|
|
11
|
+
keys = root,sqlalchemy,alembic
|
|
12
|
+
|
|
13
|
+
[handlers]
|
|
14
|
+
keys = console
|
|
15
|
+
|
|
16
|
+
[formatters]
|
|
17
|
+
keys = generic
|
|
18
|
+
|
|
19
|
+
[logger_root]
|
|
20
|
+
level = WARNING
|
|
21
|
+
handlers = console
|
|
22
|
+
qualname =
|
|
23
|
+
|
|
24
|
+
[logger_sqlalchemy]
|
|
25
|
+
level = WARNING
|
|
26
|
+
handlers =
|
|
27
|
+
qualname = sqlalchemy.engine
|
|
28
|
+
|
|
29
|
+
[logger_alembic]
|
|
30
|
+
level = INFO
|
|
31
|
+
handlers =
|
|
32
|
+
qualname = alembic
|
|
33
|
+
|
|
34
|
+
[handler_console]
|
|
35
|
+
class = StreamHandler
|
|
36
|
+
args = (sys.stderr,)
|
|
37
|
+
level = NOTSET
|
|
38
|
+
formatter = generic
|
|
39
|
+
|
|
40
|
+
[formatter_generic]
|
|
41
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
42
|
+
datefmt = %H:%M:%S
|
|
@@ -17,6 +17,10 @@ BASELINE_TABLES = {
|
|
|
17
17
|
"workspace_subscription",
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
_DATABASE_PACKAGE_DIR = Path(__file__).resolve().parent
|
|
21
|
+
_ALEMBIC_CONFIG_PATH = _DATABASE_PACKAGE_DIR / "alembic.ini"
|
|
22
|
+
_ALEMBIC_SCRIPT_LOCATION = _DATABASE_PACKAGE_DIR / "alembic"
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
def upgrade_database(engine: Engine) -> None:
|
|
22
26
|
"""Upgrade a new or existing TopicGate database to the latest schema."""
|
|
@@ -50,8 +54,7 @@ def upgrade_database(engine: Engine) -> None:
|
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
def _alembic_config(connection: Connection) -> Config:
|
|
53
|
-
|
|
54
|
-
config
|
|
55
|
-
config.set_main_option("script_location", str(repository_root / "alembic"))
|
|
57
|
+
config = Config(str(_ALEMBIC_CONFIG_PATH))
|
|
58
|
+
config.set_main_option("script_location", str(_ALEMBIC_SCRIPT_LOCATION))
|
|
56
59
|
config.attributes["connection"] = connection
|
|
57
60
|
return config
|
|
@@ -89,9 +89,18 @@ src/topicgate/infrastructure/credentials/__init__.py
|
|
|
89
89
|
src/topicgate/infrastructure/credentials/credential_store.py
|
|
90
90
|
src/topicgate/infrastructure/credentials/os_credential_store.py
|
|
91
91
|
src/topicgate/infrastructure/database/__init__.py
|
|
92
|
+
src/topicgate/infrastructure/database/alembic.ini
|
|
92
93
|
src/topicgate/infrastructure/database/base.py
|
|
93
94
|
src/topicgate/infrastructure/database/database_context.py
|
|
94
95
|
src/topicgate/infrastructure/database/migrations.py
|
|
96
|
+
src/topicgate/infrastructure/database/alembic/README
|
|
97
|
+
src/topicgate/infrastructure/database/alembic/env.py
|
|
98
|
+
src/topicgate/infrastructure/database/alembic/script.py.mako
|
|
99
|
+
src/topicgate/infrastructure/database/alembic/versions/7c3e9f1a2b4d_add_observation_retention_policy.py
|
|
100
|
+
src/topicgate/infrastructure/database/alembic/versions/93fa5748f4b5_initial_revision.py
|
|
101
|
+
src/topicgate/infrastructure/database/alembic/versions/b84d61a9c2e7_rename_retention_database_bytes.py
|
|
102
|
+
src/topicgate/infrastructure/database/alembic/versions/d2a4c6e8f010_add_control_operation_lease.py
|
|
103
|
+
src/topicgate/infrastructure/database/alembic/versions/f1490ca3b908_persist_latest_observed_mqtt_state.py
|
|
95
104
|
src/topicgate/infrastructure/database/mappers/broker_profile_mapper.py
|
|
96
105
|
src/topicgate/infrastructure/database/mappers/config_mapper.py
|
|
97
106
|
src/topicgate/infrastructure/database/mappers/mapper_helper.py
|
|
@@ -169,6 +178,7 @@ tests/test_mcp_disconnected_startup.py
|
|
|
169
178
|
tests/test_mcp_middleware.py
|
|
170
179
|
tests/test_mcp_server_instructions.py
|
|
171
180
|
tests/test_mcp_setup_service.py
|
|
181
|
+
tests/test_migrate_app_versions.py
|
|
172
182
|
tests/test_mqtt_callbacks.py
|
|
173
183
|
tests/test_mqtt_client.py
|
|
174
184
|
tests/test_mqtt_message_row.py
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from alembic import command
|
|
3
5
|
from sqlalchemy import create_engine, inspect, text
|
|
@@ -9,6 +11,7 @@ from topicgate.infrastructure.database.migrations import (
|
|
|
9
11
|
BASELINE_REVISION,
|
|
10
12
|
_alembic_config,
|
|
11
13
|
)
|
|
14
|
+
import topicgate.infrastructure.database.migrations as migrations
|
|
12
15
|
import topicgate.infrastructure.database.models # noqa: F401
|
|
13
16
|
|
|
14
17
|
|
|
@@ -37,6 +40,18 @@ def test_new_database_is_migrated_to_head(tmp_path) -> None:
|
|
|
37
40
|
engine.dispose()
|
|
38
41
|
|
|
39
42
|
|
|
43
|
+
def test_alembic_config_uses_package_local_migrations() -> None:
|
|
44
|
+
config = _alembic_config(None)
|
|
45
|
+
package_database_dir = Path(migrations.__file__).resolve().parent
|
|
46
|
+
|
|
47
|
+
assert Path(config.config_file_name).resolve() == (
|
|
48
|
+
package_database_dir / "alembic.ini"
|
|
49
|
+
)
|
|
50
|
+
assert Path(config.get_main_option("script_location")).resolve() == (
|
|
51
|
+
package_database_dir / "alembic"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
40
55
|
def test_sqlite_connections_enable_wal_and_busy_timeout(tmp_path) -> None:
|
|
41
56
|
database_path = tmp_path / "coordination.db"
|
|
42
57
|
database = DatabaseContext(f"sqlite:///{database_path.as_posix()}")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
REPOSITORY_ROOT = Path(__file__).parents[1]
|
|
9
|
+
SCRIPT = REPOSITORY_ROOT / "release" / "migrate_app_versions.py"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_migrate_app_versions_updates_release_version_sources(tmp_path: Path) -> None:
|
|
13
|
+
for relative_path in (
|
|
14
|
+
"pyproject.toml",
|
|
15
|
+
"server.json",
|
|
16
|
+
".claude-plugin/marketplace.json",
|
|
17
|
+
"topicgate-plugin/plugin.json",
|
|
18
|
+
"topicgate-plugin/.codex-plugin/plugin.json",
|
|
19
|
+
"topicgate-plugin/.claude-plugin/plugin.json",
|
|
20
|
+
):
|
|
21
|
+
source = REPOSITORY_ROOT / relative_path
|
|
22
|
+
destination = tmp_path / relative_path
|
|
23
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
shutil.copy2(source, destination)
|
|
25
|
+
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
[sys.executable, str(SCRIPT), "2.1.4"],
|
|
28
|
+
cwd=tmp_path,
|
|
29
|
+
capture_output=True,
|
|
30
|
+
check=False,
|
|
31
|
+
text=True,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
assert result.returncode == 0, result.stderr
|
|
35
|
+
assert "Migrated application versions to 2.1.4" in result.stdout
|
|
36
|
+
|
|
37
|
+
for relative_path in (
|
|
38
|
+
"topicgate-plugin/plugin.json",
|
|
39
|
+
"topicgate-plugin/.codex-plugin/plugin.json",
|
|
40
|
+
"topicgate-plugin/.claude-plugin/plugin.json",
|
|
41
|
+
):
|
|
42
|
+
manifest = json.loads((tmp_path / relative_path).read_text(encoding="utf-8"))
|
|
43
|
+
assert manifest["version"] == "2.1.4"
|
|
44
|
+
|
|
45
|
+
marketplace = json.loads(
|
|
46
|
+
(tmp_path / ".claude-plugin/marketplace.json").read_text(encoding="utf-8")
|
|
47
|
+
)
|
|
48
|
+
assert marketplace["plugins"][0]["version"] == "2.1.4"
|
|
49
|
+
|
|
50
|
+
server = json.loads((tmp_path / "server.json").read_text(encoding="utf-8"))
|
|
51
|
+
assert server["version"] == "2.1.4"
|
|
52
|
+
assert server["packages"][0]["version"] == "2.1.4"
|
|
53
|
+
|
|
54
|
+
pyproject = (tmp_path / "pyproject.toml").read_text(encoding="utf-8")
|
|
55
|
+
assert 'version = "2.1.4"' in pyproject
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|