Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/ci-auto-fix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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"
# 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

- name: Create PR on other failures (diagnostic)
if: steps.git-check.outputs.changed == 'false'
run: |
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
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"
# 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
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.'
})
2 changes: 1 addition & 1 deletion app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 3 additions & 10 deletions app/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 4 additions & 3 deletions app/pages/ecopath.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
Expand Down
28 changes: 14 additions & 14 deletions app/pages/ecospace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand Down Expand Up @@ -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
)
Expand All @@ -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
)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions src/pypath/analysis/prebalance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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']),
Expand Down Expand Up @@ -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]),
Expand Down
Loading