• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.45
/src/python/pants/engine/env_vars.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
import fnmatch
1✔
7
import re
1✔
8
from collections.abc import Iterator, Sequence
1✔
9
from dataclasses import dataclass
1✔
10

11
from pants.util.frozendict import FrozenDict
1✔
12
from pants.util.ordered_set import FrozenOrderedSet
1✔
13

14
name_value_re = re.compile(r"([A-Za-z_]\w*)=(.*)")
1✔
15
shorthand_re = re.compile(r"([A-Za-z_]\w*)")
1✔
16

17
EXTRA_ENV_VARS_USAGE_HELP = """\
1✔
18
Entries are strings in the form `ENV_VAR=value` to use explicitly; or just
19
`ENV_VAR` to copy the value of a variable in Pants's own environment.
20
`fnmatch` globs like `ENV_VAR_PREFIXED_*` can be used to copy multiple environment variables.
21
"""
22

23

24
class CompleteEnvironmentVars(FrozenDict):
1✔
25
    """CompleteEnvironmentVars contains all environment variables from the current Pants process.
26

27
    NB: Consumers should almost always prefer to consume the `EnvironmentVars` type, which is
28
    filtered to a relevant subset of the environment.
29
    """
30

31
    def get_subset(
1✔
32
        self, requested: Sequence[str], *, allowed: Sequence[str] | None = None
33
    ) -> FrozenDict[str, str]:
34
        """Extract a subset of named env vars.
35

36
        Given a list of extra environment variable specifiers as strings, filter the contents of
37
        the Pants environment to only those variables.
38

39
        Each variable can be specified either as a name or as a name=value pair.
40
        In the former case, the value for that name is taken from this env. In the latter
41
        case the specified value overrides the value in this env.
42

43
        If `allowed` is specified, the requested variable names must be in that list, or an error
44
        will be raised.
45
        """
UNCOV
46
        allowed_set = None if allowed is None else set(allowed)
×
UNCOV
47
        env_var_subset: dict[str, str] = {}
×
48

UNCOV
49
        def check_and_set(name: str, value: str | None):
×
UNCOV
50
            if allowed_set is not None and name not in allowed_set:
×
51
                raise ValueError(
×
52
                    f"{name} is not in the list of variable names that are allowed to be set. "
53
                    f"Must be one of {','.join(sorted(allowed_set))}."
54
                )
UNCOV
55
            if value is not None:
×
UNCOV
56
                env_var_subset[name] = value
×
57

UNCOV
58
        for env_var in requested:
×
UNCOV
59
            name_value_match = name_value_re.match(env_var)
×
UNCOV
60
            if name_value_match:
×
UNCOV
61
                check_and_set(name_value_match[1], name_value_match[2])
×
UNCOV
62
            elif shorthand_re.match(env_var):
×
UNCOV
63
                for name, value in self.get_or_match(env_var):
×
UNCOV
64
                    check_and_set(name, value)
×
65
            else:
UNCOV
66
                raise ValueError(
×
67
                    f"An invalid variable was requested via the --test-extra-env-var "
68
                    f"mechanism: {env_var}"
69
                )
70

UNCOV
71
        return FrozenDict(env_var_subset)
×
72

73
    def get_or_match(self, name_or_pattern: str) -> Iterator[tuple[str, str]]:
1✔
74
        """Get the value of an envvar if it has an exact match, otherwise all fnmatches.
75

76
        Although fnmatch could also handle direct matches, it is significantly slower (roughly 2000
77
        times).
78
        """
UNCOV
79
        if value := self.get(name_or_pattern):
×
UNCOV
80
            yield name_or_pattern, value
×
UNCOV
81
            return  # do not check fnmatches if we have an exact match
×
82

83
        # fnmatch.filter looks tempting,
84
        # but we'd need to iterate once for the filtering the keys and again for getting the values
UNCOV
85
        for k, v in self.items():
×
86
            # we use fnmatchcase to avoid normalising the case with `os.path.normcase` on Windows systems
UNCOV
87
            if fnmatch.fnmatchcase(k, name_or_pattern):
×
UNCOV
88
                yield k, v
×
89

90

91
@dataclass(frozen=True)
1✔
92
class EnvironmentVarsRequest:
1✔
93
    """Requests a subset of the variables set in the environment.
94

95
    Requesting only the relevant subset of the environment reduces invalidation caused by unrelated
96
    changes.
97
    """
98

99
    requested: FrozenOrderedSet[str]
1✔
100
    allowed: FrozenOrderedSet[str] | None
1✔
101

102
    def __init__(self, requested: Sequence[str], allowed: Sequence[str] | None = None):
1✔
UNCOV
103
        object.__setattr__(self, "requested", FrozenOrderedSet(requested))
×
UNCOV
104
        object.__setattr__(self, "allowed", None if allowed is None else FrozenOrderedSet(allowed))
×
105

106

107
class EnvironmentVars(FrozenDict[str, str]):
1✔
108
    """A subset of the variables set in the environment.
109

110
    Accesses to `os.environ` cannot be accurately tracked, so @rules that need access to the
111
    environment should use APIs from this module instead.
112

113
    Wherever possible, the `EnvironmentVars` type should be consumed rather than the
114
    `CompleteEnvironmentVars`, as it represents a filtered/relevant subset of the environment, rather
115
    than the entire unfiltered environment.
116
    """
117

118

119
class PathEnvironmentVariable(FrozenOrderedSet):
1✔
120
    """The PATH environment variable entries, split on `os.pathsep`."""
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc