This is an automated email from the ASF dual-hosted git repository. asorokoumov pushed a commit to branch fixup-0.7.0-rc3 in repository https://gitbox.apache.org/repos/asf/otava.git
commit e5e8aaf1e63e4d0549245fb6f3ef0298774b23cf Author: Alex Sorokoumov <[email protected]> AuthorDate: Thu Nov 27 20:33:36 2025 -0800 Revert "Fix multiple issues discovered via running examples (#95)" This reverts commit 5276f24de6f0853f8304374dd1b3772501863c3c. --- docs/GRAFANA.md | 2 +- docs/GRAPHITE.md | 2 +- docs/POSTGRESQL.md | 2 +- examples/postgresql/init-db/schema.sql | 12 +- otava/config.py | 26 +--- pyproject.toml | 1 - tests/config_test.py | 232 +-------------------------------- uv.lock | 13 +- 8 files changed, 13 insertions(+), 277 deletions(-) diff --git a/docs/GRAFANA.md b/docs/GRAFANA.md index 922c0ad..13e8210 100644 --- a/docs/GRAFANA.md +++ b/docs/GRAFANA.md @@ -61,7 +61,7 @@ docker-compose -f examples/graphite/docker-compose.yaml up --force-recreate --al Run otava in another tab: ```bash -docker-compose -f examples/graphite/docker-compose.yaml run otava analyze my-product.test --since=-10m --update-grafana +docker-compose -f examples/graphite/docker-compose.yaml run otava otava analyze my-product.test --since=-10m --update-grafana ``` Expected output: diff --git a/docs/GRAPHITE.md b/docs/GRAPHITE.md index 54d0e83..d7987b9 100644 --- a/docs/GRAPHITE.md +++ b/docs/GRAPHITE.md @@ -105,7 +105,7 @@ docker-compose -f examples/graphite/docker-compose.yaml up --force-recreate --al Run otava in another tab: ```bash -docker-compose -f examples/graphite/docker-compose.yaml run otava analyze my-product.test --since=-10m +docker-compose -f examples/graphite/docker-compose.yaml run otava otava analyze my-product.test --since=-10m ``` Expected output: diff --git a/docs/POSTGRESQL.md b/docs/POSTGRESQL.md index 7ecda0b..115483a 100644 --- a/docs/POSTGRESQL.md +++ b/docs/POSTGRESQL.md @@ -99,7 +99,7 @@ docker-compose -f examples/postgresql/docker-compose.yaml up --force-recreate -- Run Otava in the other tab to show results for a single test `aggregate_mem` and update the database with newly found change points: ```bash -docker-compose -f examples/postgresql/docker-compose.yaml run --build otava analyze aggregate_mem --update-postgres +docker-compose -f examples/postgresql/docker-compose.yaml run --build otava bin/otava analyze aggregate_mem --update-postgres ``` Expected output: diff --git a/examples/postgresql/init-db/schema.sql b/examples/postgresql/init-db/schema.sql index 1092331..632cf3f 100644 --- a/examples/postgresql/init-db/schema.sql +++ b/examples/postgresql/init-db/schema.sql @@ -75,12 +75,12 @@ INSERT INTO configs (id, benchmark, store, instance_type, cache) VALUES INSERT INTO experiments (id, ts, branch, commit, commit_ts, username, details_url) VALUES - ('aggregate-36e5ccd2', '2025-03-14 12:03:02+00', 'trunk', '36e5ccd2', '2025-03-13 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-36e5ccd2'), - ('aggregate-d5460f38', '2025-03-27 12:03:02+00', 'trunk', 'd5460f38', '2025-03-25 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-d5460f38'), - ('aggregate-bc9425cb', '2025-04-01 12:03:02+00', 'trunk', 'bc9425cb', '2025-04-02 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-bc9425cb'), - ('aggregate-14df1b11', '2025-04-07 12:03:02+00', 'trunk', '14df1b11', '2025-04-06 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-14df1b11'), - ('aggregate-ac40c0d8', '2025-04-14 12:03:02+00', 'trunk', 'ac40c0d8', '2025-04-13 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-ac40c0d8'), - ('aggregate-0af4ccbc', '2025-04-28 12:03:02+00', 'trunk', '0af4ccbc', '2025-04-27 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-0af4ccbc'); + ('aggregate-36e5ccd2', '2024-03-14 12:03:02+00', 'trunk', '36e5ccd2', '2024-03-13 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-36e5ccd2'), + ('aggregate-d5460f38', '2024-03-27 12:03:02+00', 'trunk', 'd5460f38', '2024-03-25 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-d5460f38'), + ('aggregate-bc9425cb', '2024-04-01 12:03:02+00', 'trunk', 'bc9425cb', '2024-04-02 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-bc9425cb'), + ('aggregate-14df1b11', '2024-04-07 12:03:02+00', 'trunk', '14df1b11', '2024-04-06 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-14df1b11'), + ('aggregate-ac40c0d8', '2024-04-14 12:03:02+00', 'trunk', 'ac40c0d8', '2024-04-13 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-ac40c0d8'), + ('aggregate-0af4ccbc', '2024-04-28 12:03:02+00', 'trunk', '0af4ccbc', '2024-04-27 10:03:02+00', 'ci', 'https://example.com/experiments/aggregate-0af4ccbc'); INSERT INTO results (experiment_id, config_id, process_cumulative_rate_mean, process_cumulative_rate_stderr, process_cumulative_rate_diff) diff --git a/otava/config.py b/otava/config.py index ca1d587..345dede 100644 --- a/otava/config.py +++ b/otava/config.py @@ -20,7 +20,6 @@ from pathlib import Path from typing import Dict, List, Optional import configargparse -from expandvars import expandvars from ruamel.yaml import YAML from otava.bigquery import BigQueryConfig @@ -61,7 +60,7 @@ def load_tests(config: Dict, templates: Dict) -> Dict[str, TestConfig]: raise ConfigError("Property `tests` is not a dictionary") result = {} - for test_name, test_config in tests.items(): + for (test_name, test_config) in tests.items(): template_names = test_config.get("inherit", []) if not isinstance(template_names, List): template_names = [templates] @@ -81,7 +80,7 @@ def load_test_groups(config: Dict, tests: Dict[str, TestConfig]) -> Dict[str, Li raise ConfigError("Property `test_groups` is not a dictionary") result = {} - for group_name, test_names in groups.items(): + for (group_name, test_names) in groups.items(): test_list = [] if not isinstance(test_names, List): raise ConfigError(f"Test group {group_name} must be a list") @@ -96,33 +95,12 @@ def load_test_groups(config: Dict, tests: Dict[str, TestConfig]) -> Dict[str, Li return result -def expand_env_vars_recursive(obj): - """Recursively expand environment variables in all string values within a nested structure. - - Raises ConfigError if any environment variables remain undefined after expansion. - """ - if isinstance(obj, dict): - return {key: expand_env_vars_recursive(value) for key, value in obj.items()} - elif isinstance(obj, list): - return [expand_env_vars_recursive(item) for item in obj] - elif isinstance(obj, str): - return expandvars(obj, nounset=True) - else: - return obj - - def load_config_from_parser_args(args: configargparse.Namespace) -> Config: config_file = getattr(args, "config_file", None) if config_file is not None: yaml = YAML(typ="safe") config = yaml.load(Path(config_file).read_text()) - # Expand environment variables in the entire config after CLI argument replacement - try: - config = expand_env_vars_recursive(config) - except Exception as e: - raise ConfigError(f"Error expanding environment variables: {e}") - templates = load_templates(config) tests = load_tests(config, templates) groups = load_test_groups(config, tests) diff --git a/pyproject.toml b/pyproject.toml index 6bb1409..24e3b36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ dependencies = [ "google-cloud-bigquery>=3.25.0", "pg8000>=1.31.2", "configargparse>=1.7.1", - "expandvars>=0.12.0", ] [project.optional-dependencies] diff --git a/tests/config_test.py b/tests/config_test.py index 4f66b95..5ce500e 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -15,19 +15,11 @@ # specific language governing permissions and limitations # under the License. import os -import tempfile from io import StringIO import pytest -from expandvars import UnboundVariable -from otava.config import ( - NestedYAMLConfigFileParser, - create_config_parser, - expand_env_vars_recursive, - load_config_from_file, - load_config_from_parser_args, -) +from otava.config import NestedYAMLConfigFileParser, load_config_from_file from otava.test_config import CsvTestConfig, GraphiteTestConfig, HistoStatTestConfig @@ -217,225 +209,3 @@ templates: for key in result.keys(): section = key.split('-')[0] assert section not in ignored_sections, f"Found key '{key}' from ignored section '{section}'" - - -def test_expand_env_vars_recursive(): - """Test the expand_env_vars_recursive function with various data types.""" - - # Set up test environment variables - test_env_vars = { - "TEST_HOST": "localhost", - "TEST_PORT": "8080", - "TEST_DB": "testdb", - "TEST_USER": "testuser", - } - - for key, value in test_env_vars.items(): - os.environ[key] = value - - try: - # Test simple string expansion - simple_string = "${TEST_HOST}:${TEST_PORT}" - result = expand_env_vars_recursive(simple_string) - assert result == "localhost:8080" - - # Test dictionary expansion - test_dict = { - "host": "${TEST_HOST}", - "port": "${TEST_PORT}", - "database": "${TEST_DB}", - "connection_string": "postgresql://${TEST_USER}@${TEST_HOST}:${TEST_PORT}/${TEST_DB}", - "timeout": 30, # non-string should remain unchanged - "enabled": True, # non-string should remain unchanged - } - - result_dict = expand_env_vars_recursive(test_dict) - expected_dict = { - "host": "localhost", - "port": "8080", - "database": "testdb", - "connection_string": "postgresql://testuser@localhost:8080/testdb", - "timeout": 30, - "enabled": True, - } - assert result_dict == expected_dict - - # Test list expansion - test_list = [ - "${TEST_HOST}", - {"nested_host": "${TEST_HOST}", "nested_port": "${TEST_PORT}"}, - ["${TEST_USER}", "${TEST_DB}"], - 123, # non-string should remain unchanged - ] - - result_list = expand_env_vars_recursive(test_list) - expected_list = [ - "localhost", - {"nested_host": "localhost", "nested_port": "8080"}, - ["testuser", "testdb"], - 123, - ] - assert result_list == expected_list - - # Test undefined variables (should throw UnboundVariable) - with pytest.raises(UnboundVariable, match="'UNDEFINED_VAR: unbound variable"): - expand_env_vars_recursive("${UNDEFINED_VAR}") - - # Test mixed defined/undefined variables (should throw UnboundVariable) - with pytest.raises(UnboundVariable, match="'UNDEFINED_VAR: unbound variable"): - expand_env_vars_recursive("prefix-${TEST_HOST}-middle-${UNDEFINED_VAR}-suffix") - - finally: - # Clean up environment variables - for key in test_env_vars: - if key in os.environ: - del os.environ[key] - - -def test_env_var_expansion_in_templates_and_tests(): - """Test that environment variable expansion works in template and test sections.""" - - # Set up test environment variables - test_env_vars = { - "CSV_DELIMITER": "$", - "CSV_QUOTE_CHAR": "!", - "CSV_FILENAME": "/tmp/test.csv", - } - - for key, value in test_env_vars.items(): - os.environ[key] = value - - # Create a temporary config file with env var placeholders - config_content = """ -templates: - csv_template_1: - csv_options: - delimiter: "${CSV_DELIMITER}" - - csv_template_2: - csv_options: - quote_char: '${CSV_QUOTE_CHAR}' - -tests: - expansion_test: - type: csv - file: ${CSV_FILENAME} - time_column: timestamp - metrics: - response_time: - column: response_ms - unit: ms - inherit: [csv_template_1, csv_template_2] -""" - - try: - with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: - f.write(config_content) - config_file_path = f.name - - try: - # Load config and verify expansion worked - parser = create_config_parser() - args = parser.parse_args(["--config-file", config_file_path]) - config = load_config_from_parser_args(args) - - # Verify test was loaded - assert "expansion_test" in config.tests - test = config.tests["expansion_test"] - assert isinstance(test, CsvTestConfig) - - # Verify that expansion worked - assert test.file == test_env_vars["CSV_FILENAME"] - - # Verify that inheritance from templates worked with expanded values - assert test.csv_options.delimiter == test_env_vars["CSV_DELIMITER"] - assert test.csv_options.quote_char == test_env_vars["CSV_QUOTE_CHAR"] - - finally: - os.unlink(config_file_path) - - finally: - # Clean up environment variables - for key in test_env_vars: - if key in os.environ: - del os.environ[key] - - -def test_cli_precedence_over_env_vars(): - """Test that CLI arguments take precedence over environment variables.""" - - # Set up environment variables - env_vars = { - "POSTGRES_HOSTNAME": "env-host.com", - "POSTGRES_PORT": "5433", - "POSTGRES_DATABASE": "env_db", - "SLACK_BOT_TOKEN": "env-slack-token", - } - - for key, value in env_vars.items(): - os.environ[key] = value - - # Create a simple config file - config_content = """ -postgres: - hostname: config_host - port: 5432 - database: config_db - username: config_user - password: config_pass - -slack: - token: config_slack_token -""" - - try: - with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: - f.write(config_content) - config_file_path = f.name - - try: - # Test 1: Only environment variables (no CLI overrides) - config_env_only = load_config_from_file(config_file_path) - - # Environment variables should override config file values - assert config_env_only.postgres.hostname == "env-host.com" - assert config_env_only.postgres.port == 5433 - assert config_env_only.postgres.database == "env_db" - assert config_env_only.slack.bot_token == "env-slack-token" - - # Values without env vars should use config file values - assert config_env_only.postgres.username == "config_user" - assert config_env_only.postgres.password == "config_pass" - - # Test 2: CLI arguments should override environment variables - cli_overrides = [ - "--postgres-hostname", - "cli-host.com", - "--postgres-port", - "5434", - "--slack-token", - "cli-slack-token", - ] - - config_cli_override = load_config_from_file( - config_file_path, arg_overrides=cli_overrides - ) - - # CLI overrides should win - assert config_cli_override.postgres.hostname == "cli-host.com" - assert config_cli_override.postgres.port == 5434 - assert config_cli_override.slack.bot_token == "cli-slack-token" - - # Values without CLI override should still use env vars - assert config_cli_override.postgres.database == "env_db" - assert config_cli_override.postgres.username == "config_user" - assert config_cli_override.postgres.password == "config_pass" - - finally: - os.unlink(config_file_path) - - finally: - # Clean up environment variables - for key in env_vars: - if key in os.environ: - del os.environ[key] diff --git a/uv.lock b/uv.lock index f2b2833..a5beddf 100644 --- a/uv.lock +++ b/uv.lock @@ -10,12 +10,11 @@ resolution-markers = [ [[package]] name = "apache-otava" -version = "0.7.0" +version = "0.6.1" source = { editable = "." } dependencies = [ { name = "configargparse" }, { name = "dateparser" }, - { name = "expandvars" }, { name = "google-cloud-bigquery", version = "3.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, { name = "google-cloud-bigquery", version = "3.35.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "numpy" }, @@ -56,7 +55,6 @@ requires-dist = [ { name = "autoflake", marker = "extra == 'dev'", specifier = ">=1.4" }, { name = "configargparse", specifier = ">=1.7.1" }, { name = "dateparser", specifier = ">=1.0.0" }, - { name = "expandvars", specifier = ">=0.12.0" }, { name = "flake8", marker = "extra == 'dev'", specifier = ">=4.0.1" }, { name = "google-cloud-bigquery", specifier = ">=3.25.0" }, { name = "isort", marker = "extra == 'dev'", specifier = ">=5.10.1" }, @@ -263,15 +261,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] -[[package]] -name = "expandvars" -version = "1.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/64/a9d8ea289d663a44b346203a24bf798507463db1e76679eaa72ee6de1c7a/expandvars-1.1.2.tar.gz", hash = "sha256:6c5822b7b756a99a356b915dd1267f52ab8a4efaa135963bd7f4bd5d368f71d7", size = 70842, upload-time = "2025-09-12T10:55:20.929Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/e6/79c43f7a55264e479a9fbf21ddba6a73530b3ea8439a8bb7fa5a281721af/expandvars-1.1.2-py3-none-any.whl", hash = "sha256:d1652fe4e61914f5b88ada93aaedb396446f55ae4621de45c8cb9f66e5712526", size = 7526, upload-time = "2025-09-12T10:55:18.779Z" }, -] - [[package]] name = "filelock" version = "3.16.1"
