Skip to content

except ImportError in specs.py masks a runner import failure — the ju integration has never run in CI #4

Description

@thorwhalen

Summary

uf/specs.py:114 wraps the entire ju spec-generation block in a bare except ImportError: that falls back to _generate_basic_spec. In the GitHub Actions runner environment, something in the ju import chain raises ImportError, so the fallback is what actually runs there.

Consequence: uf's ju integration — the package's entire reason to exist — has never been exercised in CI. CI is green because the fallback path is green.

How this was found

While fixing a real bug in #3 (func_to_form_spec was called without nest_under_field=None, so ju returned {'rjsf': {...}} and form_spec.get('schema', {}) yielded {} — every generated RJSF form was empty, served to the browser from uf/routes.py:123 and :189).

That bug reproduced locally (2 failing tests) but CI was green throughout — which is only possible if CI never took the ju path. The except ImportError was left deliberately untouched in #3, because narrowing it would have surfaced the hidden failure as a CI failure in a PR that was about something else.

What is not known

Which import fails on the runner. The following were ruled out during investigation and are not the cause: ipywidgets, graphviz (lazily imported), every name in ju/__init__ checked against the 0.1.31 wheel, dol 0.3.44, i2 0.1.63. An isolated-venv reproduction attempt failed for unrelated permission reasons.

Suggested approach

Narrowing the except clause is not the right first move — it converts a silent problem into a red CI with no diagnosis.

Better:

  1. Add a no-fallback smoke test (or a strict=True flag on the spec store) that imports ju and generates one spec with no except guard, and let it fail loudly in CI. The traceback will name the failing import — which is the missing piece.
  2. With that known, fix the actual dependency problem (likely an undeclared or under-pinned transitive dep) and then narrow the except to the specific import it was meant to guard, if it is still needed at all.
  3. Consider whether the fallback should exist at all in a package whose purpose is the ju integration. If ju is not importable, silently serving degraded empty-ish forms is arguably worse than failing.

Reproduce

# locally the ju path IS taken — tests pass post-#3
python -m pytest -q          # 29 passed

# in CI the fallback is taken; prove it by asserting the ju path
python -c "from ju.rjsf import func_to_form_spec; print('ju import OK')"

Add the equivalent assertion to CI to see it fail there.

Surfaced during the 2026-07-30 wave-0 rollout batch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions