Skip to content
Open
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
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ repos:
language: python
additional_dependencies:
- https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl
exclude: '(.*pixi\.lock)|(\.git_archival\.txt)|(.*\.patch$)'
exclude: '(.*pixi\.lock)|(\.git_archival\.txt)|(.*\.patch$)|(^cuda_core/cuda/core/_vendored/)'
args: ["--fix"]

- id: no-markdown-in-docs-source
Expand Down Expand Up @@ -111,6 +111,7 @@ repos:
alias: mypy-cuda-core
name: mypy-cuda-core
files: ^cuda_core/cuda/.*\.(py|pyi)$
exclude: ^cuda_core/cuda/core/_vendored/
pass_filenames: false
args: [--config-file=cuda_core/pyproject.toml, cuda_core/cuda/core]
additional_dependencies:
Expand Down
94 changes: 94 additions & 0 deletions cuda_core/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,97 @@ so that they are documented but don't appear in the main index.
### API stability

Reviews should point out where existing public APIs are broken.

### Deprecation and API lifecycle

`cuda.core` follows SemVer (see `docs/source/:

- **New APIs** may be added at any time (`x.Y.0`).
- **Breaking removals** only happen in **major releases** (`X.0.0`).
- Per the support policy, a deprecation notice must be present for **at least
one minor release** before the API is actually removed.
- Changes should be notated in the code and also in the release notes in the
"Deprecated APIs" section.

**Annotating a new API** — Use the `versionadded` decorator from the vendored
`cuda.core._vendored.deprecated.sphinx` module:

```python

from cuda.core._vendored.deprecated.sphinx import versionadded

@versionadded("1.2.0")
def new_feature(...):
"""Short description.
"""
```

Alternatively, if the vagaries of how we implement functions in Cython does not
allow this, you can add the reST `versionadded` directive directly:

```python
def new_feature(...):
"""Short description.

.. versionadded:: 1.2.0
"""
```

**Annotating a changed API** — Use the `versionchanged` decorator from the
vendored `cuda.core._vendored.deprecated.sphinx` module:

```python

from cuda.core._vendored.deprecated.sphinx import versionchanged

@versionchanged("1.2.0", reason="The old version was broken because...")
def new_feature(...):
"""Short description.
"""
```

Alternatively, if the vagaries of how we implement functions in Cython does not
allow this, you can add the reST `versionchanged` directive directly:

```python
def new_feature(...):
"""Short description.

.. versionchanged:: 1.2.0
The old version was broken because...
"""
```

**Deprecating an existing API** — use the `@deprecated` decorator from the
vendored `cuda.core._vendored.deprecated.sphinx` module and add a
`.. deprecated::` directive in the docstring. The decorator emits a
`DeprecationWarning` at call time; the docstring directive surfaces it in the
generated docs.

```python
from cuda.core._vendored.deprecated.sphinx import deprecated

@deprecated(version="1.2.0", reason="Use `new_feature` instead.")
def old_feature(...):
"""Short description.
"""
```

Rules to follow when deprecating:

- The `version=` argument must be the **first** in which the
deprecation appears, not the release in which removal is planned.
- The `reason=` string must name the replacement (if one exists) so users
know what to migrate to.
- Keep the old implementation fully functional — do not change its behavior,
only add the decorator.
- The deprecated API must remain in the codebase for **at least one full minor
release cycle** before it can be removed in a subsequent major release.

**Removing a deprecated API** — removals land in a **major release**. Before
removing, verify that the deprecation has been present since at least the
previous minor release. Remove the decorator, the implementation, and any
`__all__` entry; update `api.rst` and the release notes accordingly.

At some point in the future, we will provide automation for removal of
deprecated APIs.
Empty file.
6 changes: 6 additions & 0 deletions cuda_core/cuda/core/_vendored/deprecated/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Vendored from the Deprecated package (https://pypi.org/project/Deprecated/),
# version 1.3.1, (c) Laurent LAPORTE, MIT License.
# Modified to remove the dependency on the `wrapt` package.

from cuda.core._vendored.deprecated.classic import deprecated
from cuda.core._vendored.deprecated.params import deprecated_params
111 changes: 111 additions & 0 deletions cuda_core/cuda/core/_vendored/deprecated/classic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Vendored from the Deprecated package (https://pypi.org/project/Deprecated/),
# version 1.3.1, (c) Laurent LAPORTE, MIT License.
# Modified to remove the dependency on the `wrapt` package.

import functools
import inspect
import warnings

# stacklevel=2 points past the wrapper to the actual call site
_routine_stacklevel = 2
_class_stacklevel = 2

string_types = (bytes, str)


class ClassicAdapter:
"""
Classic adapter -- *for advanced usage only*

This adapter is used to get the deprecation message according to the wrapped
object type: class, function, standard method, static method, or class method.

This is the base class of the :class:`~deprecated.sphinx.SphinxAdapter` class
which is used to update the wrapped object docstring.
"""

def __init__(self, reason="", version="", action=None, category=DeprecationWarning, extra_stacklevel=0):
self.reason = reason or ""
self.version = version or ""
self.action = action
self.category = category
self.extra_stacklevel = extra_stacklevel

def get_deprecated_msg(self, wrapped, instance):
if instance is None:
if inspect.isclass(wrapped):
fmt = "Call to deprecated class {name}."
else:
fmt = "Call to deprecated function (or staticmethod) {name}."
else:
if inspect.isclass(instance):
fmt = "Call to deprecated class method {name}."
else:
fmt = "Call to deprecated method {name}."
if self.reason:
fmt += " ({reason})"
if self.version:
fmt += " -- Deprecated since version {version}."
return fmt.format(name=wrapped.__name__, reason=self.reason or "", version=self.version or "")

def __call__(self, wrapped):
if inspect.isclass(wrapped):
old_new1 = wrapped.__new__

def wrapped_cls(cls, *args, **kwargs):
msg = self.get_deprecated_msg(wrapped, None)
stacklevel = _class_stacklevel + self.extra_stacklevel
if self.action:
with warnings.catch_warnings():
warnings.simplefilter(self.action, self.category)
warnings.warn(msg, category=self.category, stacklevel=stacklevel)
else:
warnings.warn(msg, category=self.category, stacklevel=stacklevel)
if old_new1 is object.__new__:
return old_new1(cls)
return old_new1(cls, *args, **kwargs)

wrapped.__new__ = staticmethod(wrapped_cls)
return wrapped

elif inspect.isroutine(wrapped):
adapter = self

@functools.wraps(wrapped)
def wrapper(*args, **kwargs):
msg = adapter.get_deprecated_msg(wrapped, None)
stacklevel = _routine_stacklevel + adapter.extra_stacklevel
if adapter.action:
with warnings.catch_warnings():
warnings.simplefilter(adapter.action, adapter.category)
warnings.warn(msg, category=adapter.category, stacklevel=stacklevel)
else:
warnings.warn(msg, category=adapter.category, stacklevel=stacklevel)
return wrapped(*args, **kwargs)

return wrapper

else:
raise TypeError(repr(type(wrapped)))


def deprecated(*args, **kwargs):
"""
Decorator which can be used to mark functions as deprecated.

It will result in a warning being emitted when the function is used.
"""
if args and isinstance(args[0], string_types):
kwargs["reason"] = args[0]
args = args[1:]

if args and not callable(args[0]):
raise TypeError(repr(type(args[0])))

if args:
adapter_cls = kwargs.pop("adapter_cls", ClassicAdapter)
adapter = adapter_cls(**kwargs)
wrapped = args[0]
return adapter(wrapped)

return functools.partial(deprecated, **kwargs)
52 changes: 52 additions & 0 deletions cuda_core/cuda/core/_vendored/deprecated/params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Vendored from the Deprecated package (https://pypi.org/project/Deprecated/),
# version 1.3.1, (c) Laurent LAPORTE, MIT License.
# Modified to remove the dependency on the `wrapt` package.

import collections
import functools
import inspect
import warnings


class DeprecatedParams:
"""
Decorator for functions where one or more parameters are deprecated.
"""

def __init__(self, param, reason="", category=DeprecationWarning):
self.messages = {}
self.category = category
self.populate_messages(param, reason=reason)

def populate_messages(self, param, reason=""):
if isinstance(param, dict):
self.messages.update(param)
elif isinstance(param, str):
fmt = "'{param}' parameter is deprecated"
reason = reason or fmt.format(param=param)
self.messages[param] = reason
else:
raise TypeError(param)

def check_params(self, signature, *args, **kwargs):
binding = signature.bind(*args, **kwargs)
bound = collections.OrderedDict(binding.arguments, **binding.kwargs)
return [param for param in bound if param in self.messages]

def warn_messages(self, messages):
for message in messages:
warnings.warn(message, category=self.category, stacklevel=3)

def __call__(self, f):
signature = inspect.signature(f)

@functools.wraps(f)
def wrapper(*args, **kwargs):
invalid_params = self.check_params(signature, *args, **kwargs)
self.warn_messages([self.messages[param] for param in invalid_params])
return f(*args, **kwargs)

return wrapper


deprecated_params = DeprecatedParams
Loading
Loading