Deprecations#

Guidelines#

What to Deprecate#

All of the following should be given deprecation warnings that give downstream packages enough time to adapt.

Breaking changes#

All breaking changes to public classes and functions. “Public” is not strictly defined in linkml, but in general these are things made available via CLI, or things that are given their own packages/modules.

Breaking changes include (but are not limited to)

  • Removal of features, classes, functions, and parameters

  • Changes to defaults

  • Refactoring that changes import location

Dependencies#

Changes to dependencies typically do not need a deprecation warning, but for updates that exclude a commonly used version with substantial differences with the newer version, a warning should be emitted. As an example, the deprecation module was initially motivated by the need to warn about deprecating support for Pydantic v1.

Supported Python Version#

All changes to the minimum supported python versions should be deprecated with at least a patch version of notice.

Duration of Deprecation#

The amount of time (or, the number of versions between when a feature is marked as deprecated and when it is removed) depends on the size of the change and the complexity of adapting to it, but typically they should coincide with a minor or major version.

Process#

There are two phases to a deprecation:

  • Deprecation - A feature is deprecated when it is marked for removal. No new code should use that feature, and its usage should emit a warning.

  • Removal - A previously deprecated feature is removed from the package.

All deprecations use the tools in the deprecation module.

Declare the Deprecation#

To declare a deprecation, create a new Deprecation class and add it to the DEPRECATIONS tuple in linkml.utils.deprecation . The Deprecation object contains

  • the information about what is being deprecated,

  • after which version it is considered deprecated

  • (Optional) which version it will be removed in

  • (Optional) what should be done instead

  • (Optional) an issue that contains further information.

For example, if we are deprecating a feature for rendering a linkml schema to semaphore, we might create an object like this:

deprecation.py

DEPRECATIONS = (
  Deprecation(
    name = "semaphore",
    message = (
      "Flag-based semaphore schemas were considered to not have a high "
      "enough information capacity to usefully represent a linkml schema"),
    recommendation = "Update to a digital schema representation or morse code",
    deprecated_in = SemVer.from_str("1.7.5"),
    removed_in = SemVer.from_str("1.8.0"),
    issue = -1
  ),
)

Declaring the deprecation will add it to the Deprecation Log.

Mark the Deprecation#

Everywhere the deprecated feature would be used, add a deprecation_warning() call that uses the name of the Deprecation object.

This will emit a DeprecationWarning that looks like this:

>>> deprecation_warning('semaphore')
[semaphore] DEPRECATED
Flag-based semaphore schemas were considered to not have a high enough information capacity to usefully represent a linkml schema
Deprecated In: 1.7.0
Removed In: 1.8.0
Recommendation: Update to a digital schema representation or morse code
See: https://github.com/linkml/linkml/issues/-1

This looks different depending on what is being deprecated, see this guide for further examples of how to deprecate various python language constructs.

Some examples:

Module#

semaphore.py

from linkml.utils import deprecation_warning
deprecation_warning('semaphore')

Function#

def render_semaphore(schema):
    deprecation_warning('semaphore')

Class#

class MyClass:
    def __init__(self):
        deprecation_warning('semaphore')

Dependency Version#

eg. if we were deprecating all versions of pysemaphore<2.0.0, in every place it is imported:

import pysemaphore
from linkml.utils.deprecation import SemVer

if SemVer.from_package('pysemaphore').major < 2:
  deprecation_warning('semaphore')

Python Version#

In linkml/__init__.py

import sys

if sys.version_info.minor <= 8:
  deprecation_warning('semaphore')

Default Value#

If we are changing a default value that is None in the function signature and filled in the function body:

def my_function(arg:Optional[str] = None):
    if arg is None:
        deprecation_warning('semaphore')
        arg = 'old_default'

Or if we are dropping support for calling a function with a str in favor of a Path

def my_function(arg: Union[Path, str]):
    if isinstance(arg, str):
        deprecation_warning('semaphore')
        arg = Path(str)

It’s inelegant to check whether a python is called explicitly with a parameter or whether it is using the default, but it is possible:

import inspect
def my_function(arg = 'default'):
    frame = inspect.currentframe()
    outer = inspect.getouterframes(frame, 1)[1]
    if arg not in outer.code_context[0]:
        deprecation_warning('semaphore')
    

Removal#

LinkML’s deprecation warnings are tested: when the version where a deprecation is marked to be removed is reached, the tests will fail (specifically tests/test_utils/test_deprecation.py::test_removed_are_removed).

Remove all deprecated functionality and the calls to deprecation_warning(), but leave the Deprecation class declaration in place as a record.

API#

Utilities for deprecating functionality and dependencies.

  • Emitting DeprecationWarnings

  • Tracking deprecated and removed in versions

  • Fail tests when something marked as removed_in is still present in the specified version

Initial draft for deprecating Pydantic 1, to make more general, needs - function wrapper version - …

To deprecate something:

  • Create a Deprecation object within the DEPRECATIONS tuple

  • Use the deprecation_warning() function wherever the deprecated feature would be used to emit the warning

class linkml.utils.deprecation.Deprecation(name: str, message: str, deprecated_in: SemVer, removed_in: SemVer | None = None, recommendation: str | None = None, issue: int | None = None, **_kwargs)[source]#

Parameterization of a deprecation.

property deprecated: bool#
deprecated_in: SemVer#

Version that the feature was deprecated in

issue: int | None = None#

GitHub version describing deprecation

message: str#

Message to be displayed explaining the deprecation

name: str#

Shorthand, unique name used to refer to this deprecation

recommendation: str | None = None#

Recommendation about what to do to replace the deprecated behavior

property removed: bool#
removed_in: SemVer | None = None#

Version that the feature will be removed in

warn(**kwargs)[source]#
class linkml.utils.deprecation.SemVer(major: int = 0, minor: int = 0, patch: int = 0, epoch: int | None = None, pre: str | None = None, pre_l: str | None = None, pre_n: str | None = None, post: str | None = None, post_n1: str | None = None, post_l: str | None = None, post_n2: str | None = None, dev: str | None = None, dev_l: str | None = None, dev_n: str | None = None, local: str | None = None, **_kwargs)[source]#

Representation of semantic version that supports inequality comparisons.

Note

The inequality methods _only_ test the numeric major, minor, and patch components of the version - ie. they do not evaluate the prerelease versions as described in the semver spec. This is not intended to be a general SemVer inequality calculator, but used only for testing deprecations

dev: str | None = None#
dev_l: str | None = None#
dev_n: str | None = None#
epoch: int | None = None#
classmethod from_package(package: str) SemVer[source]#

Get semver from package name

classmethod from_str(v: str) SemVer | None[source]#

Create a SemVer from a string using PEP 440 syntax.

Examples

>>> version = SemVer.from_str("v0.1.0")
>>> print(version)
0.1.0
local: str | None = None#
major: int = 0#
minor: int = 0#
patch: int = 0#
post: str | None = None#
post_l: str | None = None#
post_n1: str | None = None#
post_n2: str | None = None#
pre: str | None = None#
pre_l: str | None = None#
pre_n: str | None = None#
linkml.utils.deprecation.deprecation_warning(name: str)[source]#

Call this with the name of the deprecation object wherever the deprecated functionality will be used

This function will

  • emit a warning if the current version is greater than deprecated_in

  • log that the deprecated feature was accessed in EMITTED for testing deprecations and muting warnings

See Also#