lib/metric-config-parser/metric_config_parser/exposure_signal.py (62 lines of code) (raw):
import enum
from textwrap import dedent
from typing import TYPE_CHECKING, Any, Type, Union
import attr
if TYPE_CHECKING:
from .config import ConfigCollection
from .analysis import AnalysisSpec
from .experiment import ExperimentConfiguration
from .data_source import DataSource, DataSourceReference
from .util import converter
class AnalysisWindow(enum.Enum):
"""
Predefined timelimits that can be used for defining when exposures
should be computed.
"""
ANALYSIS_WINDOW_START = "analysis_window_start"
ANALYSIS_WINDOW_END = "analysis_window_end"
ENROLLMENT_START = "enrollment_start"
ENROLLMENT_END = "enrollment_end"
WindowLimit = Union[int, AnalysisWindow, None]
def structure_window_limit(value: Any, _klass: Type) -> WindowLimit:
try:
return AnalysisWindow(value)
except Exception:
return int(value)
converter.register_structure_hook(WindowLimit, structure_window_limit)
@attr.s(auto_attribs=True, frozen=True, slots=True)
class ExposureSignal:
"""
Jetstream exposure signal representation.
Jetstream exposure signals are supersets of mozanalysis exposure signals
with some additional metdata required for analysis.
"""
name: str
data_source: DataSource
select_expression: str
friendly_name: str
description: str
window_start: WindowLimit = attr.ib(None)
window_end: WindowLimit = attr.ib(None)
@window_end.validator
@window_start.validator
def validate_window(self, _attribute, value):
if value is not None and not isinstance(value, int):
AnalysisWindow(value)
@attr.s(auto_attribs=True)
class ExposureSignalDefinition:
"""Describes the interface for defining an exposure signal in configuration."""
name: str
data_source: DataSourceReference
select_expression: str
friendly_name: str
description: str
window_start: WindowLimit = None
window_end: WindowLimit = None
def resolve(
self,
spec: "AnalysisSpec",
conf: "ExperimentConfiguration",
configs: "ConfigCollection",
) -> ExposureSignal:
return ExposureSignal(
name=self.name,
data_source=self.data_source.resolve(spec, conf=conf, configs=configs),
select_expression=self.select_expression,
friendly_name=(
dedent(self.friendly_name) if self.friendly_name else self.friendly_name
),
description=(dedent(self.description) if self.description else self.description),
window_start=self.window_start,
window_end=self.window_end,
)