Coverage for src/debputy/types.py: 16%
66 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-01-27 13:59 +0000
1import dataclasses
2from typing import (
3 TypeVar,
4 TYPE_CHECKING,
5 Sequence,
6 Tuple,
7 Mapping,
8 Dict,
9 Optional,
10 TypedDict,
11 NotRequired,
12 List,
13 MutableMapping,
14)
16if TYPE_CHECKING:
17 from debputy.plugin.api import VirtualPath
18 from debputy.filesystem_scan import FSPath
20 VP = TypeVar("VP", VirtualPath, FSPath)
21 S = TypeVar("S", str, bytes)
22else:
23 VP = TypeVar("VP", "VirtualPath", "FSPath")
24 S = TypeVar("S", str, bytes)
27class EnvironmentModificationSerialized(TypedDict):
28 replacements: NotRequired[Dict[str, str]]
29 removals: NotRequired[List[str]]
32@dataclasses.dataclass(slots=True, frozen=True)
33class EnvironmentModification:
34 replacements: Sequence[Tuple[str, str]] = tuple()
35 removals: Sequence[str] = tuple()
37 def __bool__(self) -> bool:
38 return bool(self.removals) or bool(self.replacements)
40 def combine(
41 self,
42 other: "Optional[EnvironmentModification]",
43 ) -> "EnvironmentModification":
44 if not other:
45 return self
46 existing_replacements = {k: v for k, v in self.replacements}
47 extra_replacements = {
48 k: v
49 for k, v in other.replacements
50 if k not in existing_replacements or existing_replacements[k] != v
51 }
52 seen_removals = set(self.removals)
53 extra_removals = [r for r in other.removals if r not in seen_removals]
55 if not extra_replacements and isinstance(self.replacements, tuple):
56 new_replacements = self.replacements
57 else:
58 combined_replacements = []
59 for k, v in existing_replacements.items():
60 if k not in extra_replacements:
61 combined_replacements.append((k, v))
63 for k, v in other.replacements:
64 if k in extra_replacements:
65 combined_replacements.append((k, v))
67 new_replacements = tuple(combined_replacements)
69 if not extra_removals and isinstance(self.removals, tuple):
70 new_removals = self.removals
71 else:
72 combined_removals = list(self.removals)
73 combined_removals.extend(extra_removals)
74 new_removals = tuple(combined_removals)
76 if self.replacements is new_replacements and self.removals is new_removals:
77 return self
79 return EnvironmentModification(
80 new_replacements,
81 new_removals,
82 )
84 def update_inplace(self, env: MutableMapping[str, str]) -> None:
85 for k, v in self.replacements:
86 existing_value = env.get(k)
87 if v == existing_value:
88 continue
89 env[k] = v
91 for k in self.removals:
92 if k not in env:
93 continue
94 del env[k]
96 def compute_env(self, base_env: Mapping[str, str]) -> Mapping[str, str]:
97 updated_env: Optional[Dict[str, str]] = None
98 for k, v in self.replacements:
99 existing_value = base_env.get(k)
100 if v == existing_value:
101 continue
103 if updated_env is None:
104 updated_env = dict(base_env)
105 updated_env[k] = v
107 for k in self.removals:
108 if k not in base_env:
109 continue
110 if updated_env is None:
111 updated_env = dict(base_env)
112 del updated_env[k]
114 if updated_env is not None:
115 return updated_env
116 return base_env