Skip to content

fix(update_pyproject_dependencies): handle '- pip:' dict deps in lower-bound yaml#174

Open
ligerzero-ai wants to merge 1 commit into
mainfrom
fix-load-yaml-pip-dict
Open

fix(update_pyproject_dependencies): handle '- pip:' dict deps in lower-bound yaml#174
ligerzero-ai wants to merge 1 commit into
mainfrom
fix-load-yaml-pip-dict

Conversation

@ligerzero-ai
Copy link
Copy Markdown

Problem

`load_yaml` in `.support/update_pyproject_dependencies.py` iterates the yaml's `dependencies` list and calls `split_dependency` on every entry. `split_dependency` does `dependency.replace(" ", "").split("=")` — which crashes when an entry is a dict instead of a string:

```
File "...update_pyproject_dependencies.py", line 126, in split_dependency
split = dependency.replace(" ", "").split(delimiter)
AttributeError: 'dict' object has no attribute 'replace'
```

This happens for any consumer with a `- pip:` block in its conda env yaml (PyPI-only deps installed via conda's pip backend) — for example:

```yaml
channels:

  • conda-forge
    dependencies:
  • python =3.10
  • numpy =1.22.0
    ...
  • pip
  • pip:
    • lz-GB-code==0.1.0
      ```

The current code parses the conda entries fine but explodes on the `- pip:` dict entry, blocking the release workflow.

Affected repos I know about:

  • `pyiron/pyiron_workflow_lammps` — already carries a local fork of this file in `main` (with the same fix, slightly less polished).
  • `pyiron/pyiron_workflow_atomistics` — hit it on the v0.0.5 release attempt I just made; I've forked the script locally there too while waiting for this upstream fix.

Fix

Detect dict-typed entries; if the key is `pip`, descend into the value list and parse `==`-pinned entries via the same `split_dependency` helper. Any other dict key raises a clear `ValueError` naming the yaml file and the offending entry.

Diff (single function, no other touches):

```diff
yaml_bounds = {}
for dependency in data["dependencies"]:

  •    package, version = split_dependency(dependency, "=", allow_no_version=True)
    
  •    yaml_bounds[package] = version
    
  •    if isinstance(dependency, dict):
    
  •        # A YAML \`- pip:\` block (PyPI-only deps installed by conda's
    
  •        # pip backend) parses as a dict; descend into its list and
    
  •        # capture the \`==\`-pinned entries. Anything without \`==\` is
    
  •        # ignored — \`load_yaml\` only collects exact version bounds.
    
  •        if "pip" in dependency:
    
  •            for pip_dependency in dependency["pip"]:
    
  •                if "==" in pip_dependency:
    
  •                    package, version = split_dependency(
    
  •                        pip_dependency, "==", allow_no_version=True
    
  •                    )
    
  •                    yaml_bounds[package] = version
    
  •        else:
    
  •            raise ValueError(
    
  •                f"Unsupported dict-typed dependency entry in {yaml_file!r}: "
    
  •                f"{dependency!r}. Only \`pip:\` sub-lists are recognised."
    
  •            )
    
  •        continue
    
  •    package, version = split_dependency(dependency, "=", allow_no_version=True)
    
  •    yaml_bounds[package] = version
    
    return yaml_bounds
    ```

Behavioural notes:

  • For `pip:` entries, the parser uses `==` as the split delimiter (PyPI pinning syntax) vs `=` for conda entries.
  • Non-`==` PyPI entries (e.g. `>=`, unconstrained) are silently skipped — consistent with the existing `allow_no_version=True` semantics for conda entries that have no `=`.
  • Non-`pip` dict entries raise, surfacing unexpected yaml shapes loudly.

Notes vs. the lammps repo fork

The fork in `pyiron/pyiron_workflow_lammps@main:.github/actions/.support/update_pyproject_dependencies.py` carries the same logical fix but uses `type(dependency) == dict` and `"pip" in dependency.keys()`. I changed those to `isinstance(...)` / `"pip" in dependency` (ruff E721 / SIM118), and dropped two stray `print()` calls that look like leftover debug output. No semantic difference.

Test plan

Local smoke test against `pyiron_workflow_atomistics/.ci_support/lower_bound.yml`:

```
$ python -c "
import sys; sys.path.insert(0, '.support')
import update_pyproject_dependencies as m
bounds = m.load_yaml('.../lower_bound.yml')
print(bounds.get('lz-GB-code')) # 0.1.0 — captured from the pip: block
print(bounds.get('numpy')) # 1.22.0 — captured from the conda block
"
```

→ OK; 16 bounds parsed; `lz-GB-code` correctly picked up from the `- pip:` block.

Negative-case smoke (synthetic yaml with a non-`pip` dict entry):

```
$ python -c "import update_pyproject_dependencies as m; m.load_yaml('/tmp/bad.yml')"
ValueError: Unsupported dict-typed dependency entry in '/tmp/bad.yml': {'foobar': ['x']}. Only `pip:` sub-lists are recognised.
```

→ OK; raises clearly.

After this lands

I'll undo the local fork in `pyiron_workflow_atomistics` (PR pyiron/pyiron_workflow_atomistics#35) by reverting back to `uses: pyiron/actions/.github/workflows/pyproject-release.yml@`. Same option opens up for lammps.

🤖 Generated with Claude Code

`load_yaml` iterates `data["dependencies"]` and calls
`split_dependency(dependency, "=", allow_no_version=True)` on each
entry. When a yaml conda env contains a `- pip:` block (PyPI-only
deps installed by conda's pip backend) the entry is a dict
(`{"pip": ["pkg==X.Y.Z", ...]}`) and `split_dependency`'s
`dependency.replace(" ", "")` raises

    AttributeError: 'dict' object has no attribute 'replace'

This blocks the release workflow for any consumer that has
PyPI-only dependencies — for example, pyiron_workflow_lammps
(already carrying a local fork of this file in main) and
pyiron_workflow_atomistics (which depends on `lz-GB-code` via
pip on the v0.0.5 release we just attempted).

Patched `load_yaml` to detect a dict entry, look for a `pip:` key,
descend into the list, and parse the `==`-pinned entries via the
existing `split_dependency` helper. Non-pip dict entries raise a
clear ValueError pointing at the offending yaml file and entry.

The lammps fix uses `type(dependency) == dict` and
`"pip" in dependency.keys()`; cleaned up to `isinstance` and
plain `in` to satisfy ruff E721/SIM118 if anyone adds linting
to this script later.

Tested locally against pyiron_workflow_atomistics'
`.ci_support/lower_bound.yml` (16 bounds parsed correctly,
including `lz-GB-code==0.1.0` from the `- pip:` block) and a
synthetic yaml containing `[{"foobar": ["x"]}]` (raises the
new ValueError).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ligerzero-ai added a commit to pyiron/pyiron_workflow_atomistics that referenced this pull request May 12, 2026
Mirrors the existing pyiron/pyiron_workflow_lammps@main fix: forks
.support/update_pyproject_dependencies.py and pyproject-release.yml
locally with a dict-aware load_yaml() that descends into '- pip:'
blocks. Unblocks the v0.0.5 PyPI release workflow, which crashed on
the existing 'lz-GB-code==0.1.0' pip dependency in lower_bound.yml.

Once pyiron/actions#174 lands and a new actions tag is cut, the
local fork can be reverted in a follow-up.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
ligerzero-ai pushed a commit to pyiron/pyiron_workflow_vasp that referenced this pull request May 12, 2026
push-pull.yml wires to pyiron/actions@actions-4.0.8. release.yml uses
a local fork of pyproject-release.yml + update_pyproject_dependencies.py
that handles '- pip:' dict deps in lower-bound.yml (the upstream script
crashes on this; fix proposed at pyiron/actions#174). Copied verbatim
from pyiron_workflow_atomistics post-#35.

.ci_support/environment.yml + lower-bound.yml pin the same versions
as atomistics 0.0.5 so pip-check + the release-bounds script align.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@liamhuber
Copy link
Copy Markdown
Member

The fork in pyiron/pyiron_workflow_lammps@main:.github/actions/.support/update_pyproject_dependencies.py carries the same logical fix but uses type(dependency) == dict and "pip" in dependency.keys(). I changed those to isinstance(...) / "pip" in dependency (ruff E721 / SIM118), and dropped two stray print() calls that look like leftover debug output. No semantic difference.

If I understand, the changes here are chronologically behind and not as well linted. Could you propagate those improvements here?

@ligerzero-ai, I'm not familiar enough with everything we do in this repo to just read our code and then be like "yes, this will work", so what I usually do is point some PR that needs the change I'm making at the branch here and just make sure it works. It sounds like you basically already went through that process downstream with your forking, but that post is also AI so I just want some clarification that this has actually happened and succeeded.

Finally, the release process on this repo is not used terribly often and not very friendly. From here, you'll want to:

  • Check what the current release is
  • Rename your branch to be actions-X.Y.Z where you've bumped on whatever is currently released
    • this sounds like a straight bugfix to me, so I recommend bumping Z
  • Run the renaming script .support/update_actions_tag.sh (this will sync all the internal invocations of the repo with your branch name)
  • Once it's merged, immediately make a release of the same name, i.e. actions-X.Y.Z++

Then whatever test repo you were pointing at the branch just keeps seamlessly working because now it's pointed at a release tag.

@ligerzero-ai
Copy link
Copy Markdown
Author

@liamhuber yes, I had meant to write to you about this earlier (last year) but I had forgotten it.

We had discussed the prospect of supporting the C.I. actions with pip-only packages, and this was the solution that I came up with at that time (I think October(?)) This is simply the AI summary of the fix.

THe problem is that the current actions parses a dictionary that doesn't allow for the extra block below with pip-only installs in the environment.yml or something like that. I added functionality to parse the dictionary. I told the AI to look for it and propagate it back upstream, which is why this post exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants