You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Issue #886 / PR #894 fixed the discoverability of *args, **kwargs for one specific case — the plot() method on every BaseExperiment subclass. The same problem applies to several other public methods across the package: bare or trailing *args, **kwargs hide real, supported parameters from Sphinx, IDE autocomplete, inspect.signature, and help(), and silently accept (or swallow) misspelled keyword arguments at runtime.
This umbrella issue extends the work to the rest of the public surface.
Reference implementation
The pattern established in PR #894 is the template:
Where the offending method is a dispatcher or abstract stub on BaseExperiment (the four-method shape: public dispatcher → _bayesian_* / _ols_* private hook), remove the public method from the base, rename its body to a protected helper (e.g. _render_plot), and force every concrete subclass to declare its own explicit, kwarg-only override that delegates via self._render_xxx(...). This converts the contract from a convention into a structural invariant — a future subclass that forgets to declare the method has no public method at all, surfacing the omission immediately.
Where the offending method already has an explicit signature with a trailing **kwargs, audit whether the kwargs are real (forwarded somewhere, used conditionally) or vestigial. If real, lift the keys to explicit named parameters or document them under Other Parameters; if not, delete the trailing **kwargs.
Add the new method(s) to the regex enforced by the numpydoc-validation pre-commit hook (currently scoped to \.plot$). The simplest evolution is to broaden it to a list of public-method patterns, or to invert the check (every public method should be PR01/PR02-clean).
Extend causalpy/tests/test_public_plot_signatures.py (or sibling tests) so the structural invariants apply to the new method names.
Update AGENTS.md to broaden the public-method discipline beyond plot().
Confirmed scope (from a static survey on 2026-05-01)
The following 25 public methods/functions in the package have *args and/or **kwargs in their signature today. Categories below; treat the inventory as a starting point — the survey itself should be re-run as part of completing this work since it likely undercounts non-class-method public surfaces.
A. Bare-dispatcher / bare-stub pattern (direct #886 analogue)
These four are exactly the same shape as the old BaseExperiment.plot. The _render_plot refactor from PR #894 should generalise mechanically.
Method
File
Notes
BaseExperiment.fit(*args, **kwargs)
experiments/base.py:138
Abstract; every subclass overrides. Surface with explicit kwargs at base or remove from base.
causalpy.utils.plot_correlations(data, columns=None, method='pearson', **kwargs) — utils.py. Has explicit named kwargs followed by **kwargs; audit the forwarding.
E. Modules not yet surveyed (sub-task)
The script in scope walked classes only via walk(BaseExperiment) plus top-level functions, so the following may have additional cases worth checking:
causalpy.data (data loaders / generators)
causalpy.checks (diagnostic checks)
causalpy.pipeline
causalpy.steps.*
causalpy.reporting
causalpy.plot_utils
causalpy.transforms
causalpy.skl_models
causalpy.maketables_adapters
A first sub-task should be to extend the static survey to cover all causalpy.* modules (skipping causalpy.tests) and append the findings to this issue before any code changes start.
Proposed approach
Sub-task 1: Re-run / extend the survey. Refresh the inventory above with the modules listed in section E and post the updated table here. (The script that produced the original inventory was kept in scratch space during PR Replace plot() *args/**kwargs with explicit signatures (#886) #894; harden into tools/audit_public_signatures.py if it'll see repeat use.)
Sub-task 2: Group A — apply the Replace plot() *args/**kwargs with explicit signatures (#886) #894_render_plot pattern to fit, get_plot_data, get_plot_data_bayesian, get_plot_data_ols. Most likely a single PR. Forces every concrete subclass to declare its own explicit signature; removes the public method from BaseExperiment. Probably the largest mechanical change.
Sub-task 3: Group B audit — trailing **kwargs. For each method, decide one of: (a) drop the kwargs entirely, (b) document them under Other Parameters, or (c) lift the actual keys used to explicit named parameters. Likely splits into one PR per method family (effect_summary, predict, score).
Sub-task 4: Group C — clean up dead **kwargs on PanelRegression.get_plot_data_*. Trivial.
Sub-task 5: Group D / E — module-level public functions. Probably one PR per module.
Sub-task 6: Broaden enforcement. Extend the numpydoc-validation regex (in pyproject.toml [tool.numpydoc_validation]) and causalpy/tests/test_public_plot_signatures.py so the no-*args/**kwargs invariant covers the methods above. Update AGENTS.md to drop the "plot() only" framing and require the discipline for all public methods.
Acceptance criteria
Updated survey covering all causalpy.* modules posted as a comment on this issue.
No public method or module-level function in causalpy/ declares *args or **kwargs at its signature, with the exception of explicitly documented forwarders that list the accepted keys under Other Parameters (and can justify why explicit naming is impractical).
numpydoc-validation pre-commit hook scope broadened beyond \.plot$ so the invariant is enforced for every method touched.
causalpy/tests/test_public_plot_signatures.py (or a renamed/expanded version) extended to assert the no-*args/**kwargs invariant for the new methods.
AGENTS.md updated to describe the broader convention.
Sphinx API pages render explicit signatures and parameter lists for every method touched (eyeball check on the RTD preview).
Motivation
Issue #886 / PR #894 fixed the discoverability of
*args, **kwargsfor one specific case — theplot()method on everyBaseExperimentsubclass. The same problem applies to several other public methods across the package: bare or trailing*args, **kwargshide real, supported parameters from Sphinx, IDE autocomplete,inspect.signature, andhelp(), and silently accept (or swallow) misspelled keyword arguments at runtime.This umbrella issue extends the work to the rest of the public surface.
Reference implementation
The pattern established in PR #894 is the template:
BaseExperiment(the four-method shape: public dispatcher →_bayesian_*/_ols_*private hook), remove the public method from the base, rename its body to a protected helper (e.g._render_plot), and force every concrete subclass to declare its own explicit, kwarg-only override that delegates viaself._render_xxx(...). This converts the contract from a convention into a structural invariant — a future subclass that forgets to declare the method has no public method at all, surfacing the omission immediately.**kwargs, audit whether the kwargs are real (forwarded somewhere, used conditionally) or vestigial. If real, lift the keys to explicit named parameters or document them underOther Parameters; if not, delete the trailing**kwargs.numpydoc-validationpre-commit hook (currently scoped to\.plot$). The simplest evolution is to broaden it to a list of public-method patterns, or to invert the check (every public method should be PR01/PR02-clean).causalpy/tests/test_public_plot_signatures.py(or sibling tests) so the structural invariants apply to the new method names.AGENTS.mdto broaden the public-method discipline beyondplot().Confirmed scope (from a static survey on 2026-05-01)
The following 25 public methods/functions in the package have
*argsand/or**kwargsin their signature today. Categories below; treat the inventory as a starting point — the survey itself should be re-run as part of completing this work since it likely undercounts non-class-method public surfaces.A. Bare-dispatcher / bare-stub pattern (direct #886 analogue)
These four are exactly the same shape as the old
BaseExperiment.plot. The_render_plotrefactor from PR #894 should generalise mechanically.BaseExperiment.fit(*args, **kwargs)experiments/base.py:138BaseExperiment.get_plot_data(*args, **kwargs)experiments/base.py:306get_plot_data_bayesian/get_plot_data_ols.BaseExperiment.get_plot_data_bayesian(*args, **kwargs)experiments/base.py:319NotImplementedError.BaseExperiment.get_plot_data_ols(*args, **kwargs)experiments/base.py:323NotImplementedError.B. Explicit signature with a trailing
**kwargsThese already document most parameters, but the trailing
**kwargs: Anyis undocumented and lets typos through silently. Need a per-method audit.BaseExperiment.effect_summary(..., **kwargs)—experiments/base.py:327DifferenceInDifferences.effect_summary(..., **kwargs)—experiments/diff_in_diff.py:655InstrumentalVariable.effect_summary(..., **kwargs)—experiments/instrumental_variable.py:299InterruptedTimeSeries.effect_summary(..., **kwargs)—experiments/interrupted_time_series.py:1278InversePropensityWeighting.effect_summary(..., **kwargs)—experiments/inverse_propensity_weighting.py:849PanelRegression.effect_summary(..., **kwargs)—experiments/panel_regression.py:447PiecewiseITS.effect_summary(..., **kwargs)—experiments/piecewise_its.py:802PrePostNEGD.effect_summary(..., **kwargs)—experiments/prepostnegd.py:369RegressionDiscontinuity.effect_summary(..., **kwargs)—experiments/regression_discontinuity.py:530RegressionKink.effect_summary(..., **kwargs)—experiments/regression_kink.py:373StaggeredDifferenceInDifferences.effect_summary(..., **kwargs)—experiments/staggered_did.py:1022SyntheticControl.effect_summary(..., **kwargs)—experiments/synthetic_control.py:868PyMCModel.predict(X, coords=None, out_of_sample=False, **kwargs)—pymc_models.pyPyMCModel.score(X, y, coords=None, **kwargs)—pymc_models.pyBayesianBasisExpansionTimeSeries.predict(..., **kwargs)—pymc_models.pyBayesianBasisExpansionTimeSeries.score(..., **kwargs)—pymc_models.pyStateSpaceTimeSeries.predict(..., **kwargs)—pymc_models.pyStateSpaceTimeSeries.score(..., **kwargs)—pymc_models.pyC. Bare-
**kwargsstubs that already have no real parametersPanelRegression.get_plot_data_bayesian(self, **kwargs)—experiments/panel_regression.py:603. The**kwargslooks dead; the body uses none of it.PanelRegression.get_plot_data_ols(self, **kwargs)—experiments/panel_regression.py:635. Same.D. Module-level public functions
causalpy.utils.plot_correlations(data, columns=None, method='pearson', **kwargs)—utils.py. Has explicit named kwargs followed by**kwargs; audit the forwarding.E. Modules not yet surveyed (sub-task)
The script in scope walked classes only via
walk(BaseExperiment)plus top-level functions, so the following may have additional cases worth checking:causalpy.data(data loaders / generators)causalpy.checks(diagnostic checks)causalpy.pipelinecausalpy.steps.*causalpy.reportingcausalpy.plot_utilscausalpy.transformscausalpy.skl_modelscausalpy.maketables_adaptersA first sub-task should be to extend the static survey to cover all
causalpy.*modules (skippingcausalpy.tests) and append the findings to this issue before any code changes start.Proposed approach
tools/audit_public_signatures.pyif it'll see repeat use.)_render_plotpattern tofit,get_plot_data,get_plot_data_bayesian,get_plot_data_ols. Most likely a single PR. Forces every concrete subclass to declare its own explicit signature; removes the public method fromBaseExperiment. Probably the largest mechanical change.**kwargs. For each method, decide one of: (a) drop the kwargs entirely, (b) document them underOther Parameters, or (c) lift the actual keys used to explicit named parameters. Likely splits into one PR per method family (effect_summary,predict,score).**kwargsonPanelRegression.get_plot_data_*. Trivial.numpydoc-validationregex (inpyproject.toml [tool.numpydoc_validation]) andcausalpy/tests/test_public_plot_signatures.pyso the no-*args/**kwargsinvariant covers the methods above. UpdateAGENTS.mdto drop the "plot()only" framing and require the discipline for all public methods.Acceptance criteria
causalpy.*modules posted as a comment on this issue.causalpy/declares*argsor**kwargsat its signature, with the exception of explicitly documented forwarders that list the accepted keys underOther Parameters(and can justify why explicit naming is impractical).numpydoc-validationpre-commit hook scope broadened beyond\.plot$so the invariant is enforced for every method touched.causalpy/tests/test_public_plot_signatures.py(or a renamed/expanded version) extended to assert the no-*args/**kwargsinvariant for the new methods.AGENTS.mdupdated to describe the broader convention.prek run --all-filespasses.pytestpasses.References
plot()-only)_render_plotrefactor +numpydoc-validationhook +test_public_plot_signatures.pyinvariant)