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

pantsbuild / pants / 18252174847

05 Oct 2025 01:36AM UTC coverage: 43.382% (-36.9%) from 80.261%
18252174847

push

github

web-flow
run tests on mac arm (#22717)

Just doing the minimal to pull forward the x86_64 pattern.

ref #20993

25776 of 59416 relevant lines covered (43.38%)

1.3 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
3✔
5

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

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

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

17
EXTRA_ENV_VARS_USAGE_HELP = """\
3✔
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):
3✔
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(
3✔
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
        """
46
        allowed_set = None if allowed is None else set(allowed)
×
47
        env_var_subset: dict[str, str] = {}
×
48

49
        def check_and_set(name: str, value: str | None):
×
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
                )
55
            if value is not None:
×
56
                env_var_subset[name] = value
×
57

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

71
        return FrozenDict(env_var_subset)
×
72

73
    def get_or_match(self, name_or_pattern: str) -> Iterator[tuple[str, str]]:
3✔
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
        """
79
        if value := self.get(name_or_pattern):
×
80
            yield name_or_pattern, value
×
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
85
        for k, v in self.items():
×
86
            # we use fnmatchcase to avoid normalising the case with `os.path.normcase` on Windows systems
87
            if fnmatch.fnmatchcase(k, name_or_pattern):
×
88
                yield k, v
×
89

90

91
@dataclass(frozen=True)
3✔
92
class EnvironmentVarsRequest:
3✔
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]
3✔
100
    allowed: FrozenOrderedSet[str] | None
3✔
101

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

106

107
class EnvironmentVars(FrozenDict[str, str]):
3✔
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):
3✔
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