From 626f005fc6a8ccb0efc28e6c151f57590e2da7d2 Mon Sep 17 00:00:00 2001 From: Brett Smith Date: Fri, 12 Jul 2024 11:35:29 -0400 Subject: [PATCH] 21935: Move arvados.util._deprecated to arvados._internal Arvados-DCO-1.1-Signed-off-by: Brett Smith --- doc/pysdk_pdoc.py | 4 ++ sdk/python/arvados/_internal/__init__.py | 66 ++++++++++++++++++++++++ sdk/python/arvados/util.py | 54 ------------------- sdk/python/tests/test_internal.py | 35 +++++++++++++ 4 files changed, 105 insertions(+), 54 deletions(-) create mode 100644 sdk/python/arvados/_internal/__init__.py create mode 100644 sdk/python/tests/test_internal.py diff --git a/doc/pysdk_pdoc.py b/doc/pysdk_pdoc.py index b246a83fd6..be254b626d 100755 --- a/doc/pysdk_pdoc.py +++ b/doc/pysdk_pdoc.py @@ -32,6 +32,10 @@ else: DEFAULT_ARGLIST = [ '--output-directory=sdk/python', '../sdk/python/build/lib/arvados/', + # Because the module is prviate, pdoc does not build documentation for any + # of it. The exclusion below additionally prevents pdoc from hyperlinking + # references under arvados._internal that appear in method signatures, etc. + '!arvados._internal', ] MD_EXTENSIONS = { 'admonitions': None, diff --git a/sdk/python/arvados/_internal/__init__.py b/sdk/python/arvados/_internal/__init__.py new file mode 100644 index 0000000000..66c82870f1 --- /dev/null +++ b/sdk/python/arvados/_internal/__init__.py @@ -0,0 +1,66 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +"""Arvados internal utilities + +Everything in `arvados._internal` is support code for the Arvados Python SDK +and tools. Nothing in this module is intended to be part of the public-facing +SDK API. Classes and functions in this module may be changed or removed at any +time. +""" + +import functools +import re +import warnings + +def deprecated(version=None, preferred=None): + """Mark a callable as deprecated in the SDK + + This will wrap the callable to emit as a DeprecationWarning + and add a deprecation notice to its docstring. + + If the following arguments are given, they'll be included in the + notices: + + * preferred: str | None --- The name of an alternative that users should + use instead. + + * version: str | None --- The version of Arvados when the callable is + scheduled to be removed. + """ + if version is None: + version = '' + else: + version = f' and scheduled to be removed in Arvados {version}' + if preferred is None: + preferred = '' + else: + preferred = f' Prefer {preferred} instead.' + def deprecated_decorator(func): + fullname = f'{func.__module__}.{func.__qualname__}' + parent, _, name = fullname.rpartition('.') + if name == '__init__': + fullname = parent + warning_msg = f'{fullname} is deprecated{version}.{preferred}' + @functools.wraps(func) + def deprecated_wrapper(*args, **kwargs): + warnings.warn(warning_msg, DeprecationWarning, 2) + return func(*args, **kwargs) + # Get func's docstring without any trailing newline or empty lines. + func_doc = re.sub(r'\n\s*$', '', func.__doc__ or '') + match = re.search(r'\n([ \t]+)\S', func_doc) + indent = '' if match is None else match.group(1) + warning_doc = f'\n\n{indent}.. WARNING:: Deprecated\n{indent} {warning_msg}' + # Make the deprecation notice the second "paragraph" of the + # docstring if possible. Otherwise append it. + docstring, count = re.subn( + rf'\n[ \t]*\n{indent}', + f'{warning_doc}\n\n{indent}', + func_doc, + count=1, + ) + if not count: + docstring = f'{func_doc.lstrip()}{warning_doc}' + deprecated_wrapper.__doc__ = docstring + return deprecated_wrapper + return deprecated_decorator diff --git a/sdk/python/arvados/util.py b/sdk/python/arvados/util.py index 68582dec96..974eb30ff3 100644 --- a/sdk/python/arvados/util.py +++ b/sdk/python/arvados/util.py @@ -11,7 +11,6 @@ import dataclasses import enum import errno import fcntl -import functools import hashlib import httplib2 import itertools @@ -24,7 +23,6 @@ import shlex import stat import subprocess import sys -import warnings import arvados.errors @@ -80,58 +78,6 @@ user_uuid_pattern = re.compile(r'[a-z0-9]{5}-tpzed-[a-z0-9]{15}') logger = logging.getLogger('arvados') -def _deprecated(version=None, preferred=None): - """Mark a callable as deprecated in the SDK - - This will wrap the callable to emit as a DeprecationWarning - and add a deprecation notice to its docstring. - - If the following arguments are given, they'll be included in the - notices: - - * preferred: str | None --- The name of an alternative that users should - use instead. - - * version: str | None --- The version of Arvados when the callable is - scheduled to be removed. - """ - if version is None: - version = '' - else: - version = f' and scheduled to be removed in Arvados {version}' - if preferred is None: - preferred = '' - else: - preferred = f' Prefer {preferred} instead.' - def deprecated_decorator(func): - fullname = f'{func.__module__}.{func.__qualname__}' - parent, _, name = fullname.rpartition('.') - if name == '__init__': - fullname = parent - warning_msg = f'{fullname} is deprecated{version}.{preferred}' - @functools.wraps(func) - def deprecated_wrapper(*args, **kwargs): - warnings.warn(warning_msg, DeprecationWarning, 2) - return func(*args, **kwargs) - # Get func's docstring without any trailing newline or empty lines. - func_doc = re.sub(r'\n\s*$', '', func.__doc__ or '') - match = re.search(r'\n([ \t]+)\S', func_doc) - indent = '' if match is None else match.group(1) - warning_doc = f'\n\n{indent}.. WARNING:: Deprecated\n{indent} {warning_msg}' - # Make the deprecation notice the second "paragraph" of the - # docstring if possible. Otherwise append it. - docstring, count = re.subn( - rf'\n[ \t]*\n{indent}', - f'{warning_doc}\n\n{indent}', - func_doc, - count=1, - ) - if not count: - docstring = f'{func_doc.lstrip()}{warning_doc}' - deprecated_wrapper.__doc__ = docstring - return deprecated_wrapper - return deprecated_decorator - @dataclasses.dataclass class _BaseDirectorySpec: """Parse base directories diff --git a/sdk/python/tests/test_internal.py b/sdk/python/tests/test_internal.py new file mode 100644 index 0000000000..6ea9407f3d --- /dev/null +++ b/sdk/python/tests/test_internal.py @@ -0,0 +1,35 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +import re + +import pytest + +from arvados import _internal + +class TestDeprecated: + @staticmethod + @_internal.deprecated('TestVersion', 'arvados.noop') + def noop_func(): + """Do nothing + + This function returns None. + """ + + @pytest.mark.parametrize('pattern', [ + r'^Do nothing$', + r'^ +.. WARNING:: Deprecated$', + r' removed in Arvados TestVersion\.', + r' Prefer arvados\.noop\b', + r'^ +This function returns None\.$', + ]) + def test_docstring(self, pattern): + assert re.search(pattern, self.noop_func.__doc__, re.MULTILINE) is not None + + def test_deprecation_warning(self): + with pytest.warns(DeprecationWarning) as check: + self.noop_func() + actual = str(check[0].message) + assert ' removed in Arvados TestVersion.' in actual + assert ' Prefer arvados.noop ' in actual -- 2.30.2