Misc/add deprecation shims - #468
Conversation
chief-dweeb
left a comment
There was a problem hiding this comment.
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?
| 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 |
There was a problem hiding this comment.
This is magic.
Kudos!
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. |
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
DeprecationWarningmessages, 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
gerrychain._deprecatedfor the following:rngargument; andgerrychain.tree,gerrychain.grid,gerrychain.updaters, andgerrychain.constraints.L1as the canonical spelling for the compactness helpers (in hindsightL_1was confusing when the proper symbol isMarkovChain.proposal,.accept,.initial_state, and.is_valid; and.funcon all Bounds classes.Compatibility behavior
TypeError.Tests
533 passed, 9 skipped, 2 xfailed.Exclusions
This PR did not restore:
GraphAPI or other behavior made obsolete by the RustworkX migration;ReCominterface;random.seed(...)control over the new explicit RNG streams;Reviewer Notes