Skip to content

Commit 0ef2df1

Browse files
menoldmtmencianaliciaaevans
authored
fix: remove deprecated pkg_resources usage ahead of planned removal in Setuptools ≥81 (#1058)
Replaces all uses of the deprecated pkg_resources API in `utils.py`, `docker_utils.py`, and `autobump.py` with `importlib.resources` and `packaging.version` equivalents. Adds a smoke test for `validate_config` to ensure schema validation works without pkg_resources. Closes #1056 --------- Co-authored-by: Joshua Zhuang <71105179+mencian@users.noreply.github.com> Co-authored-by: Alicia A. Evans <108547992+aliciaaevans@users.noreply.github.com>
1 parent 16f2ab9 commit 0ef2df1

File tree

5 files changed

+61
-26
lines changed

5 files changed

+61
-26
lines changed

bioconda_utils/autobump.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
from conda.exports import MatchSpec, VersionOrder
6565
import conda.exceptions
6666

67-
from pkg_resources import parse_version
67+
from packaging.version import parse as _pep440_parse, InvalidVersion
6868

6969
from . import __version__
7070
from . import utils
@@ -78,11 +78,28 @@
7878
from .githandler import GitHandler
7979
from .githubhandler import GitHubHandler
8080

81-
# pkg_resources.parse_version returns a Version or LegacyVersion object
82-
# as defined in packaging.version. Since it's bundling it's own copy of
83-
# packaging, the class it returns is not the same as the one we can import.
84-
# So we cheat by having it create a LegacyVersion delibarately.
85-
LegacyVersion = parse_version("1.1").__class__ # pylint: disable=invalid-name
81+
def _parse_or_legacy(s: str):
82+
"""
83+
Attempt to parse a version string as a PEP 440-compliant Version.
84+
85+
Returns
86+
-------
87+
tuple
88+
(parsed_version, is_legacy)
89+
- parsed_version : packaging.version.Version instance if parseable,
90+
otherwise the original string for legacy (non-PEP 440) versions.
91+
- is_legacy : bool indicating whether the string is a legacy version.
92+
93+
Notes
94+
-----
95+
This replaces the deprecated ``pkg_resources.parse_version`` + LegacyVersion
96+
check. ``is_legacy`` allows downstream logic to gate acceptance of
97+
unparseable versions.
98+
"""
99+
try:
100+
return _pep440_parse(s), False # Version, not legacy
101+
except InvalidVersion:
102+
return s, True # raw string, legacy
86103

87104

88105
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
@@ -680,21 +697,22 @@ def select_version(current: str, versions: Sequence[str]) -> str:
680697
defined by parse_version)
681698
682699
- may only be "Legacy" (=strange) if current is Legacy (as
683-
defined by parse_version)
700+
identified by PEP 440 parsing)
684701
"""
685-
current_version = parse_version(current)
686-
current_is_legacy = isinstance(current_version, LegacyVersion)
702+
current_version, current_is_legacy = _parse_or_legacy(current)
687703
latest_vo = VersionOrder(current)
688704
latest = current
689705
for vers in versions:
690706
if "-" in vers: # ignore versions with local (FIXME)
691707
continue
692-
vers_version = parse_version(vers)
708+
vers_version, vers_is_legacy = _parse_or_legacy(vers)
693709
# allow prerelease only if current is prerelease
694-
if vers_version.is_prerelease and not current_version.is_prerelease:
710+
if (
711+
getattr(vers_version, "is_prerelease", False)
712+
and not getattr(current_version, "is_prerelease", False)
713+
):
695714
continue
696715
# allow legacy only if current is legacy
697-
vers_is_legacy = isinstance(vers_version, LegacyVersion)
698716
if vers_is_legacy and not current_is_legacy:
699717
continue
700718
# using conda version order here as that's what will be

bioconda_utils/docker_utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
import pwd
5454
import grp
5555
from textwrap import dedent
56-
import pkg_resources
56+
from importlib.resources import files, as_file
5757
import re
5858
from distutils.version import LooseVersion
5959

@@ -341,10 +341,10 @@ def _build_image(self):
341341
if self.requirements:
342342
fout.write(open(self.requirements).read())
343343
else:
344-
fout.write(open(pkg_resources.resource_filename(
345-
'bioconda_utils',
346-
'bioconda_utils-requirements.txt')
347-
).read())
344+
# pkg_resources (deprecated) is replaced with importlib.resources
345+
with as_file(files('bioconda_utils') / 'bioconda_utils-requirements.txt') as req_path:
346+
with open(req_path, 'r', encoding='utf-8') as fh:
347+
fout.write(fh.read())
348348

349349
proxies = "\n".join("ENV {} {}".format(k, v)
350350
for k, v in self._find_proxy_settings())

bioconda_utils/lint/check_build_help.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ def check_deps(self, deps):
6666
class uses_setuptools(LintCheck):
6767
"""The recipe uses setuptools in run depends
6868
69-
Most Python packages only need setuptools during installation.
70-
Check if the package really needs setuptools (e.g. because it uses
71-
pkg_resources or setuptools console scripts).
69+
Most Python packages only need setuptools during installation. Check if the
70+
package really needs setuptools (e.g. because it uses pkg_resources
71+
(deprecated; slated for removal) or setuptools console scripts).
7272
7373
"""
7474

bioconda_utils/utils.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
from github import Github
4242

43-
import pkg_resources
43+
from importlib.resources import files, as_file
4444
import pandas as pd
4545
import tqdm as _tqdm
4646
import aiohttp
@@ -1170,10 +1170,13 @@ def validate_config(config):
11701170
"""
11711171
if not isinstance(config, dict):
11721172
config = yaml.safe_load(open(config))
1173-
fn = pkg_resources.resource_filename(
1174-
'bioconda_utils', 'config.schema.yaml'
1175-
)
1176-
schema = yaml.safe_load(open(fn))
1173+
1174+
# Load packaged schema without pkg_resources (deprecated)
1175+
# files('bioconda_utils') returns a Traversable to the package contents
1176+
with as_file(files('bioconda_utils') / 'config.schema.yaml') as schema_path:
1177+
with open(schema_path, 'r', encoding='utf-8') as fh:
1178+
schema = yaml.safe_load(fh)
1179+
11771180
validate(config, schema)
11781181

