Skip to content

fix(plugins): isolate malformed plugins and proposals, warn on duplicate names, honour late network opt-in - #379

Merged
kevincostner17 merged 1 commit into
mainfrom
fix/plugin-isolation
Sep 15, 2026
Merged

kevincostner17 merged 1 commit into
mainfrom
fix/plugin-isolation

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

Fixes three plugin-registry bugs in src/freshdata/plugins.py.

  • Plugin failures escape isolation: bad proposals or a malformed entry point break the run #297 Plugin failures escaping isolation
    • Proposals for a column the plugin was not given (an expert's own column, or a backend's frame) are dropped and logged once per plugin, instead of raising KeyError in apply.
    • The provenance merge is guarded, so a malformed proposal is dropped with a log message.
    • _max_risk falls back to the documented "high" default for non-string or unhashable values.
    • Each entry point is loaded and registered inside its own try, so one malformed entry point is skipped and the rest of its group still registers.
    • Plugins raising in propose() stay isolated.
  • Duplicate plugin names are replaced silently; docs promise a warning #298 Duplicate plugin names: reusing a name within a kind logs a WARNING naming the replaced and replacing plugin. The last registration still wins, as documented. Re-registering the same object is silent.
  • FRESHDATA_ALLOW_NETWORK_PLUGINS set after registration never activates the plugin #299 Late network opt-in: network plugins registered without allow_network=True are marked network_blocked. Every registry accessor re-reads FRESHDATA_ALLOW_NETWORK_PLUGINS at call time, so setting the variable after registration activates the plugin. Without it, network plugins stay inactive (offline by default is unchanged). A missing dependency still keeps a plugin inactive.

docs/plugins.md now covers dropped off-target or malformed proposals, entry-point skipping, the duplicate-name warning, and when the environment variable is read.

Behaviour changes

  • Off-target or malformed proposals are dropped with a log message instead of crashing the clean.
  • Unsetting FRESHDATA_ALLOW_NETWORK_PLUGINS later also turns an env-enabled network plugin off again; plugins registered with allow_network=True are unaffected.
  • Reusing a plugin name within one kind logs a WARNING (via logging, as the module already does).

Tests

New tests/test_plugin_isolation.py:

  • Each issue's reproduction.
  • Wrong-column and malformed-provenance proposals are dropped and the clean completes; the wrong-column warning is logged once across columns and runs.
  • Backend off-frame proposals are dropped.
  • A raising propose() stays isolated.
  • Unhashable max_risk falls back to "high".
  • A malformed entry point is skipped while the next one registers.
  • The duplicate-name warning is checked with caplog, including the same-object and cross-kind cases.
  • Setting the variable late activates a plugin (experts, backends, entry points); network plugins stay inactive without it, or with values other than 1; unsetting it deactivates env-gated plugins; allow_network=True does not depend on the variable; missing dependencies still win.

Verification

  • ruff check .: passed. mypy src/freshdata: no issues.
  • pytest -m "not online and not large":
    • py3.12: 4268 passed, 6 skipped
    • py3.9: 4264 passed, 10 skipped
  • Plugin contract job steps (tests/test_plugins.py and example plugin contracts): passed.

Closes #297
Closes #298
Closes #299

…ate names, honour late network opt-in

Plugin failures escaping isolation (#297):
- _cap_and_stamp drops proposals for a column the plugin was not given
  (an expert's own column; a backend's frame columns) instead of letting
  them reach the executor, where they raised KeyError. Logged once per
  plugin so a confused expert does not warn for every column.
- The provenance merge and dataclasses.replace are guarded: a proposal
  with non-mapping provenance is logged and dropped rather than raising.
  A non-string risk is ranked as "high", as unknown risks already were.
- _max_risk requires a string, so unhashable metadata such as
  max_risk=["low"] falls back to the documented "high" default instead
  of raising TypeError.
- _ensure_entry_points registers each entry point inside its own try.
  Previously a _register failure escaped after _entry_points_loaded was
  set, so every later entry point in the group was never registered.
- A plugin raising inside propose() stays isolated (unchanged).

Duplicate plugin names (#298):
- _register logs a WARNING naming the replaced and replacing plugin when
  a name is reused within a kind. Last registration still wins, as
  docs/plugins.md describes; re-registering the same object is silent.

Late FRESHDATA_ALLOW_NETWORK_PLUGINS opt-in (#299):
- A network-using plugin registered without allow_network=True is stored
  with network_blocked=True. Every registry accessor re-reads the
  environment variable, so setting it after registration activates the
  plugin and unsetting it deactivates it again. allow_network=True stays
  unconditional, missing dependencies still win, and without the
  variable network plugins remain inactive (offline by default).

Tests in tests/test_plugin_isolation.py cover each issue's reproduction;
docs/plugins.md describes the dropped proposals, entry-point skipping,
the duplicate-name warning and when the variable is read.

Closes #297
Closes #298
Closes #299
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: dbff5c0e-4ace-4690-822a-09877fcff907


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

FreshData benchmark report — performance

  • freshdata: ?
  • python: ?
  • platform: ?
fixture n_rows n_cols p50 s p95 s peak MB repair % false-repair % preserve % trust monotonic export %

Authored-code reduction (Metric 6)

@kevincostner17
kevincostner17 merged commit ecb0c78 into main Sep 15, 2026
21 checks passed
kevincostner17 added a commit that referenced this pull request Sep 15, 2026
Add Unreleased Added, Changed and Fixed entries for merged PRs #351,
#352, #353, #354, #356, #357, #358, #360, #361, #362, #363, #364, #365,
#367, #368, #369, #370, #371, #372, #374, #375, #377, #378 and #379.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant