lib/metric-config-parser/metric_config_parser/parameter.py (57 lines of code) (raw):

from collections import defaultdict from typing import Any, Dict, Mapping, Optional, Union import attr from .errors import InvalidConfigurationException from .util import converter @attr.s(auto_attribs=True) class ParameterDefinition: """ * distinct_by_branch * indicates whether value for the parameter needs to be specified for each branch or if the value can be applied across all configuration branches (default: False) """ name: str # implicit in configuration friendly_name: Optional[str] = None description: Optional[str] = None value: Optional[Union[str, Dict[str, str]]] = None distinct_by_branch: Optional[bool] = False default: Optional[Union[str, Dict[str, Any]]] = None def validate(self) -> "ParameterDefinition": """ Validates that branch related configuration is correct. It should not run on every instance of ParameterDefinition object. Outcome parameters containing defaults would not always adhere to the rules defined here. """ if self.distinct_by_branch and not ( isinstance(self.value, dict) or isinstance(self.default, dict) ): error_msg = ( f"Parameter {self.name} configured " "to be distinct by branch, a mapping expected in the following format: " 'for values: value.branch_1 = "1"' 'and defaults: default.branch_1 = "1"' "See https://experimenter.info/jetstream/outcomes#parameterizing-outcomes for more information" # noqa: E501 ) raise InvalidConfigurationException(error_msg) elif not self.distinct_by_branch and not ( isinstance(self.value, str) or isinstance(self.default, str) ): error_msg = ( f"Parameter {self.name} configured " "to not be distinct by branch, but wrong value type provided. " "Expected format: " f'value = "param_value", provided: {self.value}' f'default = "", provided: {self.default}' "See https://experimenter.info/jetstream/outcomes#parameterizing-outcomes for more information" # noqa: E501 ) raise InvalidConfigurationException(error_msg) return self converter.register_structure_hook( ParameterDefinition, lambda obj, _type: ParameterDefinition(**obj) ) @attr.s(auto_attribs=True) class ParameterSpec: """ Object for holding definitions of all parameters. """ definitions: Dict[str, ParameterDefinition] = attr.Factory(dict) @classmethod def from_dict(cls, d: Mapping[str, Any]) -> "ParameterSpec": """ Converts a dictionary object containing parameter configuration into a ParameterSpec Object that contains a ParameterDefinition for each parameter. """ params: Dict[str, Any] = {"definitions": defaultdict()} for param_name, param_config in d.items(): params["definitions"][param_name] = converter.structure( { "name": param_name, **dict((kk.lower(), vv) for kk, vv in param_config.items()), }, ParameterDefinition, ) return cls(**params) converter.register_structure_hook(ParameterSpec, lambda obj, _type: ParameterSpec.from_dict(obj))