Skip to content

Misc/add deprecation shims - #468

Merged
peterrrock2 merged 11 commits into
1.0.0from
misc/add-deprecation-shims
Jul 31, 2026
Merged

Misc/add deprecation shims#468
peterrrock2 merged 11 commits into
1.0.0from
misc/add-deprecation-shims

Conversation

@peterrrock2

Copy link
Copy Markdown
Collaborator

Summary

This PR restores compatibility for many GerryChain 0.3.2 call patterns whose names or callback signatures changed during the 1.0 work. Legacy calls continue to run with actionable DeprecationWarning messages, while the canonical 1.0 signatures remain unchanged.

The warnings identify the replacement API and state that the compatibility path will be removed in GerryChain 2.0.

Not all pre-1.0 behavior is restored, but I made sure that workflows documented in readthedocs were covered.

What changed

  • Added shared compatibility helpers in gerrychain._deprecated for the following:
    • renamed and ignored parameters;
    • legacy defaults;
    • renamed public functions and properties;
    • moved functions that must be resolved lazily;
    • callbacks that do not yet accept the keyword-only rng argument; and
    • legacy tree callback parameter names and argument ordering.
  • Restored renamed parameters across partitions, updaters, constraints, optimizers, proposals, tree algorithms, and assignment helpers.
  • Kept removed parameters out of the canonical signatures. Calls using those parameters are handled by wrappers and receive a warning.
  • Adapted pre-1.0 proposal, acceptance, bipartition, balanced-cut, spanning-tree, and cut-choice callbacks at their shared call sites.
  • Restored moved or renamed public entry points in gerrychain.tree, gerrychain.grid, gerrychain.updaters, and gerrychain.constraints.
  • Reverted to L1 as the canonical spelling for the compactness helpers (in hindsight L_1 was confusing when the proper symbol is $L^1$ and not $L_1$)
  • Restored deprecated attribute access for:
    • MarkovChain.proposal, .accept, .initial_state, and .is_valid; and
    • .func on all Bounds classes.

Compatibility behavior

  • Supplying a legacy and canonical parameter name together raises a clear TypeError.
  • Legacy callbacks are inspected and adapted once before use. Exceptions raised inside a callback are not caught and retried.
  • Canonical 1.0 calls do not emit deprecation warnings.
  • Deprecated calls identify the old name, its replacement, and the planned 2.0 removal.

Tests

  • Added focused tests for renamed parameters, aliases, ignored arguments, callback adaptation, warning messages, canonical signatures, and legacy read/write attributes.
  • Full test suite: 533 passed, 9 skipped, 2 xfailed.

Exclusions

This PR did not restore:

  • the pre-1.0 Graph API or other behavior made obsolete by the RustworkX migration;
  • the old ReCom interface;
  • old positional layouts where parameters were reordered or inserted; or
  • global random.seed(...) control over the new explicit RNG streams;

Reviewer Notes

  • Thorough review not required. A quick sanity check to make sure I haven't missed anything substantial is appreciated.

@chief-dweeb chief-dweeb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great - especially for a major release.

It also makes the change-log less important, as users will discover what has changed in a very natural and convenient way.

I do wonder, however, how you identified all of the changes that would need to be dealt with. For my change-log, I used the ast module to go over both codebases and then compare the two in code. What did you do?

Comment thread gerrychain/_deprecated.py
Comment on lines +33 to +111
def deprecated_parameters(
renamed: Mapping[str, str] | None = None,
ignored: Mapping[str, str] | None = None,
defaults: Mapping[str, Any] | None = None,
) -> Callable[[Callable[P, R]], Callable[P, R]]:
"""Translate legacy keywords before calling a function with its canonical signature.

Renamed arguments are translated to their canonical names, ignored arguments are removed, and
legacy defaults are supplied when their canonical argument is absent. Each compatibility
action emits a :class:`DeprecationWarning`. The decorated callable retains the wrapped
function's metadata and canonical signature.

Args:
renamed (Mapping[str, str] | None, optional): Mapping from legacy keyword names to
canonical keyword names.
ignored (Mapping[str, str] | None, optional): Mapping from removed keyword names to the
explanation appended to their warning. Supplied values are discarded.
defaults (Mapping[str, Any] | None, optional): Mapping from canonical keyword names to
legacy default values. A default is supplied only when the argument was not otherwise
bound.

Returns:
Callable[[Callable[P, R]], Callable[P, R]]: A decorator that adds the requested legacy
argument handling.

Raises:
TypeError: When a caller supplies both a legacy keyword and its canonical replacement.
"""
renamed = {} if renamed is None else renamed
ignored = {} if ignored is None else ignored
defaults = {} if defaults is None else defaults

def decorate(fn: Callable[P, R]) -> Callable[P, R]:
signature = inspect.signature(fn)
fn_name = getattr(fn, "__qualname__", type(fn).__qualname__)

@functools.wraps(fn)
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
mutable_kwargs = cast(dict[str, Any], kwargs)
for old_name, new_name in renamed.items():
if old_name not in mutable_kwargs:
continue
if new_name in mutable_kwargs:
raise TypeError(
f"{fn_name} received both {old_name!r} and {new_name!r}; "
f"use only {new_name!r}."
)
mutable_kwargs[new_name] = mutable_kwargs.pop(old_name)
_warn(
f"{fn_name}(..., {old_name}=...) is deprecated; use "
f"{new_name}=... instead. The legacy name will be removed in GerryChain 2.0."
)

for name, reason in ignored.items():
if name not in mutable_kwargs:
continue
mutable_kwargs.pop(name)
_warn(
f"{fn_name}(..., {name}=...) is deprecated and ignored. {reason} "
"The argument will be rejected in GerryChain 2.0."
)

if defaults:
bound = signature.bind_partial(*args, **mutable_kwargs)
for name, value in defaults.items():
if name in bound.arguments:
continue
mutable_kwargs[name] = value
_warn(
f"{fn_name}() omitted {name!r}; using the legacy default {value!r}. "
f"Pass {name}=... explicitly. The implicit default will be removed in "
"GerryChain 2.0."
)

return fn(*args, **mutable_kwargs)

return wrapped

return decorate

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is magic.

Kudos!

@peterrrock2

Copy link
Copy Markdown
Collaborator Author

This is great - especially for a major release.

It also makes the change-log less important, as users will discover what has changed in a very natural and convenient way.

I do wonder, however, how you identified all of the changes that would need to be dealt with. For my change-log, I used the ast module to go over both codebases and then compare the two in code. What did you do?

Thank you! I used the super scientific "run the old tutorial guide and some old scripts and put a shim around anything that breaks" method to find all of these. I figure that I have used more features of GerryChain than most, so anything I bumped into was worth shimming.

@peterrrock2
peterrrock2 merged commit 56eacf4 into 1.0.0 Jul 31, 2026
1 check passed
@peterrrock2
peterrrock2 deleted the misc/add-deprecation-shims branch July 31, 2026 00:28
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