diff --git a/Makefile b/Makefile index 285d46c3b28..772b64ccdff 100644 --- a/Makefile +++ b/Makefile @@ -116,12 +116,15 @@ config-convert-dry-run: ## convert old style galaxy ini to yaml (dry run) config-convert: ## convert old style galaxy ini to yaml $(CONFIG_MANAGE) convert galaxy -config-rebuild: ## Rebuild all sample YAML and RST files from config schema +config-rebuild: ## Rebuild all sample YAML, RST files, and type stubs from config schema $(CONFIG_MANAGE) build_sample_yaml galaxy --add-comments $(CONFIG_MANAGE) build_rst galaxy > doc/source/admin/galaxy_options.rst + $(CONFIG_MANAGE) build_config_types galaxy $(CONFIG_MANAGE) build_sample_yaml reports --add-comments $(CONFIG_MANAGE) build_rst reports > doc/source/admin/reports_options.rst + $(CONFIG_MANAGE) build_config_types reports $(CONFIG_MANAGE) build_sample_yaml tool_shed --add-comments + $(CONFIG_MANAGE) build_config_types tool_shed config-lint: ## lint galaxy YAML configuration file $(CONFIG_MANAGE) lint galaxy diff --git a/lib/galaxy/authnz/psa_authnz.py b/lib/galaxy/authnz/psa_authnz.py index c7413809e78..b7e35c6a7fa 100644 --- a/lib/galaxy/authnz/psa_authnz.py +++ b/lib/galaxy/authnz/psa_authnz.py @@ -174,7 +174,7 @@ class PSAAuthnz(IdentityProvider): auth_pipeline = app_config.oidc_auth_pipeline or AUTH_PIPELINE # Add extra steps to the auth pipeline if configured. if app_config.oidc_auth_pipeline_extra: - auth_pipeline = auth_pipeline + tuple(app_config.oidc_auth_pipeline_extra) + auth_pipeline = tuple(auth_pipeline) + tuple(app_config.oidc_auth_pipeline_extra) self.config["SOCIAL_AUTH_PIPELINE"] = auth_pipeline self.config["DISCONNECT_PIPELINE"] = DISCONNECT_PIPELINE self.config[setting_name("AUTHENTICATION_BACKENDS")] = (BACKENDS[provider],) diff --git a/lib/galaxy/config/__init__.py b/lib/galaxy/config/__init__.py index 07dd38adbfa..ace3236d531 100644 --- a/lib/galaxy/config/__init__.py +++ b/lib/galaxy/config/__init__.py @@ -32,6 +32,7 @@ from urllib.parse import urlparse import yaml +from galaxy.config._galaxy_config_schema_attributes import GalaxyAppConfigurationAttributes from galaxy.config.schema import AppSchema from galaxy.exceptions import ConfigurationError from galaxy.util import ( @@ -625,7 +626,7 @@ class BaseAppConfiguration(HasDynamicProperties): class CommonConfigurationMixin: """Shared configuration settings code for Galaxy and ToolShed.""" - sentry_dsn: str + sentry_dsn: Optional[str] config_dict: dict[str, str] @property @@ -669,7 +670,7 @@ class CommonConfigurationMixin: raise ConfigurationError(f"Unable to create missing directory: {path}\n{unicodify(e)}") -class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): +class GalaxyAppConfiguration(GalaxyAppConfigurationAttributes, BaseAppConfiguration, CommonConfigurationMixin): renamed_options = { "blacklist_file": "email_domain_blocklist_file", "whitelist_file": "email_domain_allowlist_file", @@ -738,7 +739,6 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): } allow_local_account_creation: bool - allowed_origin_hostnames: list[str] builds_file_path: str container_resolvers_config_file: str database_connection: str @@ -752,10 +752,8 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): hash_function: HashFunctionNameEnum integrated_tool_panel_config: str involucro_path: str - len_file_path: str manage_dependency_relationships: bool monitor_thread_join_timeout: int - mulled_channels: list[str] new_file_path: str nginx_upload_store: str password_expiration_period: timedelta @@ -766,21 +764,12 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): themes: dict[str, dict[str, str]] themes_by_host: dict[str, dict[str, dict[str, str]]] tool_data_path: str - tool_dependency_dir: Optional[str] - tool_filters: list[str] - tool_label_filters: list[str] tool_path: str - tool_section_filters: list[str] - toolbox_filter_base_modules: list[str] track_jobs_in_database: bool trust_jupyter_notebook_conversion: bool tus_upload_store: str use_remote_user: bool user_library_import_dir_auto_creation: bool - user_library_import_symlink_allowlist: list[str] - user_tool_filters: list[str] - user_tool_label_filters: list[str] - user_tool_section_filters: list[str] visualization_plugins_directory: str workflow_resource_params_mapper: str @@ -942,7 +931,7 @@ class GalaxyAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): raise ConfigurationError(f"Unrecognized value for hash_function option: {self.hash_function}") self.hash_function = HashFunctionNameEnum[self.hash_function] self.metadata_strategy = kwargs.get("metadata_strategy", "directory") - self.use_remote_user = self.use_remote_user or self.single_user + self.use_remote_user = bool(self.use_remote_user or self.single_user) self.fetch_url_allowlist_ips = parse_allowlist_ips(listify(kwargs.get("fetch_url_allowlist"))) self.job_queue_cleanup_interval = int(kwargs.get("job_queue_cleanup_interval", "5")) diff --git a/lib/galaxy/config/_galaxy_config_schema_attributes.py b/lib/galaxy/config/_galaxy_config_schema_attributes.py new file mode 100644 index 00000000000..d669475a3b4 --- /dev/null +++ b/lib/galaxy/config/_galaxy_config_schema_attributes.py @@ -0,0 +1,473 @@ +# AUTOGENERATED by config_manage.py build_config_types — do not edit manually. +# Run `make config-rebuild` to regenerate from the config schema. +from datetime import timedelta +from typing import Any + + +class GalaxyAppConfigurationAttributes: + """Type annotations for schema-defined "galaxy" config attributes.""" + + config_dir: str + managed_config_dir: str + data_dir: str + templates_dir: str + cache_dir: str + database_connection: str | None + database_engine_option_pool_size: int + database_engine_option_max_overflow: int + database_engine_option_pool_recycle: int + database_engine_option_server_side_cursors: bool + database_query_profiling_proxy: bool + database_template: str | None + database_log_query_counts: bool + slow_query_log_threshold: float + enable_per_request_sql_debugging: bool + install_database_connection: str | None + database_auto_migrate: bool + database_wait: bool + database_wait_attempts: int + database_wait_sleep: float + history_audit_table_prune_interval: int + kombu_sqla_transport_cleanup_interval: int + file_path: str + new_file_path: str + maximum_upload_file_size: int + tool_config_file: Any + shed_tool_config_file: str + migrated_tools_config: str + integrated_tool_panel_config: str + tool_path: str + tool_dependency_dir: str | None + dependency_resolvers_config_file: str + conda_prefix: str | None + conda_exec: str | None + conda_debug: bool + conda_ensure_channels: str + conda_use_local: bool + conda_auto_install: bool + conda_auto_init: bool + conda_copy_dependencies: bool + local_conda_mapping_file: str + modules_mapping_files: str + use_cached_dependency_manager: bool + tool_dependency_cache_dir: str | None + precache_dependencies: bool + tool_sheds_config_file: str + watch_tools: str + watch_job_rules: str + watch_core_config: str + watch_tours: str + short_term_storage_dir: str + short_term_storage_default_duration: int + short_term_storage_maximum_duration: int + short_term_storage_cleanup_interval: int + bulk_storage_operation_dataset_minimum_days_to_expiration: int + bulk_storage_operation_completed_run_retention_days: int + prune_expired_bulk_storage_operations_interval: int + recover_stale_bulk_storage_operation_runs_interval: int + file_sources_config_file: str + file_sources: list[Any] + object_store_templates_config_file: str | None + object_store_templates: list[dict[str, Any]] | None + file_source_templates_config_file: str | None + file_source_templates: list[dict[str, Any]] | None + user_config_templates_use_saved_configuration: str + enable_mulled_containers: bool + container_resolvers_config_file: str | None + container_resolvers: list[Any] + involucro_path: str + involucro_auto_init: bool + mulled_channels: list[str] + enable_tool_shed_check: bool + hours_between_check: int + tool_data_table_config_path: str + shed_tool_data_table_config: str + tool_data_path: str + shed_tool_data_path: str | None + watch_tool_data_dir: str + refgenie_config_file: str | None + build_sites_config_file: str + builds_file_path: str + len_file_path: str + datatypes_config_file: str + sniff_compressed_dynamic_datatypes_default: bool + datatypes_disable_auto: bool + visualization_plugins_directory: str + tour_config_dir: str + enable_tool_generated_tours: bool + webhooks_dir: str + job_working_directory: str + template_cache_path: str + check_job_script_integrity: bool + check_job_script_integrity_count: int + check_job_script_integrity_sleep: float + default_job_shell: str + tool_search_index_dir: str + tool_tag_mappings_file: str | None + biotools_content_directory: str | None + biotools_use_api: bool + biotools_service_cache_type: str + biotools_service_cache_data_dir: str + biotools_service_cache_lock_dir: str + biotools_service_cache_url: str | None + biotools_service_cache_table_name: str + biotools_service_cache_schema_name: str | None + citation_cache_type: str + citation_cache_data_dir: str + citation_cache_lock_dir: str + citation_cache_url: str | None + citation_cache_table_name: str + citation_cache_schema_name: str | None + mulled_resolution_cache_type: str + mulled_resolution_cache_data_dir: str + mulled_resolution_cache_lock_dir: str + mulled_resolution_cache_expire: int + mulled_resolution_cache_url: str | None + mulled_resolution_cache_table_name: str + mulled_resolution_cache_schema_name: str | None + object_store_config_file: str + object_store_config: list[Any] + object_store_cache_monitor_driver: str + object_store_cache_monitor_interval: int + object_store_cache_path: str + object_store_cache_size: float + object_store_always_respect_user_selection: bool + object_store_store_by: str + smtp_server: str | None + smtp_username: str | None + smtp_password: str | None + smtp_ssl: bool + mailing_join_addr: str | None + mailing_join_subject: str + mailing_join_body: str + error_email_to: str | None + email_from: str | None + custom_activation_email_message: str | None + instance_resource_url: str | None + instance_access_url: str | None + email_domain_blocklist_file: str | None + email_domain_allowlist_file: str | None + email_ban_file: str | None + canonical_email_rules: Any + registration_warning_message: str + user_activation_on: bool + activation_grace_period: int + inactivity_box_content: str + password_expiration_period: timedelta + enable_account_interface: bool + session_duration: int + ga_code: str | None + plausible_server: str | None + plausible_domain: str | None + matomo_server: str | None + matomo_site_id: str | None + display_servers: str + enable_old_display_applications: bool + aws_estimate: bool + carbon_emission_estimates: bool + geographical_server_location_code: str + power_usage_effectiveness: float + interactivetools_enable: bool + interactivetools_upstream_proxy: bool + interactivetools_proxy_host: str | None + interactivetools_base_path: str + interactivetools_map: str | None + interactivetoolsproxy_map: str | None + interactivetools_prefix: str + retry_interactivetool_metadata_internally: bool + visualizations_visible: bool + message_box_visible: bool + message_box_content: str | None + message_box_class: str + brand: str | None + display_galaxy_brand: bool + pretty_datetime_format: str + trs_servers_config_file: str + user_preferences_extra_conf_path: str + default_locale: str + galaxy_url_prefix: str + galaxy_infrastructure_url: str + galaxy_infrastructure_web_port: int + welcome_url: str + logo_url: str + logo_src: str + logo_src_secondary: str | None + helpsite_url: str + wiki_url: str + quota_url: str + support_url: str + citation_url: str + citation_bibtex: str + release_doc_base_url: str + screencasts_url: str + terms_url: str | None + static_enabled: bool + static_cache_time: int + static_dir: str + static_dist_dir: str + static_images_dir: str + static_favicon_dir: str + static_scripts_dir: str + static_style_dir: str + static_robots_txt: str + display_chunk_size: int + apache_xsendfile: bool + nginx_x_accel_redirect_base: str | None + upstream_gzip: bool + upstream_mod_zip: bool + x_frame_options: str + nginx_upload_store: str | None + nginx_upload_path: str | None + nginx_upload_job_files_store: str | None + nginx_upload_job_files_path: str | None + tus_upload_store: str | None + tus_upload_store_job_files: str | None + chunk_upload_size: int + dynamic_proxy_manage: bool + dynamic_proxy: str + dynamic_proxy_session_map: str + dynamic_proxy_bind_port: int + dynamic_proxy_bind_ip: str + dynamic_proxy_debug: bool + dynamic_proxy_external_proxy: bool + dynamic_proxy_prefix: str + dynamic_proxy_golang_noaccess: int + dynamic_proxy_golang_clean_interval: int + dynamic_proxy_golang_docker_address: str + dynamic_proxy_golang_api_key: str | None + auto_configure_logging: bool + log_destination: str + log_rotate_size: str + log_rotate_count: int + log_level: str + logging: Any + database_engine_option_echo: bool + database_engine_option_echo_pool: bool + log_events: bool + log_actions: bool + fluent_log: bool + fluent_host: str + fluent_port: int + sanitize_all_html: bool + sanitize_allowlist_file: str + serve_xss_vulnerable_mimetypes: bool + allowed_origin_hostnames: list[str] + trust_jupyter_notebook_conversion: bool + debug: bool + use_access_logging_middleware: bool + use_lint: bool + use_profile: bool + use_printdebug: bool + monitor_thread_join_timeout: int + use_heartbeat: bool + heartbeat_interval: int + heartbeat_log: str + sentry_dsn: str | None + sentry_event_level: str + sentry_traces_sample_rate: float + sentry_client_traces_sample_rate: float + sentry_ca_certs: str | None + enable_statsd_middleware: bool + statsd_host: str | None + statsd_port: int + statsd_prefix: str + statsd_influxdb: bool + statsd_mock_calls: bool + queue_metrics_interval: int + library_import_dir: str | None + user_library_import_dir: str | None + user_library_import_dir_auto_creation: bool + user_library_import_symlink_allowlist: list[str] + user_library_import_check_permissions: bool + allow_path_paste: bool + disable_library_comptypes: str | None + tool_name_boost: float + tool_name_exact_multiplier: float + tool_id_boost: float + tool_section_boost: float + tool_description_boost: float + tool_label_boost: float + tool_stub_boost: float + tool_help_boost: float + tool_help_bm25f_k1: float + tool_search_limit: int + tool_enable_ngram_search: bool + tool_ngram_minsize: int + tool_ngram_maxsize: int + tool_ngram_factor: float + tool_test_data_directories: str + id_secret: str + use_remote_user: bool + remote_user_maildomain: str | None + remote_user_header: str + remote_user_secret: str + remote_user_logout_href: str | None + post_user_logout_href: str + normalize_remote_user_email: bool + single_user: str | None + admin_users: str | None + require_login: bool + show_welcome_with_login: bool + prefer_oidc_login: bool + allow_local_account_creation: bool + disable_local_accounts: bool + allow_user_deletion: bool + allow_user_impersonation: bool + show_user_prepopulate_form: bool + upload_from_form_button: str + allow_user_dataset_purge: bool + new_user_dataset_access_role_default_private: bool + expose_user_name: bool + expose_user_email: bool + fetch_url_allowlist: str | None + enable_beta_gdpr: bool + edam_panel_views: str + edam_toolbox_ontology_path: str + panel_views_dir: str + panel_views: list[Any] + default_panel_view: str + default_workflow_export_format: str + parallelize_workflow_scheduling_within_histories: bool + maximum_workflow_invocation_duration: int + maximum_workflow_jobs_per_scheduling_iteration: int + flush_per_n_datasets: int + max_discovered_files: int + history_local_serial_workflow_scheduling: bool + enable_oidc: bool + oidc_config_file: str + oidc_backends_config_file: str + oidc_auth_pipeline: list[Any] + oidc_auth_pipeline_extra: list[Any] + oidc_scope_prefix: str + auth_config_file: str + api_allow_run_as: str | None + bootstrap_admin_api_key: str | None + organization_name: str | None + organization_url: str | None + ga4gh_service_id: str | None + ga4gh_service_environment: str | None + enable_tool_tags: bool + enable_unique_workflow_defaults: bool + simplified_workflow_run_ui: str + simplified_workflow_run_ui_target_history: str + simplified_workflow_run_ui_job_cache: str + ftp_upload_site: str | None + ftp_upload_dir: str | None + ftp_upload_dir_identifier: str + ftp_upload_dir_template: str | None + ftp_upload_purge: bool + enable_quotas: bool + expose_dataset_path: bool + enable_tool_source_display: bool + job_metrics_config_file: str + job_metrics: list[Any] + expose_potentially_sensitive_job_metrics: bool + enable_legacy_sample_tracking_api: bool + enable_data_manager_user_view: bool + data_manager_config_file: str + shed_data_manager_config_file: str + galaxy_data_manager_data_path: str | None + job_config_file: str + job_config: Any + dependency_resolvers: list[Any] + dependency_resolution: Any + default_job_resubmission_condition: str | None + track_jobs_in_database: bool + use_tasked_jobs: bool + local_task_queue_workers: int + job_handler_monitor_sleep: float + job_runner_monitor_sleep: float + workflow_monitor_sleep: float + workflow_completion_monitor_sleep: float + calculate_dataset_hash: str + hash_function: str + metadata_strategy: str + retry_metadata_internally: bool + max_metadata_value_size: int + outputs_to_working_directory: bool + retry_job_output_collection: int + tool_evaluation_strategy: str + preserve_python_environment: str + cleanup_job: str + drmaa_external_runjob_script: str | None + drmaa_external_killjob_script: str | None + external_chown_script: str | None + real_system_username: str + environment_setup_file: str | None + enable_beta_markdown_export: bool + markdown_export_css: str + markdown_export_css_pages: str + markdown_export_css_invocation_reports: str + markdown_export_prologue: str + markdown_export_epilogue: str + markdown_export_prologue_pages: str + markdown_export_prologue_invocation_reports: str + markdown_export_epilogue_pages: str + markdown_export_epilogue_invocation_reports: str + job_resource_params_file: str + workflow_resource_params_file: str + workflow_resource_params_mapper: str | None + workflow_schedulers_config_file: str + workflow_scheduling_separate_materialization_iteration: bool + cache_user_job_count: bool + toolbox_auto_sort: bool + tool_filters: list[str] + tool_label_filters: list[str] + tool_section_filters: list[str] + user_tool_filters: list[str] + user_tool_section_filters: list[str] + user_tool_label_filters: list[str] + toolbox_filter_base_modules: list[str] + amqp_internal_connection: str | None + enable_celery_tasks: bool + enable_tool_requests: bool + celery_conf: Any + celery_user_rate_limit: float + celery_user_concurrency_limit: int + use_pbkdf2: bool + cookie_domain: str | None + select_type_workflow_threshold: int + ai_api_key: str | None + ai_api_base_url: str | None + ai_model: str + inference_services: Any + agent_model_capabilities_file: str + gtn_database_path: str + gtn_database_url: str + enable_tool_recommendations: bool + tool_recommendation_model_path: str + topk_recommendations: int + admin_tool_recommendations_path: str + overwrite_model_recommendations: bool + error_report_file: str + tool_destinations_config_file: str + welcome_directory: str + vault_config_file: str + vault_token_renewal_interval: int + url_headers_config_file: str + display_builtin_converters: bool + themes_config_file: str + enable_beacon_integration: bool + tool_training_recommendations: bool + tool_training_recommendations_link: str + tool_training_recommendations_api_url: str + citations_export_message_html: str + enable_sse_updates: bool + history_audit_monitor_poll_interval: int + enable_notification_system: bool + enable_mcp_server: bool + mcp_server_path: str + expired_notifications_cleanup_interval: int + dispatch_notifications_interval: int + help_forum_api_url: str + enable_help_forum_tool_panel_integration: bool + file_source_temp_dir: str | None + file_source_webdav_use_temp_files: bool + file_source_listings_expiry_time: int + install_tool_dependencies: bool + install_repository_dependencies: bool + install_resolver_dependencies: bool + enable_failed_jobs_working_directory_cleanup: bool + failed_jobs_working_directory_cleanup_days: int + failed_jobs_working_directory_cleanup_interval: int + enable_beta_tool_formats: bool diff --git a/lib/galaxy/config/_reports_config_schema_attributes.py b/lib/galaxy/config/_reports_config_schema_attributes.py new file mode 100644 index 00000000000..5b7b645a778 --- /dev/null +++ b/lib/galaxy/config/_reports_config_schema_attributes.py @@ -0,0 +1,16 @@ +# AUTOGENERATED by config_manage.py build_config_types — do not edit manually. +# Run `make config-rebuild` to regenerate from the config schema. + + +class ReportsAppConfigurationAttributes: + """Type annotations for schema-defined "reports" config attributes.""" + + log_level: str + database_connection: str + file_path: str + new_file_path: str + template_cache_path: str + use_heartbeat: bool + smtp_server: str + error_email_to: str + enable_beta_gdpr: bool diff --git a/lib/galaxy/config/_tool_shed_config_schema_attributes.py b/lib/galaxy/config/_tool_shed_config_schema_attributes.py new file mode 100644 index 00000000000..09662ed9d26 --- /dev/null +++ b/lib/galaxy/config/_tool_shed_config_schema_attributes.py @@ -0,0 +1,97 @@ +# AUTOGENERATED by config_manage.py build_config_types — do not edit manually. +# Run `make config-rebuild` to regenerate from the config schema. +from typing import Any + + +class ToolShedAppConfigurationAttributes: + """Type annotations for schema-defined "tool_shed" config attributes.""" + + log_level: str + database_connection: str + hgweb_config_dir: str | None + hgweb_repo_prefix: str + config_hg_for_dev: str | None + tool_shed_url: str | None + file_path: str + new_file_path: str + builds_file_path: str + pretty_datetime_format: str + toolshed_search_on: bool + whoosh_index_dir: str + model_cache_dir: str + repo_name_boost: float + repo_description_boost: float + repo_long_description_boost: float + repo_homepage_url_boost: float + repo_remote_repository_url_boost: float + repo_owner_username_boost: float + categories_boost: float + tool_name_boost: float + tool_description_boost: float + tool_help_boost: float + tool_repo_owner_username: float + ga_code: str | None + plausible_server: str | None + plausible_domain: str | None + matomo_server: str | None + matomo_site_id: str | None + id_secret: str + use_remote_user: bool + remote_user_secret: str + remote_user_maildomain: str | None + remote_user_header: str + remote_user_logout_href: str | None + admin_users: str | None + require_login: bool + allow_local_account_creation: bool + allow_user_deletion: bool + smtp_server: str | None + email_from: str | None + smtp_username: str | None + smtp_password: str | None + smtp_ssl: bool + support_url: str + mailing_join_addr: str + ga4gh_service_id: str | None + organization_name: str | None + organization_url: str | None + ga4gh_service_environment: str | None + use_heartbeat: bool + enable_galaxy_flavor_docker_image: bool + message_box_visible: bool + message_box_content: str | None + message_box_class: str + static_enabled: bool + static_cache_time: int + static_dir: str + static_images_dir: str + static_favicon_dir: str + static_scripts_dir: str + static_style_dir: str + enable_beta_gdpr: bool + apache_xsendfile: bool + nginx_x_accel_redirect_base: str | None + nginx_upload_path: str | None + email_domain_blocklist_file: str | None + email_domain_allowlist_file: str | None + email_ban_file: str | None + canonical_email_rules: Any + brand: str | None + citation_cache_type: str + citation_cache_data_dir: str + citation_cache_lock_dir: str + citation_cache_url: str | None + citation_cache_table_name: str + citation_cache_schema_name: str | None + log_actions: bool + password_expiration_period: int + sentry_dsn: str | None + sentry_event_level: str + sentry_traces_sample_rate: float + sentry_ca_certs: str | None + session_duration: int + terms_url: str | None + auth_config_file: str + bootstrap_admin_api_key: str | None + shed_tool_data_table_config: str + datatypes_config_file: str diff --git a/lib/galaxy/config/config_manage.py b/lib/galaxy/config/config_manage.py index 267b9f76b3c..ed05f124143 100644 --- a/lib/galaxy/config/config_manage.py +++ b/lib/galaxy/config/config_manage.py @@ -8,6 +8,7 @@ from argparse import ( ) from collections.abc import Callable from io import StringIO +from pathlib import Path from textwrap import TextWrapper from typing import ( Any, @@ -532,12 +533,118 @@ def _get_option_desc(option: dict[str, Any]) -> str: return desc +_SCHEMA_TO_PYTHON_TYPE: dict[str, str] = { + "str": "str", + "bool": "bool", + "int": "int", + "float": "float", + "any": "Any", + "seq": "list[Any]", +} + +_CONFIG_TYPE_CLASS_NAMES: dict[str, str] = { + "galaxy": "GalaxyAppConfigurationAttributes", + "tool_shed": "ToolShedAppConfigurationAttributes", + "reports": "ReportsAppConfigurationAttributes", +} + +# Per-app overrides for attributes whose runtime Python type differs from what +# the schema alone would generate. Causes include: post-processing in +# _process_config (listify, timedelta conversion), BaseAppConfiguration +# guarantees that override a null schema default, or more specific element +# types for seq attrs. +_ATTR_TYPE_OVERRIDES: dict[str, dict[str, str]] = { + "galaxy": { + # Always resolved to a concrete str by BaseAppConfiguration._set_config_base + "config_dir": "str", + "data_dir": "str", + "managed_config_dir": "str", + # BaseAppConfiguration declares str; always non-null at runtime + "object_store_store_by": "str", + # Listified by _process_config or CommonConfigurationMixin + "allowed_origin_hostnames": "list[str]", + "mulled_channels": "list[str]", + "tool_filters": "list[str]", + "tool_label_filters": "list[str]", + "tool_section_filters": "list[str]", + "toolbox_filter_base_modules": "list[str]", + "user_library_import_symlink_allowlist": "list[str]", + "user_tool_filters": "list[str]", + "user_tool_label_filters": "list[str]", + "user_tool_section_filters": "list[str]", + # Can be conditionally set to None in _process_config despite non-null schema default + "interactivetools_map": "str | None", + "tool_dependency_dir": "str | None", + # Config file paths that are optional (not required to be set) + "file_source_templates_config_file": "str | None", + "object_store_templates_config_file": "str | None", + "amqp_internal_connection": "str | None", + # seq attrs with more specific element types + "file_source_templates": "list[dict[str, Any]] | None", + "object_store_templates": "list[dict[str, Any]] | None", + # Stored as float despite int schema type + "object_store_cache_size": "float", + # Converted from int (days) to timedelta by _process_config + "password_expiration_period": "timedelta", + }, + "tool_shed": {}, + "reports": {}, +} + +_CONFIG_DIR = Path(__file__).resolve().parent + + +def _python_type_for_option(option: dict[str, Any]) -> str: + schema_type = option.get("type", "str") + default = option.get("default") + py_type = _SCHEMA_TO_PYTHON_TYPE.get(schema_type, "Any") + # Attributes with a null default remain None at runtime when not configured — + # _update_raw_config_from_kwargs skips type conversion when value is None. + if default is None and py_type in ("str", "int", "float"): + return f"{py_type} | None" + return py_type + + +def _build_config_types(args: Namespace, app_desc: App) -> None: + schema = app_desc.schema + app_name = app_desc.app_name + class_name = _CONFIG_TYPE_CLASS_NAMES[app_name] + output_path = _CONFIG_DIR / f"_{app_name}_config_schema_attributes.py" + + overrides = _ATTR_TYPE_OVERRIDES.get(app_name, {}) + attr_types = {key: overrides.get(key, _python_type_for_option(option)) for key, option in schema.app_schema.items()} + needs_any = any("Any" in t for t in attr_types.values()) + needs_timedelta = any("timedelta" in t for t in attr_types.values()) + imports = ["from datetime import timedelta"] if needs_timedelta else [] + if needs_any: + imports.append("from typing import Any") + + lines = [ + "# AUTOGENERATED by config_manage.py build_config_types — do not edit manually.", + "# Run `make config-rebuild` to regenerate from the config schema.", + *imports, + "", + "", + f"class {class_name}:", + f' """Type annotations for schema-defined "{app_name}" config attributes."""', + "", + ] + for key, py_type in attr_types.items(): + lines.append(f" {key}: {py_type}") + lines.append("") + + content = "\n".join(lines) + output_path.write_text(content) + print(f"Written: {output_path}") + + ACTIONS: dict[str, Callable] = { "convert": _run_conversion, "build_sample_yaml": _build_sample_yaml, "validate": _validate, "lint": _lint, "build_rst": _to_rst, + "build_config_types": _build_config_types, } diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index c16bc6901b0..354cd0dd5fe 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -1,5 +1,5 @@ # When committing this file, make sure to run make config-rebuild to rebuild -# all sample YAML and RST files, and add those to your commit. +# all sample YAML, RST files, and type stubs, and add those to your commit. type: map desc: | Galaxy is configured by default to be usable in a single-user development diff --git a/lib/galaxy/dependencies/__init__.py b/lib/galaxy/dependencies/__init__.py index b28bf8af973..0cb34fb839f 100644 --- a/lib/galaxy/dependencies/__init__.py +++ b/lib/galaxy/dependencies/__init__.py @@ -161,7 +161,7 @@ class ConditionalDependencies: # Parse file source templates config file_source_templates_conf_yml = self.config_object.file_source_templates_config_file - if exists(file_source_templates_conf_yml): + if file_source_templates_conf_yml and exists(file_source_templates_conf_yml): with open(file_source_templates_conf_yml) as f: file_source_templates_conf = apply_syntactic_sugar(yaml.safe_load(f)) for file_source_template in file_source_templates_conf: diff --git a/lib/galaxy/tool_util/deps/dependencies.py b/lib/galaxy/tool_util/deps/dependencies.py index bfd2f19cf86..6078ee76c1a 100644 --- a/lib/galaxy/tool_util/deps/dependencies.py +++ b/lib/galaxy/tool_util/deps/dependencies.py @@ -1,6 +1,5 @@ from typing import ( Any, - Dict, List, Optional, Union, @@ -28,7 +27,7 @@ class AppInfo: library_import_dir: Optional[str] = None, enable_mulled_containers: bool = False, container_resolvers_config_file: Optional[str] = None, - container_resolvers_config_dict: Optional[Dict[str, Any]] = None, + container_resolvers_config_dict: Optional[List[Any]] = None, involucro_path: Optional[str] = None, involucro_auto_init: bool = True, mulled_channels: List[str] = DEFAULT_CHANNELS, diff --git a/lib/tool_shed/webapp/config.py b/lib/tool_shed/webapp/config.py index 9c0e3d44964..5a70c803fd7 100644 --- a/lib/tool_shed/webapp/config.py +++ b/lib/tool_shed/webapp/config.py @@ -15,6 +15,7 @@ from galaxy.config import ( get_database_engine_options, TOOL_SHED_CONFIG_SCHEMA_PATH, ) +from galaxy.config._tool_shed_config_schema_attributes import ToolShedAppConfigurationAttributes from galaxy.config.schema import AppSchema from galaxy.exceptions import ConfigurationError from galaxy.util import string_as_bool @@ -29,7 +30,7 @@ log = logging.getLogger(__name__) TOOLSHED_APP_NAME = "tool_shed" -class ToolShedAppConfiguration(BaseAppConfiguration, CommonConfigurationMixin): +class ToolShedAppConfiguration(ToolShedAppConfigurationAttributes, BaseAppConfiguration, CommonConfigurationMixin): default_config_file_name = "tool_shed.yml" add_sample_file_to_defaults = {"datatypes_config_file"} diff --git a/test/unit/app/tools/test_error_reporting.py b/test/unit/app/tools/test_error_reporting.py index a233564ac68..f9c18c41eb5 100644 --- a/test/unit/app/tools/test_error_reporting.py +++ b/test/unit/app/tools/test_error_reporting.py @@ -27,7 +27,7 @@ class TestErrorReporter(TestCase, UsesApp): self.tmp_path = Path(tempfile.mkdtemp()) self.email_path = self.tmp_path / "email.json" smtp_server = f"mock_emails_to_path://{self.email_path}" - self.app.config.smtp_server = smtp_server # type: ignore[attr-defined] + self.app.config.smtp_server = smtp_server self.app.workflow_manager = mock.MagicMock() def tearDown(self):