From 86cbfd55cdd06662d09338866008da66769832d6 Mon Sep 17 00:00:00 2001 From: razinkele Date: Sun, 4 Jan 2026 21:21:40 +0200 Subject: [PATCH 1/6] chore(ci): add workflow to auto-fix formatting and open diagnostic PRs for CI failures --- .github/workflows/ci-auto-fix.yml | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/ci-auto-fix.yml diff --git a/.github/workflows/ci-auto-fix.yml b/.github/workflows/ci-auto-fix.yml new file mode 100644 index 0000000..88e7059 --- /dev/null +++ b/.github/workflows/ci-auto-fix.yml @@ -0,0 +1,79 @@ +name: CI Auto-Fix + +on: + workflow_run: + workflows: ["CI"] + types: [completed] + +jobs: + auto_fix: + if: github.event.workflow_run.conclusion == 'failure' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + + - name: Install dev deps + run: python -m pip install -e '.[dev]' + + - name: Run ruff and black auto-fixes + run: | + set -e + ruff format --quiet --fix . || true + black --quiet . || true + + - name: Check for formatting changes + id: git-check + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + if [ -n "$(git status --porcelain)" ]; then echo "changed=true" >> $GITHUB_OUTPUT; else echo "changed=false" >> $GITHUB_OUTPUT; fi + + - name: Create branch and PR for formatting fixes + if: steps.git-check.outputs.changed == 'true' + env: + BRANCH_NAME: auto/ci-fix/format-${{ github.run_id }} + run: | + git checkout -b "$BRANCH_NAME" + git add -A + git commit -m "style: apply ruff/black fixes from CI auto-fix" + git push origin "$BRANCH_NAME" + gh pr create --title "style: apply ruff/black fixes (auto)" --body "Automated formatting fixes (ruff/black) applied by CI Auto-Fix workflow. This PR will be auto-merged when checks pass." --base main --head "$BRANCH_NAME" --label "chore" --web || true + + - name: Run tests to check if fixes pass + run: python -m pytest -q || true + + - name: Create draft PR on other failures + if: steps.git-check.outputs.changed == 'false' + run: | + echo "Opening draft PR for CI failure diagnostics" + BRANCH=auto/ci-fix/failure-${{ github.run_id }} + git checkout -b "$BRANCH" + # Add a short diagnostic file for human triage + echo "CI failure triggered by workflow_run id: ${{ github.event.workflow_run.id }}" > CI_FAILURE.md + git add CI_FAILURE.md + git commit -m "ci: record CI failure for diagnostics" + git push origin "$BRANCH" + gh pr create --title "ci: investigate failing CI run #${{ github.event.workflow_run.id }}" --body "Draft PR created automatically to investigate CI failure (workflow_run id: ${{ github.event.workflow_run.id }}). Please see workflow run logs for details." --base main --head "$BRANCH" --draft --label "ci-fail" --web || true + + - name: Comment on original run + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const runId = context.payload.workflow_run.id + const owner = context.repo.owner + const repo = context.repo.repo + await github.rest.actions.createWorkflowRunComment({ + owner, repo, run_id: runId, body: 'CI Auto-Fix ran: formatting fixes applied or diagnostic PR created.' + }) From a74c3e60597d7adedb5ab37b64b6ddba54c47232 Mon Sep 17 00:00:00 2001 From: razinkele Date: Sun, 4 Jan 2026 21:23:30 +0200 Subject: [PATCH 2/6] ci: enable auto-merge for formatting-fix PRs created by CI --- .github/workflows/ci-auto-fix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-auto-fix.yml b/.github/workflows/ci-auto-fix.yml index 88e7059..2b95ed8 100644 --- a/.github/workflows/ci-auto-fix.yml +++ b/.github/workflows/ci-auto-fix.yml @@ -48,7 +48,8 @@ jobs: git add -A git commit -m "style: apply ruff/black fixes from CI auto-fix" git push origin "$BRANCH_NAME" - gh pr create --title "style: apply ruff/black fixes (auto)" --body "Automated formatting fixes (ruff/black) applied by CI Auto-Fix workflow. This PR will be auto-merged when checks pass." --base main --head "$BRANCH_NAME" --label "chore" --web || true + # Create PR and enable auto-merge so formatting fixes are merged automatically when checks pass + gh pr create --title "style: apply ruff/black fixes (auto)" --body "Automated formatting fixes (ruff/black) applied by CI Auto-Fix workflow. This PR will be auto-merged when checks pass." --base main --head "$BRANCH_NAME" --auto || true - name: Run tests to check if fixes pass run: python -m pytest -q || true From 3f819ec71b0393e2516050ef0fdb9b3d35a5c8d5 Mon Sep 17 00:00:00 2001 From: razinkele Date: Sun, 4 Jan 2026 21:24:35 +0200 Subject: [PATCH 3/6] ci: add auto-fix label when creating formatting PRs --- .github/workflows/ci-auto-fix.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-auto-fix.yml b/.github/workflows/ci-auto-fix.yml index 2b95ed8..949c3a3 100644 --- a/.github/workflows/ci-auto-fix.yml +++ b/.github/workflows/ci-auto-fix.yml @@ -48,8 +48,9 @@ jobs: git add -A git commit -m "style: apply ruff/black fixes from CI auto-fix" git push origin "$BRANCH_NAME" - # Create PR and enable auto-merge so formatting fixes are merged automatically when checks pass - gh pr create --title "style: apply ruff/black fixes (auto)" --body "Automated formatting fixes (ruff/black) applied by CI Auto-Fix workflow. This PR will be auto-merged when checks pass." --base main --head "$BRANCH_NAME" --auto || true + # Ensure auto-fix label exists then create PR and enable auto-merge so formatting fixes are merged automatically when checks pass + gh label create "auto-fix" --color ffcc00 --description "Automated formatting fixes from CI" || true + gh pr create --title "style: apply ruff/black fixes (auto)" --body "Automated formatting fixes (ruff/black) applied by CI Auto-Fix workflow. This PR will be auto-merged when checks pass." --base main --head "$BRANCH_NAME" --label "auto-fix" --auto || true - name: Run tests to check if fixes pass run: python -m pytest -q || true From b910870a55bb45e156f9c9ea982a3f795ebd2df3 Mon Sep 17 00:00:00 2001 From: razinkele Date: Sun, 4 Jan 2026 21:25:48 +0200 Subject: [PATCH 4/6] ci: create non-draft diagnostic PRs and ensure 'ci-fail' label --- .github/workflows/ci-auto-fix.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-auto-fix.yml b/.github/workflows/ci-auto-fix.yml index 949c3a3..6315ac6 100644 --- a/.github/workflows/ci-auto-fix.yml +++ b/.github/workflows/ci-auto-fix.yml @@ -55,10 +55,10 @@ jobs: - name: Run tests to check if fixes pass run: python -m pytest -q || true - - name: Create draft PR on other failures + - name: Create PR on other failures (diagnostic) if: steps.git-check.outputs.changed == 'false' run: | - echo "Opening draft PR for CI failure diagnostics" + echo "Opening PR for CI failure diagnostics" BRANCH=auto/ci-fix/failure-${{ github.run_id }} git checkout -b "$BRANCH" # Add a short diagnostic file for human triage @@ -66,7 +66,9 @@ jobs: git add CI_FAILURE.md git commit -m "ci: record CI failure for diagnostics" git push origin "$BRANCH" - gh pr create --title "ci: investigate failing CI run #${{ github.event.workflow_run.id }}" --body "Draft PR created automatically to investigate CI failure (workflow_run id: ${{ github.event.workflow_run.id }}). Please see workflow run logs for details." --base main --head "$BRANCH" --draft --label "ci-fail" --web || true + # Ensure ci-fail label exists and create a non-draft PR for triage + gh label create "ci-fail" --color ee0000 --description "CI failure requires human triage" || true + gh pr create --title "ci: investigate failing CI run #${{ github.event.workflow_run.id }}" --body "PR created automatically to investigate CI failure (workflow_run id: ${{ github.event.workflow_run.id }}). Please see workflow run logs for details." --base main --head "$BRANCH" --label "ci-fail" --web || true - name: Comment on original run uses: actions/github-script@v7 From a1b8e3a08d152ff1626224cc7bc11f2414f659ce Mon Sep 17 00:00:00 2001 From: razinkele Date: Sun, 4 Jan 2026 21:47:38 +0200 Subject: [PATCH 5/6] chore: apply CodeRabbit quick fixes (unused vars, optimize col lookup, unused lambda args) --- app/pages/ecopath.py | 7 ++++--- app/pages/ecospace.py | 28 ++++++++++++++-------------- src/pypath/analysis/prebalance.py | 6 +++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/pages/ecopath.py b/app/pages/ecopath.py index bbb568b..0ba3a8c 100644 --- a/app/pages/ecopath.py +++ b/app/pages/ecopath.py @@ -732,7 +732,7 @@ def _handle_model_params_edit(): duration=5 ) - except (ValueError, TypeError) as e: + except (ValueError, TypeError): ui.notification_show( f"Invalid numeric value for {col_name}: '{new_value}'", type="error", @@ -785,7 +785,7 @@ def _handle_diet_matrix_edit(): duration=2 ) - except (ValueError, TypeError) as e: + except (ValueError, TypeError): ui.notification_show( f"Invalid numeric value for diet: '{new_value}'", type="error", @@ -952,9 +952,10 @@ def model_results_table(): # Add special styling for calculated columns (EE, GE, TL) calculated_cols = ['EE', 'GE', 'TL'] + col_positions = {c: i for i, c in enumerate(formatted_df.columns)} for col in calculated_cols: if col in formatted_df.columns: - col_idx = list(formatted_df.columns).index(col) + col_idx = col_positions[col] for row_idx in range(len(formatted_df)): is_no_data = bool(no_data_mask.iloc[row_idx][col]) if col in no_data_mask.columns else False is_stanza = bool(stanza_mask.iloc[row_idx][col]) if (stanza_mask is not None and col in stanza_mask.columns) else False diff --git a/app/pages/ecospace.py b/app/pages/ecospace.py index 0962610..594f29a 100644 --- a/app/pages/ecospace.py +++ b/app/pages/ecospace.py @@ -234,7 +234,7 @@ def create_hexagon(center_x: float, center_y: float, radius: float) -> "Polygon" angles = np.linspace(0, 2 * np.pi, 7) + np.pi / 6 # Rotate by 30 degrees x_coords = center_x + radius * np.cos(angles) y_coords = center_y + radius * np.sin(angles) - return Polygon(zip(x_coords, y_coords)) + return Polygon(zip(x_coords, y_coords, strict=True)) def ecospace_ui(): @@ -578,14 +578,14 @@ def ecospace_ui(): ) -def ecospace_server(input: Inputs, output: Outputs, session: Session, model_data: reactive.Value, sim_results: reactive.Value): +def ecospace_server(input: Inputs, _output: Outputs, _session: Session, _model_data: reactive.Value, _sim_results: reactive.Value): """Server logic for ECOSPACE page.""" # Reactive values for spatial state grid = reactive.Value(None) boundary_polygon = reactive.Value(None) # Store uploaded boundary for visualization - ecospace_params = reactive.Value(None) - spatial_results = reactive.Value(None) + ecospace_params = reactive.Value(None) # TODO: reserved for future use # noqa: F841 + spatial_results = reactive.Value(None) # TODO: reserved for future use # noqa: F841 # Load and display boundary polygon immediately on file upload @reactive.effect @@ -655,9 +655,9 @@ def load_boundary_on_upload(): # Clean up temporary directory shutil.rmtree(temp_dir, ignore_errors=True) - except Exception as e: + except (ValueError, IOError, OSError, zipfile.BadZipFile) as e: ui.notification_show( - f"Error loading boundary: {str(e)}", + f"Error loading boundary: {e!s}", type="warning", duration=5 ) @@ -838,9 +838,9 @@ def create_spatial_grid(): # Clean up temporary directory shutil.rmtree(temp_dir, ignore_errors=True) - except Exception as e: + except (ValueError, IOError, OSError, zipfile.BadZipFile) as e: ui.notification_show( - f"Error processing spatial file: {str(e)}", + f"Error processing spatial file: {e!s}", type="error", duration=6 ) @@ -849,9 +849,9 @@ def create_spatial_grid(): # Enable run button ui.update_action_button("run_spatial_sim", disabled=False) - except Exception as e: + except (ValueError, OSError) as e: ui.notification_show( - f"Error creating grid: {str(e)}", + f"Error creating grid: {e!s}", type="error", duration=5 ) @@ -929,7 +929,7 @@ def grid_plot(): folium.GeoJson( boundary_geojson, name='Boundary', - style_function=lambda x: { + style_function=lambda _x: { 'fillColor': 'red', 'color': 'red', 'weight': 2.5, @@ -971,7 +971,7 @@ def grid_plot(): folium.GeoJson( geojson_data, name='Grid Patches', - style_function=lambda x: { + style_function=lambda _x: { 'fillColor': 'lightblue', 'color': 'steelblue', 'weight': 0.5, # Thinner lines for large grids @@ -1003,7 +1003,7 @@ def grid_plot(): # Add polygon to map folium.GeoJson( geojson_data, - style_function=lambda x: { + style_function=lambda _x: { 'fillColor': 'lightblue', 'color': 'steelblue', 'weight': 1.5, @@ -1284,7 +1284,7 @@ def habitat_plot(): x, y = geom.exterior.xy color = cmap(norm(habitat[idx])) polygon = MplPolygon( - list(zip(x, y)), + list(zip(x, y, strict=True)), facecolor=color, edgecolor='darkgreen', linewidth=1.2, diff --git a/src/pypath/analysis/prebalance.py b/src/pypath/analysis/prebalance.py index 9034bee..63c72ba 100644 --- a/src/pypath/analysis/prebalance.py +++ b/src/pypath/analysis/prebalance.py @@ -46,7 +46,7 @@ def _calculate_trophic_levels(model: RpathParams) -> pd.Series: # Iteratively calculate TL for consumers # TL = 1 + weighted average of prey TLs max_iterations = 50 - for iteration in range(max_iterations): + for _iteration in range(max_iterations): tl_old = tl.copy() for i, group in enumerate(groups): @@ -313,7 +313,7 @@ def plot_biomass_vs_trophic_level( # Add group labels (sample if too many) if len(df) <= 30: - for idx, row in df.iterrows(): + for _idx, row in df.iterrows(): ax.annotate( row['Group'], (row['TL'], row['Biomass']), @@ -379,7 +379,7 @@ def plot_vital_rate_vs_trophic_level( # Add labels for interesting points if len(df) <= 20: - for idx, row in df.iterrows(): + for _idx, row in df.iterrows(): ax.annotate( row['Group'], (row['TL'], row[rate_name]), From 6e792fbb3b51fb76c42057a868c581f321f3b4a6 Mon Sep 17 00:00:00 2001 From: razinkele Date: Sun, 4 Jan 2026 21:48:32 +0200 Subject: [PATCH 6/6] chore: follow-up CodeRabbit suggestions (replace mutable set with frozenset; narrow except to OSError and log) --- app/config.py | 2 +- app/logger.py | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/app/config.py b/app/config.py index 69a3b1c..78c666c 100644 --- a/app/config.py +++ b/app/config.py @@ -155,7 +155,7 @@ class ValidationConfig: def __post_init__(self): """Initialize validation sets.""" if self.valid_group_types is None: - self.valid_group_types = {0, 1, 2, 3} + self.valid_group_types = frozenset({0, 1, 2, 3}) @dataclass diff --git a/app/logger.py b/app/logger.py index bd6a234..b8a6706 100644 --- a/app/logger.py +++ b/app/logger.py @@ -31,16 +31,9 @@ file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(formatter) logger.addHandler(file_handler) - except Exception: - # If can't create logs directory, just use console - pass - - -def get_logger(name: str = None): - """Get a logger instance. - - Parameters - ---------- + except OSError as e: + # If can't create logs directory, log a warning and continue using console + logger.warning(f"Could not create log directory '{log_dir}': {e!s}") name : str, optional Logger name (typically __name__). If None, returns root app logger.