Add Ruff linting and format the codebase#83
Conversation
- Add a CI lint job for `ruff check` and `ruff format --check` - Configure Ruff in `pyproject.toml` - Reformat Python sources to match the new style
|
Ready to act? Review this PR in Change Stack to turn feedback into patch suggestions you can inspect and refine. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds Ruff lint job and Ruff config; applies repository-wide formatting and refactors across GUI, exporter, core, examples, pyTFM, solver, and tests. Small functional edits appear in examples (params), multigrid tessellation mode, and exporter colorbar/scalebar behavior. ChangesRepository-wide linting, formatting, and exporter/UI refactors
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
saenopy/gui/solver/modules/exporter/FiberViewer.py (2)
24-38:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftGlobal cache has no eviction and can grow indefinitely.
cacheskeeps entries forever and is keyed byid(data). This can leak memory over long sessions and risks stale cache reuse if an object ID is recycled.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/solver/modules/exporter/FiberViewer.py` around lines 24 - 38, The global caches dict keyed by id(data) in get_cache (and the Cache placeholder class) can grow unbounded and risk stale reuse on id reuse; replace the manual id-based dict with a weak-reference mapping (e.g., use weakref.WeakKeyDictionary or WeakValueDictionary) keyed by the ndarray object itself so entries are removed automatically when the array is garbage-collected, update get_cache to look up and store cache objects in that weak map instead of using id(data), and ensure any creation logic still returns a Cache instance when needed (symbols: caches, Cache, get_cache, id(data)).
155-177:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix invalid default for
process_stackintensity range.
scale_intensity()doestuple(range); with the currentrange=rangedefault, omittingrangepasses the built-inrangetype, which will raise at runtime. Set a 2-number default instead (e.g.,(0.0, 1.0)).🐛 Proposed fix
def process_stack( stack, channel, @@ - range=range, + range=(0.0, 1.0), @@ ):🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/solver/modules/exporter/FiberViewer.py` around lines 155 - 177, The default argument for process_stack is wrong: change the parameter named range (used by scale_intensity) from the built-in type to a numeric 2-tuple (e.g., range=(0.0, 1.0)) so that scale_intensity(tuple(range)) receives a valid pair; update the function signature in process_stack and any tests or callers relying on the old default if needed, and ensure references to scale_intensity, process_stack, and the range parameter are consistent.
🧹 Nitpick comments (7)
pyproject.toml (1)
84-84: ⚖️ Poor tradeoffConsider incrementally removing
E722(bareexcept:) from the Ruff ignore listFile: pyproject.toml
Lines: 84-84Snippet showing the final state of code at these lines:
"E722",Ruff is configured to ignore E722, but
ruff check --select E722 --statistics .reports 4 bareexcept:instances insaenopy/gui/common/(QExtendedGraphicsView.py:324,QtShortCuts.py:1423,QtShortCuts.py:1458,QtShortCuts.py:1561). Bareexcept:catchesSystemExitandKeyboardInterrupt, which can hinder debugging and graceful shutdown—replace with targeted exceptions (e.g.,except Exception:or more specific types) as you remove the ignore incrementally (note: there are additional bareexcept:inunused/, which is excluded by config).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pyproject.toml` at line 84, Ruff is currently ignoring E722 (bare except:) which masks four occurrences in saenopy/gui/common — replace each bare except: in QExtendedGraphicsView.py (around line 324) and QtShortCuts.py (around lines 1423, 1458, 1561) with a more specific handler (preferably except Exception: or an even narrower exception type relevant to the try block), run unit/interactive tests and ruff (ruff check --select E722 --statistics .) to confirm no remaining instances, and once all occurrences are fixed remove "E722" from the ignore list in pyproject.toml so the linter enforces it going forward.saenopy/gui/common/sigmoid_widget.py (1)
214-218: ⚡ Quick winRemove unreachable branch in
get_range().Line 215 returns immediately, so Lines 216-218 are dead and the effective API is always normalized
[minx, maxx]. Please delete the unreachable return (or invert the logic if mapped values are intended).♻️ Proposed cleanup
def get_range(self): return self.minx, self.maxx - return self.original_min + (self.original_max - self.original_min) * self.minx, self.original_min + ( - self.original_max - self.original_min - ) * self.maxx🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/common/sigmoid_widget.py` around lines 214 - 218, The get_range() function currently has an early return that makes the mapped-value branch unreachable; remove the immediate "return self.minx, self.maxx" and keep the computed mapped return using original_min/original_max so get_range() returns (original_min + (original_max - original_min)*minx, original_min + (original_max - original_min)*maxx); ensure you update any callers if the API was intended to stay normalized instead (alternatively invert this change if normalized values are the desired API).saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py (1)
31-31: ⚡ Quick winRemove debug print statement.
The
print(iter)statement appears to be leftover debug code and should be removed or replaced with proper logging.🧹 Suggested fix
- print(iter)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py` at line 31, Remove the leftover debug print call print(iter) in mask_interpolation.py: delete the print(iter) statement (or replace it with a suitable logger call using the project's logging facility if runtime info is required) so there are no stray prints; locate the occurrence by searching for the literal print(iter) or references to the iter variable in the mask interpolation function and update accordingly.saenopy/gui/tmp/render_results2.py (1)
7-95: ⚡ Quick winRemove or rename the first
paramsblock to avoid dead configuration.Line 96 fully overwrites Line 7’s dictionary, so the first block is unreachable configuration and makes active settings ambiguous.
Also applies to: 96-185
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/tmp/render_results2.py` around lines 7 - 95, The file defines the dictionary named params twice (the initial params block and a later params that overwrites it), leaving the first block dead; remove or rename the first params block (or merge its intended keys into the later params dictionary) so there is a single authoritative params variable used by the rest of render_results2.py, and if you rename it update all references to params accordingly (search for the symbol params to locate usages).saenopy/gui/tfm2d/modules/draw.py (1)
120-121: ⚡ Quick winRemove redundant import inside method.
The
import numpy as npat line 120 is redundant since numpy is already imported at module level (line 1). Method-level imports are non-idiomatic and waste time on each method call.🧹 Proposed fix
draw.ellipse((x1 - size // 2, y1 - size // 2, x1 + size // 2, y1 + size // 2), fill=color) - import numpy as np - im = np.asarray(self.full_image)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/tfm2d/modules/draw.py` around lines 120 - 121, Remove the redundant method-level "import numpy as np" statement in saenopy/gui/tfm2d/modules/draw.py; instead rely on the existing module-level numpy import (np) already present at the top of the file, so delete the in-method import line and ensure all uses inside that function reference the module-level "np" identifier.saenopy/gui/orientation/modules/result.py (2)
63-91: 💤 Low valueUnusual TypedDict definition pattern.
Same issue as with
GlobalResultabove - the empty class definition is immediately shadowed by the functionalTypedDictreassignment.♻️ Suggested refactor to standard TypedDict pattern
Apply the same refactor as suggested for
GlobalResultto use either purely class-based or purely functional syntax.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/orientation/modules/result.py` around lines 63 - 91, The DistanceResult TypedDict is currently defined with an empty class then immediately reassigned via the TypedDict(...) functional call; remove the redundant empty class definition and choose a single consistent pattern (either convert the functional call into a class-based TypedDict class named DistanceResult with the listed keys as annotations, or keep the functional TypedDict(...) and delete the empty "class DistanceResult(TypedDict): pass"); update references if needed to the symbol DistanceResult so code uses the chosen definition; mirror the same refactor you applied for GlobalResult to maintain consistency.
44-60: 💤 Low valueUnusual TypedDict definition pattern.
The code defines an empty
GlobalResultclass, then immediately reassigns it with aTypedDictconstructor. This pattern is functionally correct but unconventional:class GlobalResult(TypedDict): pass GlobalResult = TypedDict("GlobalResult", {...})The more idiomatic approach would be either:
- Use the class-based syntax with annotations directly
- Use only the functional
TypedDict(...)syntax without the empty classThis pattern may have been generated by auto-formatting and could confuse readers.
♻️ Suggested refactor to standard TypedDict pattern
Option 1 (class-based, preferred for complex types):
-class GlobalResult(TypedDict): - pass - - -GlobalResult = TypedDict( - "GlobalResult", - { +class GlobalResult(TypedDict): "Mean Coherency": float, "Mean Coherency (weighted by intensity)": float, ... - }, -) + }Option 2 (functional, if dynamic construction is needed):
-class GlobalResult(TypedDict): - pass - - GlobalResult = TypedDict( "GlobalResult", { ... }, )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@saenopy/gui/orientation/modules/result.py` around lines 44 - 60, The current pattern creates an empty class GlobalResult(TypedDict) and then reassigns GlobalResult via TypedDict(...); remove the redundant empty class and use a single, idiomatic definition for the TypedDict: either replace both lines with a class-based TypedDict named GlobalResult containing annotated keys ("Mean Coherency", "Mean Angle", etc.) mapped to float, or keep only the functional assignment GlobalResult = TypedDict("GlobalResult", {...}); ensure the final symbol GlobalResult refers to a single TypedDict definition and adjust imports if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/test.yml:
- Line 16: Add credential persistence hardening to the checkout step: update the
GitHub Actions checkout step (uses: actions/checkout@v4) to set
persist-credentials: false so the GITHUB_TOKEN is not kept available to
subsequent steps; ensure the lint job does not rely on persisted credentials
after updating the checkout configuration.
- Around line 16-17: The workflow currently references actions by mutable tags
actions/checkout@v4 and astral-sh/setup-uv@v5; replace those tag refs with the
corresponding immutable commit SHAs for the exact releases (pin the usages of
actions/checkout and astral-sh/setup-uv to their commit hashes), verifying the
commit SHA for the desired v4 and v5 releases before updating the workflow so
each uses the full commit hash instead of the semantic tag.
- Around line 12-19: The lint job currently uses default GitHub Actions
permissions; update the "lint" job definition to explicitly set minimal
permissions (e.g., set permissions.contents: read) to enforce least-privilege
for the job named "lint" that runs the uvx ruff steps; add the permissions block
under the lint job (adjacent to runs-on/timeout-minutes) so only read access to
repository contents is granted.
In `@pyproject.toml`:
- Line 90: pyproject.toml currently globally ignores Ruff error code F821 which
is masking 51 undefined-name issues; remove "F821" from [tool.ruff.lint].ignore
and then fix the undefined references such as BatchEvaluate, StackSelector,
get_stacks_spheroid, and settings (e.g., import the correct modules, add missing
definitions, or qualify names) in the affected files like
saenopy/gui/orientation/modules/Detection.py; if a name is legitimately dynamic,
add a targeted per-file-ignores or inline `# noqa: F821` with a comment
explaining why instead of a global ignore.
In `@saenopy/examples.py`:
- Line 211: The example data for BrightfieldTFM uses the wrong material
parameter key "ds" which causes a KeyError when the solver expects
material_parameters["d_s"]; update the example dictionary in BrightfieldTFM to
use "d_s": 0.034 (or add a compatibility mapping) so SemiAffineFiberMaterial
construction reads the correct key from material_parameters.
In `@saenopy/gui/common/patch_lifreader.py`:
- Line 31: Remove the debugging print statement that spams logs in the recursive
XML parser: delete the
print(item.findall("./Data/Image/ImageDescription/Dimensions/DimensionDescription"))
call in patch_lifreader.py (the recursive traversal that iterates over item and
calls findall). If retaining info is needed for debugging, replace it with a
logger.debug(...) call instead of print so output is controllable via logging
level.
In `@saenopy/gui/solver/modules/exporter/ExporterRender2D.py`:
- Line 625: The direct index into factor_prefix (factor_prefix[power +
base_power]) can raise KeyError for computed exponents; change both occurrences
to safely look up the prefix using factor_prefix.get(power + base_power, "") (or
check membership with "if (power + base_power) in factor_prefix") and only
prepend the prefix when present, ensuring variables power, base_power and
factor_prefix in ExporterRender2D are used and both lines (the current
assignment and the similar one at the later occurrence) are updated accordingly.
- Around line 258-259: The code in ExporterRender2D.py normalizes map_im using
(vmax - vmin) which can be zero; update the normalization before the lines that
compute map_im = map_im / (vmax - vmin) and map_im = plt.get_cmap(cmap)(map_im)
to guard against division by zero (e.g., compute a safe denom = vmax - vmin and
if denom == 0 set denom = 1 or set map_im to zeros/ones as appropriate), then
perform normalization and optionally clamp values into [0,1] (using np.clip)
before calling plt.get_cmap(cmap) so color mapping remains stable when vmax ==
vmin.
In `@saenopy/gui/solver/modules/exporter/ExporterRender3D.py`:
- Around line 315-317: The scalar bar is being removed unconditionally; update
the code around plotter.remove_scalar_bar() to check params["colorbar"]["hide"]
first (i.e., call plotter.remove_scalar_bar() only when
params["colorbar"]["hide"] is True) so the 3D exporter honors the colorbar.hide
flag; look for the call to plotter.remove_scalar_bar() in ExporterRender3D and
gate it with an if params["colorbar"]["hide"]: condition.
In `@saenopy/gui/solver/modules/showVectorField.py`:
- Around line 179-181: Guard the denominator used to auto-scale "factor" in
showVectorField (the expression using np.nanmax(point_cloud[name + "_mag2"])
when computing factor) against 0 or NaN: compute denom =
np.nanmax(point_cloud[name + "_mag2"]), check with np.isfinite(denom) and denom
!= 0, and only apply factor = factor * norm_stack_size / denom when valid;
otherwise set factor to a safe default (e.g., 0 or leave unchanged) or skip
scaling so rendering remains stable. Ensure you reference the variables factor
and point_cloud[name + "_mag2"] and replace the direct division with this
guarded logic inside the same block where factor is computed.
In `@saenopy/gui/spheroid/modules/ForceCalculator.py`:
- Around line 79-86: process() is reading the live widget value
(str(self.lookup_table.value())) instead of the mapped parameter, which can
cause execution to use a different lookup table than the persisted value; change
process to pass force_parameters["lookup_table"] (converted to str if needed)
into jf.force.reconstruct_gui in place of str(self.lookup_table.value()),
ensuring the function uses the scheduled/exported lookup_table value rather than
the current widget state.
In `@saenopy/gui/spheroid/modules/LookupTable.py`:
- Around line 255-260: The run() method is still constructing the output path
from self.output (the input folder) even though you added the
QtShortCuts.QInputFilename widget self.lookup_table; update run() (and the
similar block around the other save usage at the 299-307 area) to use
self.lookup_table.value() as the destination path when present (falling back to
the previous behavior only if lookup_table.value() is empty), and ensure any
file writing uses that exact path (create parent dirs if needed) so
dialog.lookup_table.value() matches where the file is actually saved.
In `@saenopy/gui/tmp/render_results2.py`:
- Around line 337-341: The code normalizes y and then accesses np.where(y <
0.05)[0][0] without safety checks; add guards in the block around the
normalization and threshold lookup (the y = y / y[0], (l,) = plt.plot(...),
index_max = np.where(...)[0][0], plt.axvline(...) sequence): first ensure y is
non-empty and y[0] is not zero before dividing (skip plotting or handle
zero-case), and after computing idxs = np.where(y < 0.05)[0] check if idxs.size
> 0 before using idxs[0] (if empty, either skip drawing the vertical line or
choose a fallback index such as -1), so plt.axvline is only called with a valid
x[index_max].
In `@saenopy/gui/tmp/render_surface_comparison.py`:
- Around line 87-91: Protect the normalization and threshold lookup around the y
array to avoid division-by-zero and IndexError: before doing y = y / y[0] in the
block containing plt.plot(...) check if y.size>0 and y[0] != 0 (or fall back to
a safe normalization, e.g. skip normalization or use np.nan/1.0) and after
plotting, replace index_max = np.where(y < 0.05)[0][0] with a guarded search
that checks if any(y < 0.05) and only computes/plots the axvline when found
(otherwise skip or use a fallback index); refer to the y normalization line and
the index_max / plt.axvline usage to locate the edits.
In `@saenopy/macro.py`:
- Line 364: The subplot index calculation using plt.subplot(1, subplot_count, 1
+ (len(plots_stretch) > 0)) can produce an out-of-range index when subplot_count
== 1 because (len(plots_stretch) > 0) evaluates to True (1) and yields 2; change
the expression to only add the offset when there is more than one subplot.
Replace that call with logic that computes index = 1 + (1 if (len(plots_stretch)
> 0 and subplot_count > 1) else 0) and pass that index into plt.subplot,
referencing the same symbols plt.subplot, subplot_count, and plots_stretch.
In `@saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py`:
- Line 29: The iteration count calculation in mask_interpolation.py erroneously
duplicates the x-axis ratio; update the expression that computes iter (the line
using np.max([...]) inside the mask interpolation routine) to compare
mask.shape[0] / dims[0] and mask.shape[1] / dims[1] so both height and width
scaling are considered (keep the ceil and * 5 safety factor intact). Ensure you
only change the second term from mask.shape[0] / dims[0] to mask.shape[1] /
dims[1] so the upscaling iteration count correctly reflects both axes.
In `@saenopy/solver.py`:
- Around line 1344-1350: The distance calculation is using
result.solvers[0].mesh instead of the current solver, so replace references to
result.solvers[0].mesh with result.solvers[i].mesh when computing dist_to_cell
(the np.min / np.linalg.norm expression that uses mesh.nodes and yxz), then keep
assigning result.solvers[i].mesh.cell_boundary_mask = included; ensure you use
the same element_size, yxz and i variables already in scope and that
result.solvers[i].mesh exists before computing.
In `@saenopy/unused/macro.py`:
- Line 591: The plt.subplot call uses 1 + (len(plots_stretch) > 0) which yields
2 when plots_stretch is non-empty (because True == 1) and can select a
non-existent subplot when subplot_count == 1; update the index expression in the
plt.subplot call (the one using subplot_count and plots_stretch) to ensure the
chosen index is clamped to the available subplot_count (for example, use min(1 +
int(len(plots_stretch) > 0), subplot_count) or equivalent) so the index never
exceeds subplot_count.
---
Outside diff comments:
In `@saenopy/gui/solver/modules/exporter/FiberViewer.py`:
- Around line 24-38: The global caches dict keyed by id(data) in get_cache (and
the Cache placeholder class) can grow unbounded and risk stale reuse on id
reuse; replace the manual id-based dict with a weak-reference mapping (e.g., use
weakref.WeakKeyDictionary or WeakValueDictionary) keyed by the ndarray object
itself so entries are removed automatically when the array is garbage-collected,
update get_cache to look up and store cache objects in that weak map instead of
using id(data), and ensure any creation logic still returns a Cache instance
when needed (symbols: caches, Cache, get_cache, id(data)).
- Around line 155-177: The default argument for process_stack is wrong: change
the parameter named range (used by scale_intensity) from the built-in type to a
numeric 2-tuple (e.g., range=(0.0, 1.0)) so that scale_intensity(tuple(range))
receives a valid pair; update the function signature in process_stack and any
tests or callers relying on the old default if needed, and ensure references to
scale_intensity, process_stack, and the range parameter are consistent.
---
Nitpick comments:
In `@pyproject.toml`:
- Line 84: Ruff is currently ignoring E722 (bare except:) which masks four
occurrences in saenopy/gui/common — replace each bare except: in
QExtendedGraphicsView.py (around line 324) and QtShortCuts.py (around lines
1423, 1458, 1561) with a more specific handler (preferably except Exception: or
an even narrower exception type relevant to the try block), run unit/interactive
tests and ruff (ruff check --select E722 --statistics .) to confirm no remaining
instances, and once all occurrences are fixed remove "E722" from the ignore list
in pyproject.toml so the linter enforces it going forward.
In `@saenopy/gui/common/sigmoid_widget.py`:
- Around line 214-218: The get_range() function currently has an early return
that makes the mapped-value branch unreachable; remove the immediate "return
self.minx, self.maxx" and keep the computed mapped return using
original_min/original_max so get_range() returns (original_min + (original_max -
original_min)*minx, original_min + (original_max - original_min)*maxx); ensure
you update any callers if the API was intended to stay normalized instead
(alternatively invert this change if normalized values are the desired API).
In `@saenopy/gui/orientation/modules/result.py`:
- Around line 63-91: The DistanceResult TypedDict is currently defined with an
empty class then immediately reassigned via the TypedDict(...) functional call;
remove the redundant empty class definition and choose a single consistent
pattern (either convert the functional call into a class-based TypedDict class
named DistanceResult with the listed keys as annotations, or keep the functional
TypedDict(...) and delete the empty "class DistanceResult(TypedDict): pass");
update references if needed to the symbol DistanceResult so code uses the chosen
definition; mirror the same refactor you applied for GlobalResult to maintain
consistency.
- Around line 44-60: The current pattern creates an empty class
GlobalResult(TypedDict) and then reassigns GlobalResult via TypedDict(...);
remove the redundant empty class and use a single, idiomatic definition for the
TypedDict: either replace both lines with a class-based TypedDict named
GlobalResult containing annotated keys ("Mean Coherency", "Mean Angle", etc.)
mapped to float, or keep only the functional assignment GlobalResult =
TypedDict("GlobalResult", {...}); ensure the final symbol GlobalResult refers to
a single TypedDict definition and adjust imports if necessary.
In `@saenopy/gui/tfm2d/modules/draw.py`:
- Around line 120-121: Remove the redundant method-level "import numpy as np"
statement in saenopy/gui/tfm2d/modules/draw.py; instead rely on the existing
module-level numpy import (np) already present at the top of the file, so delete
the in-method import line and ensure all uses inside that function reference the
module-level "np" identifier.
In `@saenopy/gui/tmp/render_results2.py`:
- Around line 7-95: The file defines the dictionary named params twice (the
initial params block and a later params that overwrites it), leaving the first
block dead; remove or rename the first params block (or merge its intended keys
into the later params dictionary) so there is a single authoritative params
variable used by the rest of render_results2.py, and if you rename it update all
references to params accordingly (search for the symbol params to locate
usages).
In `@saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py`:
- Line 31: Remove the leftover debug print call print(iter) in
mask_interpolation.py: delete the print(iter) statement (or replace it with a
suitable logger call using the project's logging facility if runtime info is
required) so there are no stray prints; locate the occurrence by searching for
the literal print(iter) or references to the iter variable in the mask
interpolation function and update accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 70f02b39-e054-4dab-9068-471b8fc4c333
📒 Files selected for processing (133)
.github/workflows/test.ymlpyproject.tomlsaenopy/__init__.pysaenopy/add_source.pysaenopy/conjugate_gradient.pysaenopy/examples.pysaenopy/export_html.pysaenopy/get_deformations.pysaenopy/gui/code/code_editor.pysaenopy/gui/code/gui_code.pysaenopy/gui/code/script_file.pysaenopy/gui/code/syntax.pysaenopy/gui/code_evluate_normal.pysaenopy/gui/common/AddFilesDialog.pysaenopy/gui/common/BatchEvaluateBase.pysaenopy/gui/common/ModuleColorBar.pysaenopy/gui/common/ModuleScaleBar.pysaenopy/gui/common/PipelineModule.pysaenopy/gui/common/PlottingWindowBase.pysaenopy/gui/common/QExtendedGraphicsView.pysaenopy/gui/common/QTimeSlider.pysaenopy/gui/common/QtShortCuts.pysaenopy/gui/common/TabModule.pysaenopy/gui/common/code_export.pysaenopy/gui/common/gui_classes.pysaenopy/gui/common/lif_reader.pysaenopy/gui/common/patch_lifreader.pysaenopy/gui/common/resources.pysaenopy/gui/common/sigmoid_widget.pysaenopy/gui/common/stack_preview.pysaenopy/gui/common/stack_selector.pysaenopy/gui/common/stack_selector_crop.pysaenopy/gui/common/stack_selector_leica.pysaenopy/gui/common/stack_selector_tif.pysaenopy/gui/example_creator.pysaenopy/gui/gui_master.pysaenopy/gui/material_fit/gui_fit.pysaenopy/gui/orientation/analyze/PlottingWindow.pysaenopy/gui/orientation/gui_orientation.pysaenopy/gui/orientation/modules/AddFilesDialog.pysaenopy/gui/orientation/modules/BatchEvaluate.pysaenopy/gui/orientation/modules/Detection.pysaenopy/gui/orientation/modules/Segmentation.pysaenopy/gui/orientation/modules/TabAngle.pysaenopy/gui/orientation/modules/TabCellOutline.pysaenopy/gui/orientation/modules/TabCoherence.pysaenopy/gui/orientation/modules/TabOrientationField.pysaenopy/gui/orientation/modules/result.pysaenopy/gui/solver/analyze/PlottingWindow.pysaenopy/gui/solver/gui_solver.pysaenopy/gui/solver/modules/BatchEvaluate.pysaenopy/gui/solver/modules/DeformationDetector.pysaenopy/gui/solver/modules/Initializer.pysaenopy/gui/solver/modules/MeshCreator.pysaenopy/gui/solver/modules/Regularizer.pysaenopy/gui/solver/modules/TabFittedDeformations.pysaenopy/gui/solver/modules/TabForces.pysaenopy/gui/solver/modules/TabPiv.pysaenopy/gui/solver/modules/TabResultView.pysaenopy/gui/solver/modules/TabStackDisplay.pysaenopy/gui/solver/modules/TabTargetDeformations.pysaenopy/gui/solver/modules/VTK_Toolbar.pysaenopy/gui/solver/modules/exporter/ExportRenderCommon.pysaenopy/gui/solver/modules/exporter/Exporter.pysaenopy/gui/solver/modules/exporter/ExporterRender2D.pysaenopy/gui/solver/modules/exporter/ExporterRender3D.pysaenopy/gui/solver/modules/exporter/FiberViewer.pysaenopy/gui/solver/modules/load_measurement_dialog.pysaenopy/gui/solver/modules/path_editor.pysaenopy/gui/solver/modules/showVectorField.pysaenopy/gui/spheroid/analyze/PlottingWindow.pysaenopy/gui/spheroid/gui_deformation_spheroid.pysaenopy/gui/spheroid/modules/AddFilesDialog.pysaenopy/gui/spheroid/modules/BatchEvaluate.pysaenopy/gui/spheroid/modules/DeformationDetector.pysaenopy/gui/spheroid/modules/ForceCalculator.pysaenopy/gui/spheroid/modules/ListWidget.pysaenopy/gui/spheroid/modules/LookupTable.pysaenopy/gui/spheroid/modules/MatplotlibWidget.pysaenopy/gui/spheroid/modules/QSlider.pysaenopy/gui/spheroid/modules/helper.pysaenopy/gui/spheroid/modules/path_editor.pysaenopy/gui/spheroid/modules/result.pysaenopy/gui/tfm2d/analyze/PlottingWindow.pysaenopy/gui/tfm2d/gui_2d.pysaenopy/gui/tfm2d/modules/BatchEvaluate.pysaenopy/gui/tfm2d/modules/CalculateDisplacements.pysaenopy/gui/tfm2d/modules/CalculateForceGeneration.pysaenopy/gui/tfm2d/modules/CalculateForces.pysaenopy/gui/tfm2d/modules/CalculateStress.pysaenopy/gui/tfm2d/modules/DisplayCellImage.pysaenopy/gui/tfm2d/modules/DisplayDeformed.pysaenopy/gui/tfm2d/modules/PipelineModule.pysaenopy/gui/tfm2d/modules/TabDeformations.pysaenopy/gui/tfm2d/modules/TabForces.pysaenopy/gui/tfm2d/modules/TabStress.pysaenopy/gui/tfm2d/modules/draw.pysaenopy/gui/tfm2d/modules/load_measurement_dialog.pysaenopy/gui/tfm2d/modules/path_editor.pysaenopy/gui/tfm2d/modules/result.pysaenopy/gui/tmp/render_results2.pysaenopy/gui/tmp/render_surface_comparison.pysaenopy/macro.pysaenopy/materials.pysaenopy/mesh.pysaenopy/multigrid_helper.pysaenopy/pyTFM/TFM_functions.pysaenopy/pyTFM/TFM_tractions.pysaenopy/pyTFM/calculate_deformation.pysaenopy/pyTFM/calculate_forces.pysaenopy/pyTFM/calculate_strain_energy.pysaenopy/pyTFM/calculate_stress.pysaenopy/pyTFM/calculate_stress_imports/find_borders.pysaenopy/pyTFM/calculate_stress_imports/graph_theory_for_cell_boundaries.pysaenopy/pyTFM/calculate_stress_imports/grid_setup_solids_py.pysaenopy/pyTFM/calculate_stress_imports/mask_interpolation.pysaenopy/pyTFM/calculate_stress_imports/stress_functions.pysaenopy/pyTFM/correct_stage_drift.pysaenopy/pyTFM/plotting.pysaenopy/result_file.pysaenopy/saveable.pysaenopy/solver.pysaenopy/stack.pysaenopy/unused/macro.pytest/interactive_material.pytest/interactive_material2.pytest/mock_dir.pytest/test_download_example.pytest/test_load_stack.pytest/test_material.pytest/test_mesh.pytest/test_non_regularize.pytest/test_use_gui.py
| lint: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: astral-sh/setup-uv@v5 | ||
| - run: uvx ruff check . | ||
| - run: uvx ruff format --check . |
There was a problem hiding this comment.
Add explicit permissions to follow least-privilege principle.
The lint job inherits default permissions, which are broader than necessary. Restrict to read-only contents access.
🔒 Recommended fix
lint:
runs-on: ubuntu-latest
timeout-minutes: 5
+ permissions:
+ contents: read
steps:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| lint: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v5 | |
| - run: uvx ruff check . | |
| - run: uvx ruff format --check . | |
| lint: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v5 | |
| - run: uvx ruff check . | |
| - run: uvx ruff format --check . |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 16-16: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 12-19: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/test.yml around lines 12 - 19, The lint job currently uses
default GitHub Actions permissions; update the "lint" job definition to
explicitly set minimal permissions (e.g., set permissions.contents: read) to
enforce least-privilege for the job named "lint" that runs the uvx ruff steps;
add the permissions block under the lint job (adjacent to
runs-on/timeout-minutes) so only read access to repository contents is granted.
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
Consider disabling credential persistence for defense-in-depth.
While this lint job doesn't use credentials, setting persist-credentials: false prevents the GITHUB_TOKEN from being accessible to subsequent steps as a security best practice.
🔒 Optional hardening
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false🧰 Tools
🪛 zizmor (1.25.2)
[warning] 16-16: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/test.yml at line 16, Add credential persistence hardening
to the checkout step: update the GitHub Actions checkout step (uses:
actions/checkout@v4) to set persist-credentials: false so the GITHUB_TOKEN is
not kept available to subsequent steps; ensure the lint job does not rely on
persisted credentials after updating the checkout configuration.
| - uses: actions/checkout@v4 | ||
| - uses: astral-sh/setup-uv@v5 |
There was a problem hiding this comment.
Pin action references to commit hashes for supply-chain security.
Both actions use semantic version tags rather than commit hashes, creating a supply-chain risk since tags can theoretically be moved. Best practice is to pin to immutable commit hashes.
🔒 Recommended fix
- - uses: actions/checkout@v4
- - uses: astral-sh/setup-uv@v5
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - uses: astral-sh/setup-uv@f0ec0a0267ad3f8c0311dfa8cecbddbe9e4d4204 # v5.3.0Note: Verify the latest commit hashes for these versions at the time of merge.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v5 | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: astral-sh/setup-uv@f0ec0a0267ad3f8c0311dfa8cecbddbe9e4d4204 # v5.3.0 |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 16-16: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 16-16: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/test.yml around lines 16 - 17, The workflow currently
references actions by mutable tags actions/checkout@v4 and
astral-sh/setup-uv@v5; replace those tag refs with the corresponding immutable
commit SHAs for the exact releases (pin the usages of actions/checkout and
astral-sh/setup-uv to their commit hashes), verifying the commit SHA for the
desired v4 and v5 releases before updating the workflow so each uses the full
commit hash instead of the semantic tag.
| "F401", | ||
| "F541", | ||
| "F811", | ||
| "F821", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check for undefined name violations that are currently being ignored
# Create temporary config without F821 in ignore list
uvx ruff check --select F821 --statistics .Repository: rgerum/saenopy
Length of output: 102
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Inspect Ruff config around the mentioned lines
echo "== pyproject.toml (Ruff section near line 90) =="
python3 - <<'PY'
import itertools
path="pyproject.toml"
start=60
end=110
with open(path,'r',encoding='utf-8') as f:
for i,line in enumerate(f,1):
if start <= i <= end:
print(f"{i:4d}:{line.rstrip()}")
if i>end: break
PY
# 2) Check whether ruff is available in this environment
echo "== ruff availability =="
( ruff --version ) 2>/dev/null || echo "ruff not found on PATH"
( python3 -m ruff --version ) 2>/dev/null || echo "python -m ruff not available"
# 3) Try running F821-only checks
echo "== running: ruff check --select F821 --statistics . =="
if command -v ruff >/dev/null 2>&1; then
ruff check --select F821 --statistics .
else
python3 -m ruff check --select F821 --statistics .
fiRepository: rgerum/saenopy
Length of output: 874
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== running: ruff check --statistics . (default config) =="
ruff check --statistics . || true
echo
echo "== F821 file counts =="
ruff check --select F821 --statistics . --output-format=concise 2>/dev/null | head -n 50 || true
echo
echo "== sample F821 errors (first 30) =="
ruff check --select F821 . --output-format=full 2>/dev/null | head -n 120 || trueRepository: rgerum/saenopy
Length of output: 5863
Stop globally ignoring Ruff F821—it’s currently masking real undefined-name findings.
pyproject.toml globally ignores F821 ([tool.ruff.lint].ignore includes "F821"), and yet ruff check --select F821 reports 51 F821 undefined-name errors—e.g., undefined BatchEvaluate/StackSelector type refs and (more concerning) undefined get_stacks_spheroid and settings in saenopy/gui/orientation/modules/Detection.py.
Remove F821 from the global ignore and fix the reported undefined names; if any are truly unavoidable (e.g., dynamic imports), use targeted # noqa: F821 / per-file-ignores with documentation rather than a blanket ignore.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyproject.toml` at line 90, pyproject.toml currently globally ignores Ruff
error code F821 which is masking 51 undefined-name issues; remove "F821" from
[tool.ruff.lint].ignore and then fix the undefined references such as
BatchEvaluate, StackSelector, get_stacks_spheroid, and settings (e.g., import
the correct modules, add missing definitions, or qualify names) in the affected
files like saenopy/gui/orientation/modules/Detection.py; if a name is
legitimately dynamic, add a targeted per-file-ignores or inline `# noqa: F821`
with a comment explaining why instead of a global ignore.
| "k": 6062.0, | ||
| "d_0": 0.0025, | ||
| "lambda_s": 0.0804, | ||
| "ds": 0.034, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check which material parameter key name is used in materials module and other examples
# Check the materials module for expected parameter names
echo "=== Checking materials module for parameter definitions ==="
rg -n -C3 "class.*Material|def __init__.*d_s|def __init__.*ds" saenopy/materials.py
# Check other examples for consistency
echo -e "\n=== Checking other example configs for d_s vs ds usage ==="
rg -n "\"d_s\"|'d_s'|\"ds\"|'ds'" saenopy/examples.py
# Check if any code references the old key name
echo -e "\n=== Checking for references to material_parameters\[\"d_s\"\] ==="
rg -n "material_parameters\[.*d_s" --type pyRepository: rgerum/saenopy
Length of output: 1892
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for material parameter access using key \"ds\" ==="
rg -n "material_parameters\[['\"]ds['\"]\]|material_parameters\.get\(['\"]ds['\"]\)" -S . --type py
echo -e "\n=== Search for any usage of string literal \"ds\" alongside \"d_s\" ==="
rg -n "\"ds\"|'ds'|\"d_s\"|'d_s'" saenopy -S --type py
echo -e "\n=== Search for mapping/normalization from \"ds\" to d_s ==="
rg -n "ds\s*[:=]\s*|['\"]ds['\"]\s*(\)|\]|\}|,)|replace\(|lower\(|upper\(|normalize" saenopy -S --type pyRepository: rgerum/saenopy
Length of output: 118
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Occurrences of string literal \"ds\" in saenopy (python only) ==="
rg -n "\"ds\"|'ds'" saenopy -S --type py
echo -e "\n=== Occurrences of parameter key \"d_s\" usage (python only) ==="
rg -n "material_parameters\[['\"]d_s['\"]\]|material_parameters\.get\(['\"]d_s['\"]\)" saenopy -S --type py
echo -e "\n=== Inspect SemiAffineFiberMaterial signature / assignments in saenopy/materials.py ==="
python3 - <<'PY'
import itertools, pathlib, re
p = pathlib.Path("saenopy/materials.py")
lines = p.read_text().splitlines()
for i,l in enumerate(lines, start=1):
if "class SemiAffineFiberMaterial" in l:
start=i-5
end=i+80
for j in range(start, end+1):
if 1<=j<=len(lines):
print(f"{j}:{lines[j-1]}")
break
PYRepository: rgerum/saenopy
Length of output: 4639
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Where material model is constructed from material_parameters (search) ==="
rg -n "SemiAffineFiberMaterial\(|LinearMaterial\(|material_parameters\s*=" -S saenopy --type py
echo -e "\n=== Search for places that reference material_parameters dict contents ==="
rg -n "material_parameters\[" -S saenopy --type py
echo -e "\n=== Inspect result_file.py conversion from old key \"ds\" to \"d_s\" ==="
python3 - <<'PY'
import pathlib
p=pathlib.Path("saenopy/result_file.py")
lines=p.read_text().splitlines()
for i,l in enumerate(lines, start=1):
if 'old="ds"' in l or "new=\"d_s\"" in l:
start=max(1,i-15); end=min(len(lines), i+15)
for j in range(start,end+1):
print(f"{j}:{lines[j-1]}")
print("-----")
PY
echo -e "\n=== Search for any normalization of ds->d_s outside result_file.py ==="
rg -n "old=\"ds\"|new=\"d_s\"|ds\".*d_s|d_s.*ds|normalize.*ds|\\bds\\b.*\\bd_s\\b" -S saenopy --type pyRepository: rgerum/saenopy
Length of output: 8519
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Context around saenopy/examples.py:211 ==="
python3 - <<'PY'
import pathlib
p=pathlib.Path("saenopy/examples.py")
lines=p.read_text().splitlines()
for i in range(190, 225):
if 1 <= i <= len(lines):
print(f"{i}:{lines[i-1]}")
PY
echo -e "\n=== Inspect saenopy/gui/code_evluate_normal.py material_parameters construction ==="
python3 - <<'PY'
import pathlib, re
p=pathlib.Path("saenopy/gui/code_evluate_normal.py")
lines=p.read_text().splitlines()
# print around the "material_parameters =" block
for idx,l in enumerate(lines, start=1):
if re.search(r"\bmaterial_parameters\s*=\s*{", l):
start=max(1, idx-5); end=min(len(lines), idx+25)
for i in range(start,end+1):
print(f"{i}:{lines[i-1]}")
break
PY
echo -e "\n=== Inspect Regularizer.py where it reads material_parameters keys ==="
python3 - <<'PY'
import pathlib
p=pathlib.Path("saenopy/gui/solver/modules/Regularizer.py")
lines=p.read_text().splitlines()
for target in [300,360,370,400]:
start=max(1,target-5); end=min(len(lines), target+20)
print(f"\n--- around line {target} ---")
for i in range(start,end+1):
print(f"{i}:{lines[i-1]}")
PYRepository: rgerum/saenopy
Length of output: 8141
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Find where saenopy/examples.py is imported/used ==="
rg -n "import saenopy\.examples|from saenopy\.examples import|saenopy\.examples" -S saenopy --type py
echo -e "\n=== Find where example[\"material_parameters\"] is accessed ==="
rg -n "example\[['\"]material_parameters['\"]\]|\\bmaterial_parameters\\b\\s*=\\s*example\\[|\\bmaterial_parameters\\b\\s*=\\s*.*example\\[" -S saenopy --type py
echo -e "\n=== Find where downloaded example dicts are wired into solver Regularizer ==="
rg -n "material_parameters\\b.*example|example.*material_parameters" -S saenopy --type pyRepository: rgerum/saenopy
Length of output: 1296
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Inspect saenopy/gui/solver/modules/BatchEvaluate.py around example material_parameters ==="
python3 - <<'PY'
import pathlib, re
p=pathlib.Path("saenopy/gui/solver/modules/BatchEvaluate.py")
lines=p.read_text().splitlines()
for i,l in enumerate(lines, start=1):
if 'data.material_parameters = example["material_parameters"]' in l:
start=max(1,i-30); end=min(len(lines), i+80)
for j in range(start,end+1):
print(f"{j}:{lines[j-1]}")
break
PY
echo -e "\n=== Search for material_parameters usage in BatchEvaluate.py ==="
rg -n "material_parameters" saenopy/gui/solver/modules/BatchEvaluate.py --type pyRepository: rgerum/saenopy
Length of output: 2160
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for get_examples implementation and any ds->d_s normalization in saenopy/examples.py ==="
rg -n "def get_examples|get_examples\(" saenopy/examples.py --type py
echo -e "\n=== List all occurrences of \"ds\" in saenopy/examples.py with context ==="
rg -n -C2 "\"ds\"|'ds'" saenopy/examples.py --type py
echo -e "\n=== Look for any replace/rename mapping between ds and d_s in saenopy/examples.py ==="
rg -n "ds.*d_s|d_s.*ds|rename.*ds|replace.*ds|material_parameters.*ds" saenopy/examples.py --type py
echo -e "\n=== Inspect get_examples function block (approx) ==="
python3 - <<'PY'
import pathlib, re
p=pathlib.Path("saenopy/examples.py")
lines=p.read_text().splitlines()
# find first def get_examples occurrence
idx=None
for i,l in enumerate(lines, start=1):
if re.match(r'\s*def\s+get_examples\s*\(', l):
idx=i; break
if idx is None:
# fallback: any get_examples_*
for i,l in enumerate(lines, start=1):
if 'def get_examples' in l:
idx=i; break
if idx:
start=max(1,idx-10); end=min(len(lines), idx+200)
for j in range(start,end+1):
print(f"{j}:{lines[j-1]}")
else:
print("No get_examples definition found")
PYRepository: rgerum/saenopy
Length of output: 603
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Search for get_examples implementation and any ds->d_s normalization in saenopy/examples.py ==="
rg -n "def get_examples|get_examples\(" saenopy/examples.py --type py
echo -e "\n=== List all occurrences of \"ds\" in saenopy/examples.py with context ==="
rg -n -C2 "\"ds\"|'ds'" saenopy/examples.py --type py
echo -e "\n=== Look for any replace/rename mapping between ds and d_s in saenopy/examples.py ==="
rg -n "ds.*d_s|d_s.*ds|rename.*ds|replace.*ds|material_parameters.*ds" saenopy/examples.py --type py
echo -e "\n=== Inspect get_examples function block (approx) ==="
python3 - <<'PY'
import pathlib, re
p=pathlib.Path("saenopy/examples.py")
lines=p.read_text().splitlines()
idx=None
for i,l in enumerate(lines, start=1):
if re.match(r'\s*def\s+get_examples\s*\(', l):
idx=i; break
if idx is None:
for i,l in enumerate(lines, start=1):
if 'def get_examples' in l:
idx=i; break
if idx:
start=max(1,idx-10); end=min(len(lines), idx+200)
for j in range(start,end+1):
print(f"{j}:{lines[j-1]}")
else:
print("No get_examples definition found")
PYRepository: rgerum/saenopy
Length of output: 603
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Inspect saenopy/examples.py: get_examples() block (around line 76) ==="
python3 - <<'PY'
import pathlib
p=pathlib.Path("saenopy/examples.py")
lines=p.read_text().splitlines()
start=40; end=160
for i in range(start, min(end, len(lines))+1):
print(f"{i}:{lines[i-1]}")
PY
echo -e "\n=== Inspect saenopy/examples.py: section containing material_parameters with \"ds\" (around line 180-230) ==="
python3 - <<'PY'
import pathlib
p=pathlib.Path("saenopy/examples.py")
lines=p.read_text().splitlines()
start=160; end=235
for i in range(start, min(end, len(lines))+1):
print(f"{i}:{lines[i-1]}")
PYRepository: rgerum/saenopy
Length of output: 9700
Fix inconsistent material parameter key in example data (d_s vs ds).
saenopy/examples.py uses "ds": 0.034, in the BrightfieldTFM example, while the solver expects material_parameters["d_s"] when constructing SemiAffineFiberMaterial (and examples are passed through without normalization), so loading that example will raise a KeyError.
"ds": 0.034,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@saenopy/examples.py` at line 211, The example data for BrightfieldTFM uses
the wrong material parameter key "ds" which causes a KeyError when the solver
expects material_parameters["d_s"]; update the example dictionary in
BrightfieldTFM to use "d_s": 0.034 (or add a compatibility mapping) so
SemiAffineFiberMaterial construction reads the correct key from
material_parameters.
| y = y / y[0] | ||
| (l,) = plt.plot(x * 1e6, y * 100, "-", label=label) | ||
| index_max = np.where(y < 0.05)[0][0] | ||
| print(x[index_max] * 1e6) | ||
| plt.axvline(x[index_max] * 1e6, color=l.get_color(), lw=0.8, linestyle="--") |
There was a problem hiding this comment.
Guard normalization/threshold lookup to avoid runtime crashes.
Line 87 and Line 89 can fail on valid edge inputs (y[0] == 0 or no values below 0.05), causing division by zero or IndexError.
Proposed fix
- y = y / y[0]
- (l,) = plt.plot(x * 1e6, y * 100, "-", label=label)
- index_max = np.where(y < 0.05)[0][0]
- print(x[index_max] * 1e6)
- plt.axvline(x[index_max] * 1e6, color=l.get_color(), lw=0.8, linestyle="--")
+ if y.size == 0 or y[0] <= 0:
+ return None
+ y = y / y[0]
+ (l,) = plt.plot(x * 1e6, y * 100, "-", label=label)
+ below = np.where(y < 0.05)[0]
+ if below.size:
+ index_max = below[0]
+ print(x[index_max] * 1e6)
+ plt.axvline(x[index_max] * 1e6, color=l.get_color(), lw=0.8, linestyle="--")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| y = y / y[0] | |
| (l,) = plt.plot(x * 1e6, y * 100, "-", label=label) | |
| index_max = np.where(y < 0.05)[0][0] | |
| print(x[index_max] * 1e6) | |
| plt.axvline(x[index_max] * 1e6, color=l.get_color(), lw=0.8, linestyle="--") | |
| if y.size == 0 or y[0] <= 0: | |
| return None | |
| y = y / y[0] | |
| (l,) = plt.plot(x * 1e6, y * 100, "-", label=label) | |
| below = np.where(y < 0.05)[0] | |
| if below.size: | |
| index_max = below[0] | |
| print(x[index_max] * 1e6) | |
| plt.axvline(x[index_max] * 1e6, color=l.get_color(), lw=0.8, linestyle="--") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@saenopy/gui/tmp/render_surface_comparison.py` around lines 87 - 91, Protect
the normalization and threshold lookup around the y array to avoid
division-by-zero and IndexError: before doing y = y / y[0] in the block
containing plt.plot(...) check if y.size>0 and y[0] != 0 (or fall back to a safe
normalization, e.g. skip normalization or use np.nan/1.0) and after plotting,
replace index_max = np.where(y < 0.05)[0][0] with a guarded search that checks
if any(y < 0.05) and only computes/plots the axvline when found (otherwise skip
or use a fallback index); refer to the y normalization line and the index_max /
plt.axvline usage to locate the edits.
| plt.ylabel("stress") | ||
| if len(plots_stretch): | ||
| plt.subplot(1, subplot_count, 1+(len(plots_stretch)>0)) | ||
| plt.subplot(1, subplot_count, 1 + (len(plots_stretch) > 0)) |
There was a problem hiding this comment.
Fix subplot index calculation for stretch-only runs.
Line 364 computes subplot index from plots_stretch, which becomes 2 when subplot_count == 1, causing an invalid subplot selection.
Proposed fix
- plt.subplot(1, subplot_count, 1 + (len(plots_stretch) > 0))
+ plt.subplot(1, subplot_count, 1 + (len(plots_shear) > 0))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| plt.subplot(1, subplot_count, 1 + (len(plots_stretch) > 0)) | |
| plt.subplot(1, subplot_count, 1 + (len(plots_shear) > 0)) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@saenopy/macro.py` at line 364, The subplot index calculation using
plt.subplot(1, subplot_count, 1 + (len(plots_stretch) > 0)) can produce an
out-of-range index when subplot_count == 1 because (len(plots_stretch) > 0)
evaluates to True (1) and yields 2; change the expression to only add the offset
when there is more than one subplot. Replace that call with logic that computes
index = 1 + (1 if (len(plots_stretch) > 0 and subplot_count > 1) else 0) and
pass that index into plt.subplot, referencing the same symbols plt.subplot,
subplot_count, and plots_stretch.
| iter = int( | ||
| np.ceil(np.max([mask.shape[0] / dims[0], mask.shape[0] / dims[0]])) * 5 | ||
| ) # times 5 is safety factor | ||
| iter = int(np.ceil(np.max([mask.shape[0] / dims[0], mask.shape[0] / dims[0]])) * 5) # times 5 is safety factor |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for the function usage and any related tests
rg -n "mask_interpolation" --type=py -C3Repository: rgerum/saenopy
Length of output: 5734
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the exact code around the referenced line
sed -n '1,120p' saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py | nl -ba | sed -n '20,60p'
# Check for the duplicated ratio pattern anywhere else
rg -n "mask\.shape\[0\]\s*/\s*dims\[0\].*mask\.shape\[0\]\s*/\s*dims\[0\]" saenopy/pyTFM || true
rg -n "np\.max\(\[mask\.shape\[0\]\s*/\s*dims\[0\]" saenopy/pyTFM || true
# Search for any tests mentioning mask_interpolation / interpolation behavior
rg -n "mask_interpolation" --type=py -S . || trueRepository: rgerum/saenopy
Length of output: 101
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py"
echo "---- code around referenced line ----"
python3 - <<'PY'
import itertools
path="saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py"
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
if 10 <= i <= 60:
print(f"{i:4d}:{line.rstrip()}")
PY
echo "---- search for the duplication pattern ----"
rg -n "mask\.shape\[0\]\s*/\s*dims\[0\].*mask\.shape\[0\]\s*/\s*dims\[0\]" saenopy/pyTFM || true
rg -n "np\.max\(\[mask\.shape\[0\]\s*/\s*dims\[0\]" saenopy/pyTFM || true
echo "---- search for any tests referencing mask_interpolation ----"
rg -n "mask_interpolation" -S . || trueRepository: rgerum/saenopy
Length of output: 3013
Fix duplicated dimension ratio in mask interpolation iteration calculation
In saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py (line 29), np.max([mask.shape[0] / dims[0], mask.shape[0] / dims[0]]) repeats the same value, so iter ignores the y-axis scaling; the second term should use mask.shape[1] / dims[1] to compare both dimensions for the upscaling case.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@saenopy/pyTFM/calculate_stress_imports/mask_interpolation.py` at line 29, The
iteration count calculation in mask_interpolation.py erroneously duplicates the
x-axis ratio; update the expression that computes iter (the line using
np.max([...]) inside the mask interpolation routine) to compare mask.shape[0] /
dims[0] and mask.shape[1] / dims[1] so both height and width scaling are
considered (keep the ceil and * 5 safety factor intact). Ensure you only change
the second term from mask.shape[0] / dims[0] to mask.shape[1] / dims[1] so the
upscaling iteration count correctly reflects both axes.
| dist_to_cell = np.min( | ||
| np.linalg.norm(result.solvers[0].mesh.nodes[:, :, None] - yxz[None, :, :], axis=1), | ||
| axis=1, | ||
| ) | ||
| included = dist_to_cell < element_size / 2 | ||
|
|
||
| result.solvers[i].mesh.cell_boundary_mask = included |
There was a problem hiding this comment.
Use the current solver index when computing cell-boundary distances.
Line 1344 hardcodes result.solvers[0] while Line 1350 writes to result.solvers[i], so multi-stack results get masks from the wrong mesh.
Proposed fix
- dist_to_cell = np.min(
- np.linalg.norm(result.solvers[0].mesh.nodes[:, :, None] - yxz[None, :, :], axis=1),
- axis=1,
- )
+ dist_to_cell = np.min(
+ np.linalg.norm(result.solvers[i].mesh.nodes[:, :, None] - yxz[None, :, :], axis=1),
+ axis=1,
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| dist_to_cell = np.min( | |
| np.linalg.norm(result.solvers[0].mesh.nodes[:, :, None] - yxz[None, :, :], axis=1), | |
| axis=1, | |
| ) | |
| included = dist_to_cell < element_size / 2 | |
| result.solvers[i].mesh.cell_boundary_mask = included | |
| dist_to_cell = np.min( | |
| np.linalg.norm(result.solvers[i].mesh.nodes[:, :, None] - yxz[None, :, :], axis=1), | |
| axis=1, | |
| ) | |
| included = dist_to_cell < element_size / 2 | |
| result.solvers[i].mesh.cell_boundary_mask = included |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@saenopy/solver.py` around lines 1344 - 1350, The distance calculation is
using result.solvers[0].mesh instead of the current solver, so replace
references to result.solvers[0].mesh with result.solvers[i].mesh when computing
dist_to_cell (the np.min / np.linalg.norm expression that uses mesh.nodes and
yxz), then keep assigning result.solvers[i].mesh.cell_boundary_mask = included;
ensure you use the same element_size, yxz and i variables already in scope and
that result.solvers[i].mesh exists before computing.
| plt.ylabel("stress") | ||
| if len(plots_stretch): | ||
| plt.subplot(1, subplot_count, 1+(len(plots_stretch)>0)) | ||
| plt.subplot(1, subplot_count, 1 + (len(plots_stretch) > 0)) |
There was a problem hiding this comment.
Fix subplot index for stretch-only plotting path.
Line 591 uses 1 + (len(plots_stretch) > 0), which selects subplot 2 even when only one subplot exists.
Proposed fix
- plt.subplot(1, subplot_count, 1 + (len(plots_stretch) > 0))
+ plt.subplot(1, subplot_count, 1 + (len(plots_shear) > 0))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@saenopy/unused/macro.py` at line 591, The plt.subplot call uses 1 +
(len(plots_stretch) > 0) which yields 2 when plots_stretch is non-empty (because
True == 1) and can select a non-existent subplot when subplot_count == 1; update
the index expression in the plt.subplot call (the one using subplot_count and
plots_stretch) to ensure the chosen index is clamped to the available
subplot_count (for example, use min(1 + int(len(plots_stretch) > 0),
subplot_count) or equivalent) so the index never exceeds subplot_count.
# Conflicts: # pyproject.toml # saenopy/gui/code/syntax.py # test/test_use_gui.py
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/installer.yml:
- Around line 14-17: Replace mutable action tags with immutable commit SHAs for
the referenced steps: pin actions/checkout@v6, astral-sh/setup-uv@v7, and
actions/upload-artifact@v4 to their respective commit SHAs in the
`.github/workflows/installer.yml` workflow; additionally, in each checkout step
that uses actions/checkout (the steps currently using actions/checkout@v6), add
the with: persist-credentials: false setting to the checkout step configuration
to prevent credential propagation. Locate the steps by the exact uses: tokens
(actions/checkout@v6, astral-sh/setup-uv@v7, actions/upload-artifact@v4) and
update their tags to specific commit SHAs and add persist-credentials: false
under the checkout steps' with block.
In @.github/workflows/test.yml:
- Around line 27-28: Replace mutable action tags with their corresponding
immutable commit SHAs: locate the three uses: "actions/checkout@v6",
"pyvista/setup-headless-display-action@v3", and "astral-sh/setup-uv@v7" in the
test job and update each to use the full commit SHA (e.g.,
actions/checkout@<full-sha>) fetched from the action's repository (or the
release commit you want to pin). Ensure you replace only the tag portion after
the @ for each uses: entry and verify the workflow still passes by running the
CI; keep a short comment noting the pinned SHA and why it was chosen.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7da4582e-2652-4339-b78b-a7295571178f
📒 Files selected for processing (6)
.github/workflows/installer.yml.github/workflows/test.ymlpyproject.tomlsaenopy/gui/code/syntax.pytest/test_download_example.pytest/test_use_gui.py
✅ Files skipped from review due to trivial changes (2)
- saenopy/gui/code/syntax.py
- test/test_download_example.py
🚧 Files skipped from review as they are similar to previous changes (1)
- pyproject.toml
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/installer.yml:
- Around line 14-17: Replace mutable action tags with immutable commit SHAs for
the referenced steps: pin actions/checkout@v6, astral-sh/setup-uv@v7, and
actions/upload-artifact@v4 to their respective commit SHAs in the
`.github/workflows/installer.yml` workflow; additionally, in each checkout step
that uses actions/checkout (the steps currently using actions/checkout@v6), add
the with: persist-credentials: false setting to the checkout step configuration
to prevent credential propagation. Locate the steps by the exact uses: tokens
(actions/checkout@v6, astral-sh/setup-uv@v7, actions/upload-artifact@v4) and
update their tags to specific commit SHAs and add persist-credentials: false
under the checkout steps' with block.
In @.github/workflows/test.yml:
- Around line 27-28: Replace mutable action tags with their corresponding
immutable commit SHAs: locate the three uses: "actions/checkout@v6",
"pyvista/setup-headless-display-action@v3", and "astral-sh/setup-uv@v7" in the
test job and update each to use the full commit SHA (e.g.,
actions/checkout@<full-sha>) fetched from the action's repository (or the
release commit you want to pin). Ensure you replace only the tag portion after
the @ for each uses: entry and verify the workflow still passes by running the
CI; keep a short comment noting the pinned SHA and why it was chosen.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7da4582e-2652-4339-b78b-a7295571178f
📒 Files selected for processing (6)
.github/workflows/installer.yml.github/workflows/test.ymlpyproject.tomlsaenopy/gui/code/syntax.pytest/test_download_example.pytest/test_use_gui.py
✅ Files skipped from review due to trivial changes (2)
- saenopy/gui/code/syntax.py
- test/test_download_example.py
🚧 Files skipped from review as they are similar to previous changes (1)
- pyproject.toml
🛑 Comments failed to post (2)
.github/workflows/installer.yml (1)
14-17:
⚠️ Potential issue | 🟠 Major | ⚡ Quick win🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Verify all workflow actions are pinned to full commit SHAs rg -nP '^\s*-\s*uses:\s*[^@]+@([0-9a-f]{40}|.+)$' .github/workflows/*.yml -C1 echo "---- Non-SHA pinned uses ----" rg -nP '^\s*-\s*uses:\s*[^@]+@(?!(?:[0-9a-f]{40})$).+' .github/workflows/*.ymlRepository: rgerum/saenopy
Length of output: 2449
🏁 Script executed:
#!/bin/bash set -euo pipefail FILE=".github/workflows/installer.yml" echo "=== installer.yml (head 140 lines with numbers) ===" nl -ba "$FILE" | sed -n '1,160p' echo echo "=== Targeted search for setup-uv and persist-credentials in installer.yml ===" rg -n "setup-uv|persist-credentials|checkout@|upload-artifact@" "$FILE" || trueRepository: rgerum/saenopy
Length of output: 171
🏁 Script executed:
#!/bin/bash set -euo pipefail FILE=".github/workflows/installer.yml" echo "=== installer.yml: lines 1-130 (with numbers) ===" cat -n "$FILE" | sed -n '1,130p' echo echo "=== installer.yml: targeted search ===" rg -n "setup-uv|persist-credentials|uses:\s*actions/checkout@|uses:\s*astral-sh/setup-uv@|upload-artifact@" "$FILE" || trueRepository: rgerum/saenopy
Length of output: 4877
🏁 Script executed:
rg -n "persist-credentials" .github/workflows/installer.yml || trueRepository: rgerum/saenopy
Length of output: 40
Harden GitHub Actions supply chain and reduce checkout credential exposure in release installer workflow.
In
.github/workflows/installer.yml, theseuses:entries are pinned only to mutable tags—pin them to immutable commit SHAs:
actions/checkout@v6(lines 14, 53, 90)astral-sh/setup-uv@v7(lines 17, 56, 93)actions/upload-artifact@v4(lines 37, 75, 115)Also, checkout steps at lines 14/53/90 should set
with: persist-credentials: falseto reduce token/credential exposure to later steps.Suggested pattern
- - uses: actions/checkout@v6 + - uses: actions/checkout@<commit-sha> # v6 + with: + persist-credentials: false - - name: Install uv - uses: astral-sh/setup-uv@v7 + - name: Install uv + uses: astral-sh/setup-uv@<commit-sha> # v7🧰 Tools
🪛 zizmor (1.25.2)
[warning] 14-14: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 14-14: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 17-17: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step
(cache-poisoning)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/installer.yml around lines 14 - 17, Replace mutable action tags with immutable commit SHAs for the referenced steps: pin actions/checkout@v6, astral-sh/setup-uv@v7, and actions/upload-artifact@v4 to their respective commit SHAs in the `.github/workflows/installer.yml` workflow; additionally, in each checkout step that uses actions/checkout (the steps currently using actions/checkout@v6), add the with: persist-credentials: false setting to the checkout step configuration to prevent credential propagation. Locate the steps by the exact uses: tokens (actions/checkout@v6, astral-sh/setup-uv@v7, actions/upload-artifact@v4) and update their tags to specific commit SHAs and add persist-credentials: false under the checkout steps' with block..github/workflows/test.yml (1)
27-28:
⚠️ Potential issue | 🟠 Major | ⚡ Quick win🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Inspect all workflow action refs and list non-SHA pins rg -nP '^\s*-\s*uses:\s*[^@]+@(?!(?:[0-9a-f]{40})$).+' .github/workflows/test.yml -C1Repository: rgerum/saenopy
Length of output: 310
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== .github/workflows/test.yml (numbered) ==" nl -ba .github/workflows/test.yml | sed -n '1,120p' echo echo "== All uses: lines with numbers ==" rg -n '^\s*-\s*uses:' .github/workflows/test.ymlRepository: rgerum/saenopy
Length of output: 163
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== .github/workflows/test.yml (numbered) ==" cat -n .github/workflows/test.yml | sed -n '1,140p' echo echo "== All uses: lines with numbers ==" rg -n '^\s*-\s*uses:' .github/workflows/test.ymlRepository: rgerum/saenopy
Length of output: 1777
Pin GitHub Actions in
testjob to immutable commit SHAs.In
.github/workflows/test.yml, thetestjob uses mutable action tags:
actions/checkout@v6(line 27)pyvista/setup-headless-display-action@v3(line 28)astral-sh/setup-uv@v7(line 33)Pin each
uses:reference to a full commit SHA to prevent tag-retargeting supply-chain risk.Suggested pattern
- - uses: actions/checkout@v6 + - uses: actions/checkout@<commit-sha> # v6 - - uses: pyvista/setup-headless-display-action@v3 + - uses: pyvista/setup-headless-display-action@<commit-sha> # v3 - - name: Install uv - uses: astral-sh/setup-uv@v7 + - name: Install uv + uses: astral-sh/setup-uv@<commit-sha> # v7🧰 Tools
🪛 zizmor (1.25.2)
[warning] 27-27: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 27-27: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 28-28: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/test.yml around lines 27 - 28, Replace mutable action tags with their corresponding immutable commit SHAs: locate the three uses: "actions/checkout@v6", "pyvista/setup-headless-display-action@v3", and "astral-sh/setup-uv@v7" in the test job and update each to use the full commit SHA (e.g., actions/checkout@<full-sha>) fetched from the action's repository (or the release commit you want to pin). Ensure you replace only the tag portion after the @ for each uses: entry and verify the workflow still passes by running the CI; keep a short comment noting the pinned SHA and why it was chosen.
Summary
pyproject.tomlwith project-specific line length, target version, and exclude/ignore rules.ruff checkandruff format --check.Testing
uvx ruff check ..uvx ruff format --check ..Summary by CodeRabbit
Chores
Style / Refactor
Bug Fixes / Stability