lib/metric-config-parser/metric_config_parser/population.py (68 lines of code) (raw):
from typing import TYPE_CHECKING, List, Optional
import attr
if TYPE_CHECKING:
from metric_config_parser.config import ConfigCollection
from metric_config_parser.monitoring import MonitoringSpec
from metric_config_parser.project import ProjectConfiguration
from metric_config_parser.data_source import DataSource, DataSourceReference
from metric_config_parser.dimension import Dimension, DimensionReference
from metric_config_parser.experiment import Channel
@attr.s(auto_attribs=True, kw_only=True)
class PopulationConfiguration:
"""Describes the interface for defining the client population in configuration."""
data_source: Optional[DataSource] = None
boolean_pref: Optional[str] = None
channel: Optional[Channel] = None
branches: List[str] = attr.Factory(list)
monitor_entire_population: bool = False
group_by_dimension: Optional[Dimension] = None
@attr.s(auto_attribs=True, kw_only=True)
class PopulationSpec:
"""Describes the interface for defining the client population."""
data_source: Optional[DataSourceReference] = None
boolean_pref: Optional[str] = None
channel: Optional[Channel] = None
branches: Optional[List[str]] = None
dimensions: List[DimensionReference] = attr.Factory(list)
monitor_entire_population: bool = False
group_by_dimension: Optional[DimensionReference] = None
def resolve(
self,
spec: "MonitoringSpec",
conf: "ProjectConfiguration",
configs: "ConfigCollection",
) -> PopulationConfiguration:
"""Create a `PopulationConfiguration` from the spec."""
if self.group_by_dimension:
if self.group_by_dimension not in self.dimensions:
raise ValueError(
f"{self.group_by_dimension} not listed as dimension, but used for grouping"
)
return PopulationConfiguration(
data_source=(
self.data_source.resolve(spec, conf, configs) if self.data_source else None
),
boolean_pref=self.boolean_pref
or (conf.population.boolean_pref if conf and not conf.is_rollout else None),
channel=self.channel or (conf.population.channel if conf else None),
branches=(
self.branches
if self.branches is not None
else (
[branch for branch in conf.population.branches]
if conf and self.boolean_pref is None and not conf.is_rollout
else []
)
),
monitor_entire_population=self.monitor_entire_population,
group_by_dimension=(
self.group_by_dimension.resolve(spec, conf, configs)
if self.group_by_dimension
else None
),
)
def merge(self, other: "PopulationSpec") -> None:
"""
Merge another population spec into the current one.
The `other` PopulationSpec overwrites existing keys.
"""
for key in attr.fields_dict(type(self)):
if key == "branches":
self.branches = self.branches if self.branches is not None else other.branches
elif key == "dimensions":
self.dimensions += other.dimensions
else:
setattr(self, key, getattr(other, key) or getattr(self, key))