Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changes/4051.doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Added a deprecation policy to the contributing guide. It defines when a
deprecation cycle is required for public API changes, a shared framework for
deciding whether a removal is worthwhile, the mechanics a deprecation must
follow (warning class, message contents, documentation, and changelog entry),
and a minimum deprecation period of at least 6 months and at least one minor
release before removal.
46 changes: 46 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,52 @@ The Zarr library is an implementation of a file format standard defined external

If an existing Zarr format version changes, or a new version of the Zarr format is released, then the Zarr library will generally require changes. It is very likely that a new Zarr format will require extensive breaking changes to the Zarr library, and so support for a new Zarr format in the Zarr library will almost certainly come in a new `major` release. When the Zarr library adds support for a new Zarr format, there may be a period of accelerated changes as developers refine newly added APIs and deprecate old APIs. In such a transitional phase breaking changes may be more frequent than usual.

### Deprecation policy

Our versioning policy (above) commits us to minimizing the effort users spend upgrading. A deprecation cycle is the main tool we use to honor that commitment. Rather than removing or changing public behavior abruptly, we first ship a release that keeps the old behavior working while warning that it is going away, and only remove it in a later release. This section defines when a deprecation cycle is required, how to decide whether a removal is worth it, and the concrete steps a deprecation must follow.

