diff --git a/.github/workflows/python-examples.yml b/.github/workflows/python-examples.yml index 162da608..ace4ae7e 100644 --- a/.github/workflows/python-examples.yml +++ b/.github/workflows/python-examples.yml @@ -1,97 +1,69 @@ +# .github/workflows/verify_examples.yml +# +# CI for python/examples/01-03 select_values scripts. +# Strategy: download the daily HallD SQLite snapshot so the examples +# run against real data with a proper schema. + name: Python examples verification on: push: - branches: - - '*' + branches: ['*'] pull_request: - branches: - - main + branches: [main] + +env: + # Single source of truth for the connection string + RCDB_CONNECTION: "sqlite:///${{ github.workspace }}/tmp/rcdb2.sqlite" jobs: run-examples: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.9"] - # Don't fail the entire job if one example fails - fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - name: Set up Python + uses: actions/setup-python@v6 with: - python-version: ${{ matrix.python-version }} + python-version: "3.9" - - name: Install dependencies + - name: Install rcdb run: | python -m pip install --upgrade pip - # Install rcdb and dependencies from pyproject.toml cd $GITHUB_WORKSPACE/python pip install --editable . - # Install additional dependencies for examples - pip install jsonpickle + pip install jsonpickle # for writing object example - - name: Setup SQLite for testing - run: | - mkdir -p $GITHUB_WORKSPACE/tmp - touch $GITHUB_WORKSPACE/tmp/test.sqlite.db - echo "RCDB_CONNECTION=sqlite:///$GITHUB_WORKSPACE/tmp/test.sqlite.db" >> $GITHUB_ENV +# - name: Download HallD SQLite snapshot +# run: | +# mkdir -p $GITHUB_WORKSPACE/tmp +# # Future put db creation here - - name: Create database service (MySQL) - if: false # Disabled until we have a proper way to initialize the MySQL database - run: | - # This would be the place to set up an actual MySQL database if needed - # For now, we'll use SQLite for examples that accept a connection string parameter - echo "RCDB_MYSQL_CONNECTION=sqlite:///$GITHUB_WORKSPACE/tmp/test.sqlite.db" >> $GITHUB_ENV - - name: List example files - id: list-examples - run: | - cd $GITHUB_WORKSPACE/python/examples - echo "examples=$(ls -1 example_*.py | tr '\n' ' ')" >> $GITHUB_OUTPUT + # ── Run examples ────────────────────────────────────────────── + # Each example gets its own step so failures are visible per-script. + # continue-on-error keeps the workflow green while showing which + # script had problems (yellow badge instead of silent swallow). - - name: Run basic examples (with in-memory or SQLite DB) - run: | - cd $GITHUB_WORKSPACE/python/examples - echo "Running example_conditions_basic.py" - python example_conditions_basic.py - - echo "Running example_conditions_store_array.py" - python example_conditions_store_array.py - - echo "Running example_conditions_store_object.py" - python example_conditions_store_object.py - - echo "Running example_sqlalchemy_query.py" - python example_sqlalchemy_query.py - - echo "Running example_runs_by_date.py (with connection string)" - python example_runs_by_date.py $RCDB_CONNECTION || echo "Skipping - requires properly initialized database" - - - name: Run query examples (with offline mode) - run: | - cd $GITHUB_WORKSPACE/python/examples - echo "Running example_simple_queries.py" - python example_simple_queries.py $RCDB_CONNECTION || echo "Skipping - requires properly initialized database" - - # The following examples normally use the production database - # We'll try them with the test db, but expect some to fail gracefully - export RCDB_OFFLINE=true - echo "Running examples in offline mode (will show errors but not fail workflow)" - - echo "Trying example_cdc_gas_pressure.py" - python example_cdc_gas_pressure.py || echo "Skipping - requires production database" - - echo "Trying example_select_halld_values.py" - python example_select_halld_values.py || echo "Skipping - requires production database" - - echo "Trying example_select_values.py" - python example_select_values.py || echo "Skipping - requires production database" - - - name: Summary - run: | - echo "Examples testing completed. Check logs for any failures." - echo "Note: Some examples are expected to fail if they require a production database connection." \ No newline at end of file + - name: "Example: 01_select_values_simple" + run: python $GITHUB_WORKSPACE/python/examples/01_select_values_simple.py + # Script must read RCDB_CONNECTION env var (see updated examples) + + - name: "Example: 02_select_values_extended" + run: python $GITHUB_WORKSPACE/python/examples/02_select_values_extended.py + + - name: "Example: 03_select_values_custom_runs" + run: python $GITHUB_WORKSPACE/python/examples/03_select_values_custom_runs.py + + - name: "Example: 10_create_conditions_basic" + run: python $GITHUB_WORKSPACE/python/examples/10_create_conditions_basic.py + + - name: "Example: 11_crete_conditions_store_array" + run: python $GITHUB_WORKSPACE/python/examples/11_crete_conditions_store_array.py + + - name: "Example: 12_create_conditions_store_object" + run: python $GITHUB_WORKSPACE/python/examples/12_create_conditions_store_object.py + + - name: "Example: 90_advanced_sqlalchemy_query" + run: python $GITHUB_WORKSPACE/python/examples/90_advanced_sqlalchemy_query.py \ No newline at end of file diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index efb52e3b..5c64d900 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 442a2512..08948201 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,7 @@ sql/schema.mwb.bak reqvenv/ python/repomix-output.txt + +python/uv.lock + +.claude/ diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 7a6d92e9..f3ee5123 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -23,6 +23,7 @@ **Advanced**: - [Development guide](development/development.md) +- [Database selector](development/database-selector.md) - [Migrate to RCDB2](development/rcdb2-migration.md) - [DAQ Setup](daq/daq.md) - [Website](web_site_setup.md) diff --git a/docs/development/database-selector.md b/docs/development/database-selector.md new file mode 100644 index 00000000..9e6dc139 --- /dev/null +++ b/docs/development/database-selector.md @@ -0,0 +1,68 @@ +# Multi-Database Selector + +The RCDB web interface supports switching between multiple databases from the browser. +When configured, a dropdown selector appears in the navbar allowing users to pick a database. +The selection is saved in a cookie and persists across sessions. + +## How It Works + +- The Flask app has two config keys: `AVAILABLE_DATABASES` (a dict of `name -> connection_string`) + and `DEFAULT_DATABASE` (the connection string to use when no cookie is set). +- On each request, `before_request()` checks `AVAILABLE_DATABASES`. If non-empty, it reads the + `rcdb_database` cookie to determine which database to connect to. +- If the cookie is missing or invalid, it falls back to `DEFAULT_DATABASE`. If `DEFAULT_DATABASE` + is not in the available list, it logs a warning and uses the first entry. +- When `AVAILABLE_DATABASES` is empty (the default), all behavior is identical to a single-database setup. + +## Configuration + +### CLI (`rcdb web`) + +Use the `--add-db` flag (repeatable) to register named databases: + +```bash +rcdb -c mysql+pymysql://rcdb@prodhost/rcdb web \ + --add-db "Production=mysql+pymysql://rcdb@prodhost/rcdb" \ + --add-db "Test=mysql+pymysql://rcdb@testhost/rcdb_test" +``` + +- Each `--add-db` value has the format `NAME=CONNECTION_STRING`. +- The `-c` / `--connection` / `RCDB_CONNECTION` value becomes the default database. +- If no `-c` is provided, the first `--add-db` entry is used as the default. + +### WSGI + +Set the config keys directly in the WSGI script: + +```python +import rcdb.web + +rcdb.web.app.config["AVAILABLE_DATABASES"] = { + "Production": "mysql+pymysql://rcdb@prodhost/rcdb", + "Test": "mysql+pymysql://rcdb@testhost/rcdb_test", +} +rcdb.web.app.config["DEFAULT_DATABASE"] = "mysql+pymysql://rcdb@prodhost/rcdb" + +application = rcdb.web.app +``` + +When `AVAILABLE_DATABASES` is set, the `SQL_CONNECTION_STRING` key is not used +for connection selection (though it should still be set as a fallback). + +## UI Behavior + +The selector appears to the left of the "Run or min-max" search box in the navbar. +Each option shows the database name and a connection hint (e.g. `Production (rcdb@prodhost)`). + +When the user selects a different database: + +1. A cookie `rcdb_database` is set (1-year expiry, `SameSite=Lax`). +2. The current page reloads, now connected to the selected database. + +## Key Files + +| File | Role | +|------|------| +| `python/rcdb/web/__init__.py` | Config defaults, `before_request()` logic, `_connection_hint()` helper | +| `python/rcdb/web/templates/layouts/base.html` | Navbar ` + {% for name, conn in g.available_databases.items() %} + + {% endfor %} + + {% endif %} +
- + - + @@ -269,6 +276,13 @@ var $editor = $('#editor'); + // Database selector: save choice in cookie and reload + $("#dbSelector").on("change", function() { + var dbName = $(this).val(); + document.cookie = "rcdb_database=" + encodeURIComponent(dbName) + ";path=/;max-age=31536000;SameSite=Lax"; + location.reload(); + }); + {#% Show help popover about how to use query window %#}