11791182

@@ -1650,7 +1653,7 @@ def extract_stable_version(version):
16501653

16511654

16521655
def yaml_remove_invalid_chars(text: str, valid_chars_re=re.compile(r"[^ \t\n\w\d:\{\}\[\]\(\);&|\$§\"'\?\!%#\\~*\.,-\^°]+")) -> str:
1653-
"""Remove chars that are invalid in yaml literal strings.
1656+
"""Remove chars that are invalid in yaml literal strings.
16541657
16551658
E.g. we do not want them to contain carriage return chars or delete chars.
16561659
"""

test/test_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from bioconda_utils import docker_utils
2323
from bioconda_utils import build
2424
from bioconda_utils import upload
25+
from bioconda_utils.utils import validate_config
2526
from helpers import ensure_missing, Recipes
2627

2728

@@ -1342,3 +1343,16 @@ def test_pkg_test_conda_package_format(
13421343
assert pkg_file.endswith({"1": ".tar.bz2", "2": ".conda"}[pkg_format])
13431344
assert os.path.exists(pkg_file)
13441345
ensure_missing(pkg_file)
1346+
1347+
1348+
def test_validate_config_smoke():
1349+
"""Minimal config that satisfies schema types should validate without error."""
1350+
cfg = {
1351+
"channels": ["conda-forge", "bioconda"],
1352+
"docker_image": "quay.io/bioconda/bioconda-utils",
1353+
"upload_channel": "bioconda",
1354+
"conda_build_version": "3.28.4",
1355+
"blacklists": [],
1356+
}
1357+
# Should not raise
1358+
validate_config(cfg)

0 commit comments

Comments
 (0)