This policy governs the user-facing Python API of `zarr-python`: importable names, function and method signatures, the exceptions raised, and other observable runtime behavior. It does not govern the Zarr data format, whose compatibility is described under [Data format compatibility](#data-format-compatibility) above, nor the `zarr.experimental` namespace, which is governed by the [Experimental API policy](#experimental-api-policy) below. Private API (names prefixed with `_`, and anything not documented as public) may change at any time without a deprecation cycle.

Any backwards-incompatible change to public API, including removing a name, changing a signature in a non-additive way, or changing observable behavior, requires a deprecation cycle unless it qualifies for one of the [exceptions](#exceptions) listed below.

#### Deciding whether to deprecate

A deprecation cycle has a real cost. Every user of the affected API must eventually migrate and we carry the deprecated code and its warning until removal. Before starting one, weigh the costs and benefits and record your reasoning in the pull request, so that proposals are evaluated on a shared scale. NumPy's [backwards compatibility policy (NEP 23)](https://numpy.org/neps/nep-0023-backwards-compatibility.html#general-principles) is a good guide to the trade-offs involved. The factors below follow the same principles.

- **How many users are affected.** `zarr-python` is widely used, including by downstream libraries, so assume that any public API is used by someone unless there is concrete evidence otherwise. Most users do not follow our issue tracker and discover a removal only when their code breaks after upgrading, so absence of reported usage is not evidence of absence of usage. Base the estimate on usage data where possible, using code search across downstream projects, documented usage, or download statistics.
- **The cost of migration to users.** Is there a drop-in replacement? Can the migration be performed mechanically, or does it require users to redesign their code? A change with a clear, easy migration path is much cheaper to justify than one without.
- **The cost of keeping the API.** What do we pay by not removing it? Is there maintenance burden, bug surface, duplicated logic, or a worse design that we can't improve while the old API exists? A high carrying cost strengthens the case for removal.
- **Who benefits.** Benefits can include improved functionality, usability, and performance for users, as well as lower maintenance cost and better future extensibility for developers. A change that gives users something they want is a stronger case than one that benefits only maintainers, since users pay the migration cost either way.
- **Whether the goal can be met without removal.** Often a refactor, an alias, or an additive change achieves the same end without breaking anyone. Prefer those. As mentioned above, removing public API is never free.

If, after weighing these factors, a removal is not clearly worthwhile, prefer to keep the API. A deprecation warning is a commitment to change or remove. If you only want to steer users away from an API without removing it, say so in its documentation rather than emitting a warning. Conversely, do not leave a decision to remove an API indefinitely deferred. A vague intention to "eventually" break something is itself a form of technical debt. Either commit to the deprecation and begin the cycle, or decide to keep the API and design around it.

#### How to deprecate

Once a deprecation is decided, it must follow these steps so that users get a consistent, actionable signal:

1. **Emit a warning at runtime.** Raise `zarr.errors.ZarrDeprecationWarning` for an API that will be *removed*. Use `zarr.errors.ZarrFutureWarning` instead when behavior will *change* rather than disappear (for example, a default value that will change). The distinction is about who needs to see the warning. Python hides `DeprecationWarning` by default except in `__main__` and under test runners, so a removal warning reaches the authors of the code that calls the deprecated API, whereas `FutureWarning` is shown to everyone, which a behavior change needs because it alters results without any change to the user's code. The [`typing_extensions.deprecated`](https://typing-extensions.readthedocs.io/en/latest/#deprecated) decorator is the preferred way to deprecate whole functions, methods, and classes. When calling `warnings.warn` directly, set `stacklevel` so the warning points at the caller's code rather than zarr internals.
2. **Write an actionable message.** The warning message must state the planned removal or change (a specific version if known, otherwise "a future release") and the migration path (i.e., what the user should do instead). A deprecation warning with no replacement guidance is incomplete.
3. **Document it.** Add a "Deprecated" admonition to the docstring, stating the same removal target and migration path as the warning, so the deprecation appears in the rendered API reference, and update any affected user-guide prose.
4. **Add a changelog entry.** Add a `removal` changelog fragment in `changes/` (for example, `changes/1234.removal.md`) so the deprecation is announced under "Deprecations and Removals" in the release notes. When the API is finally removed, add a second `removal` fragment for that release.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about for changed, not deprecated, behavior i.e., what you state in point 1?


When an API is being replaced rather than simply dropped, deprecate the old name and introduce its replacement in a single step, rather than repeatedly adjusting the same signature across releases. Users should have to migrate only once.

#### How long a deprecation lasts

A deprecated API must remain available, emitting its warning, for **at least 6 months and at least one minor release** before it is removed. The same minimum applies to a behavior change announced with `ZarrFutureWarning`. The old behavior stays the default, with the warning, for at least that long before the change takes effect. These are minimums rather than specific targets. For widely-used API, prefer a longer cycle, and communicate the upcoming removal beyond the warning itself (for example, in release notes or a migration guide).

The removal itself is a backwards-incompatible change, and is classified under our [versioning policy](#versioning) by the upgrade effort it imposes. Use a `major` release for the removal of widely-used API versus a `minor` release when the impact is genuinely small. This differs from [Semantic Versioning](https://semver.org/), under which any removal would require a `major` release. Again, the [versioning policy](#versioning) above explains why. The deprecation period, not the release type, is the guarantee users rely on. A removal is only permitted once the minimum period has elapsed, regardless of which release it lands in.

#### Exceptions

A full deprecation cycle may be shortened or skipped in some cases. When it is, explain why in the pull request and the changelog entry:

- **Security fixes and data-corruption bugs**, where continuing the old behavior actively harms users.
- **Changes forced by the Zarr format specification**, which are governed by [Data format compatibility](#data-format-compatibility) and may move faster during a format transition.
- **Recently introduced API**, which has had little time to be adopted. API that has not yet appeared in a release can be changed freely. API that appeared in a recent release may be changed in a `minor` release with a shortened cycle, as the [versioning policy](#versioning) allows, when the number of affected users is small because the API is new.
- **Experimental API**, which is exempt and governed by the [Experimental API policy](#experimental-api-policy). This includes the deprecated re-export left in `zarr.experimental` when a feature is promoted to stable, which that policy keeps for one minor release.

## Experimental API policy

The `zarr.experimental` namespace contains features that are under active development and may change without notice. When contributing to or depending on experimental features, please keep the following in mind:
Expand Down
Loading