def test_get() -> None: fd1 = FrozenDict({"a": 0}) assert fd1["a"] == 0 assert fd1.get("a") == 0 with pytest.raises(KeyError): fd1["z"] assert fd1.get("z") is None assert fd1.get("z", 26) == 26
class UnionMembership: union_rules: FrozenDict[Type, FrozenOrderedSet[Type]] @classmethod def from_rules(cls, rules: Iterable[UnionRule]) -> UnionMembership: mapping: DefaultDict[Type, OrderedSet[Type]] = defaultdict(OrderedSet) for rule in rules: mapping[rule.union_base].add(rule.union_member) return cls(mapping) def __init__(self, union_rules: Mapping[Type, Iterable[Type]]) -> None: self.union_rules = FrozenDict({ base: FrozenOrderedSet(members) for base, members in union_rules.items() }) def __getitem__(self, union_type: _T) -> FrozenOrderedSet[_T]: """Get all members of this union type. If the union type does not exist because it has no members registered, this will raise an IndexError. Note that the type hint assumes that all union members will have subclassed the union type - this is only a convention and is not actually enforced. So, you may have inaccurate type hints. """ return self.union_rules[union_type] # type: ignore[return-value] def get(self, union_type: _T) -> FrozenOrderedSet[_T]: """Get all members of this union type. If the union type does not exist because it has no members registered, return an empty FrozenOrderedSet. Note that the type hint assumes that all union members will have subclassed the union type - this is only a convention and is not actually enforced. So, you may have inaccurate type hints. """ return self.union_rules.get( union_type, FrozenOrderedSet()) # type: ignore[return-value] def is_member(self, union_type: Type, putative_member: Type) -> bool: members = self.union_rules.get(union_type) if members is None: raise TypeError(f"Not a registered union type: {union_type}") return type(putative_member) in members def has_members(self, union_type: Type) -> bool: """Check whether the union has an implementation or not.""" return bool(self.union_rules.get(union_type)) def has_members_for_all(self, union_types: Iterable[Type]) -> bool: """Check whether every union given has an implementation or not.""" return all(self.has_members(union_type) for union_type in union_types)
class PantsEnvironment: """PantsEnvironment is a representation of the environment variables the currently-executing Pants process was invoked with.""" env: FrozenDict[str, str] @deprecated( "2.5.0.dev0", hint_message= "Request a subset Environment (using EnvironmentRequest) or the CompleteEnvironment.", ) def __init__(self, env: Optional[Mapping[str, str]] = None) -> None: """Initialize a `PantsEnvironment` with the current contents of the environment. Explicitly specify the env argument to create a mock environment for testing. """ self.env = FrozenDict(env or {}) def get_subset( self, requested: Sequence[str], *, allowed: Optional[Sequence[str]] = None) -> FrozenDict[str, str]: """Extract a subset of named env vars. Given a list of extra environment variable specifiers as strings, filter the contents of the pants environment to only those variables. Each variable can be specified either as a name or as a name=value pair. In the former case, the value for that name is taken from this env. In the latter case the specified value overrides the value in this env. If `allowed` is specified, the requested variable names must be in that list, or an error will be raised. """ allowed_set = None if allowed is None else set(allowed) env_var_subset: Dict[str, str] = {} def check_and_set(name: str, value: Optional[str]): if allowed_set is not None and name not in allowed_set: raise ValueError( f"{name} is not in the list of variable names that are allowed to be set. " f"Must be one of {','.join(sorted(allowed_set))}.") if value is not None: env_var_subset[name] = value for env_var in requested: name_value_match = name_value_re.match(env_var) if name_value_match: check_and_set(name_value_match[1], name_value_match[2]) elif shorthand_re.match(env_var): check_and_set(env_var, self.env.get(env_var)) else: raise ValueError( f"An invalid variable was requested via the --test-extra-env-var " f"mechanism: {env_var}") return FrozenDict(env_var_subset)
class UnionMembership: union_rules: FrozenDict[Type, FrozenOrderedSet[Type]] def __init__(self, union_rules: Mapping[Type, Iterable[Type]]) -> None: self.union_rules = FrozenDict({ base: FrozenOrderedSet(members) for base, members in union_rules.items() }) def is_member(self, union_type: Type, putative_member: Type) -> bool: members = self.union_rules.get(union_type) if members is None: raise TypeError(f"Not a registered union type: {union_type}") return type(putative_member) in members def has_members(self, union_type: Type) -> bool: """Check whether the union has an implementation or not.""" return bool(self.union_rules.get(union_type)) def has_members_for_all(self, union_types: typing.Iterable[Type]) -> bool: """Check whether every union given has an implementation or not.""" return all(self.has_members(union_type) for union_type in union_types)
class Target(ABC): """A Target represents a combination of fields that are valid _together_.""" # Subclasses must define these alias: ClassVar[str] core_fields: ClassVar[Tuple[Type[Field], ...]] # Subclasses may define these v1_only: ClassVar[bool] = False # These get calculated in the constructor address: Address plugin_fields: Tuple[Type[Field], ...] field_values: FrozenDict[Type[Field], Field] @final def __init__( self, unhydrated_values: Dict[str, Any], *, address: Address, # NB: `union_membership` is only optional to facilitate tests. In production, we should # always provide this parameter. This should be safe to do because production code should # rarely directly instantiate Targets and should instead use the engine to request them. union_membership: Optional[UnionMembership] = None, ) -> None: self.address = address self.plugin_fields = self._find_plugin_fields(union_membership or UnionMembership({})) field_values = {} aliases_to_field_types = { field_type.alias: field_type for field_type in self.field_types } for alias, value in unhydrated_values.items(): if alias not in aliases_to_field_types: raise InvalidFieldException( f"Unrecognized field `{alias}={value}` in target {address}. Valid fields for " f"the target type `{self.alias}`: {sorted(aliases_to_field_types.keys())}.", ) field_type = aliases_to_field_types[alias] field_values[field_type] = field_type(value, address=address) # For undefined fields, mark the raw value as None. for field_type in set(self.field_types) - set(field_values.keys()): field_values[field_type] = field_type(raw_value=None, address=address) self.field_values = FrozenDict(field_values) @final @property def field_types(self) -> Tuple[Type[Field], ...]: return (*self.core_fields, *self.plugin_fields) @final class PluginField: """A sentinel class to allow plugin authors to add additional fields to this target type. Plugin authors may add additional fields by simply registering UnionRules between the `Target.PluginField` and the custom field, e.g. `UnionRule(PythonLibrary.PluginField, TypeChecked)`. The `Target` will then treat `TypeChecked` as a first-class citizen and plugins can use that Field like any other Field. """ def __repr__(self) -> str: return (f"{self.__class__}(" f"address={self.address}, " f"alias={repr(self.alias)}, " f"core_fields={list(self.core_fields)}, " f"plugin_fields={list(self.plugin_fields)}, " f"field_values={list(self.field_values.values())}" f")") def __str__(self) -> str: fields = ", ".join(str(field) for field in self.field_values.values()) address = f"address=\"{self.address}\"{', ' if fields else ''}" return f"{self.alias}({address}{fields})" @final @classmethod def _find_plugin_fields( cls, union_membership: UnionMembership) -> Tuple[Type[Field], ...]: return cast( Tuple[Type[Field], ...], tuple(union_membership.union_rules.get(cls.PluginField, ()))) @final @classmethod def _find_registered_field_subclass( cls, requested_field: Type[_F], *, registered_fields: Iterable[Type[Field]]) -> Optional[Type[_F]]: """Check if the Target has registered a subclass of the requested Field. This is necessary to allow targets to override the functionality of common fields like `Sources`. For example, Python targets may want to have `PythonSources` to add extra validation that every source file ends in `*.py`. At the same time, we still want to be able to call `my_python_tgt.get(Sources)`, in addition to `my_python_tgt.get(PythonSources)`. """ subclass = next( (registered_field for registered_field in registered_fields if issubclass(registered_field, requested_field)), None, ) return cast(Optional[Type[_F]], subclass) @final def _maybe_get(self, field: Type[_F]) -> Optional[_F]: result = self.field_values.get(field, None) if result is not None: return cast(_F, result) field_subclass = self._find_registered_field_subclass( field, registered_fields=self.field_types) if field_subclass is not None: return cast(_F, self.field_values[field_subclass]) return None @final def __getitem__(self, field: Type[_F]) -> _F: """Get the requested `Field` instance belonging to this target. If the `Field` is not registered on this `Target` type, this method will raise a `KeyError`. To avoid this, you should first call `tgt.has_field()` or `tgt.has_fields()` to ensure that the field is registered, or, alternatively, use `Target.get()`. See the docstring for `Target.get()` for how this method handles subclasses of the requested Field and for tips on how to use the returned value. """ result = self._maybe_get(field) if result is not None: return result raise KeyError( f"The target `{self}` does not have a field `{field.__name__}`. Before calling " f"`my_tgt[{field.__name__}]`, call `my_tgt.has_field({field.__name__})` to " f"filter out any irrelevant Targets or call `my_tgt.get({field.__name__})` to use the " f"default Field value.") @final def get(self, field: Type[_F], *, default_raw_value: Optional[Any] = None) -> _F: """Get the requested `Field` instance belonging to this target. This will return an instance of the requested field type, e.g. an instance of `Compatibility`, `Sources`, `EntryPoint`, etc. Usually, you will want to grab the `Field`'s inner value, e.g. `tgt.get(Compatibility).value`. (For `AsyncField`s, you would call `await Get[SourcesResult](SourcesRequest, tgt.get(Sources).request)`). This works with subclasses of `Field`s. For example, if you subclass `Sources` to define a custom subclass `PythonSources`, both `python_tgt.get(PythonSources)` and `python_tgt.get(Sources)` will return the same `PythonSources` instance. If the `Field` is not registered on this `Target` type, this will return an instance of the requested Field by using `default_raw_value` to create the instance. Alternatively, first call `tgt.has_field()` or `tgt.has_fields()` to ensure that the field is registered, or, alternatively, use indexing (e.g. `tgt[Compatibility]`) to raise a KeyError when the field is not registered. """ result = self._maybe_get(field) if result is not None: return result return field(raw_value=default_raw_value, address=self.address) @final @classmethod def _has_fields(cls, fields: Iterable[Type[Field]], *, registered_fields: Iterable[Type[Field]]) -> bool: unrecognized_fields = [ field for field in fields if field not in registered_fields ] if not unrecognized_fields: return True for unrecognized_field in unrecognized_fields: maybe_subclass = cls._find_registered_field_subclass( unrecognized_field, registered_fields=registered_fields) if maybe_subclass is None: return False return True @final def has_field(self, field: Type[Field]) -> bool: """Check that this target has registered the requested field. This works with subclasses of `Field`s. For example, if you subclass `Sources` to define a custom subclass `PythonSources`, both `python_tgt.has_field(PythonSources)` and `python_tgt.has_field(Sources)` will return True. """ return self.has_fields([field]) @final def has_fields(self, fields: Iterable[Type[Field]]) -> bool: """Check that this target has registered all of the requested fields. This works with subclasses of `Field`s. For example, if you subclass `Sources` to define a custom subclass `PythonSources`, both `python_tgt.has_fields([PythonSources])` and `python_tgt.has_fields([Sources])` will return True. """ return self._has_fields(fields, registered_fields=self.field_types) @final @classmethod def class_field_types( cls, union_membership: UnionMembership) -> Tuple[Type[Field], ...]: """Return all registered Fields belonging to this target type. You can also use the instance property `tgt.field_types` to avoid having to pass the parameter UnionMembership. """ return (*cls.core_fields, *cls._find_plugin_fields(union_membership)) @final @classmethod def class_has_field(cls, field: Type[Field], *, union_membership: UnionMembership) -> bool: """Behaves like `Target.has_field()`, but works as a classmethod rather than an instance method.""" return cls.class_has_fields([field], union_membership=union_membership) @final @classmethod def class_has_fields(cls, fields: Iterable[Type[Field]], *, union_membership: UnionMembership) -> bool: """Behaves like `Target.has_fields()`, but works as a classmethod rather than an instance method.""" return cls._has_fields(fields, registered_fields=cls.class_field_types( union_membership=union_membership))