Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create ConfigCollection from non-git path #683

Merged
merged 3 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions lib/metric-config-parser/metric_config_parser/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,3 +805,146 @@ def merge(self, other: "ConfigCollection"):
self.functions.functions = functions

self.repos += other.repos


@attr.s(auto_attribs=True)
class LocalConfigCollection(ConfigCollection):
"""
Collection of experiment-specific configurations pulled in
from a local directory (structured like metric-hub).

Unless you are Experimenter, you likely want to use ConfigCollection instead.
"""

@classmethod
def from_local_path(cls, path: str, is_private: bool = False) -> "ConfigCollection":
"""Load configs from a local non-repo copy of a metric-hub-like folder structure.

Args:
path (str): path to the configs. Looks for TOML files in the
following locations (all of which are optional):
- . (root) - creates Config
- outcomes - creates Outcome
- defaults - creates DefaultConfig
- definitions - creates DefinitionConfig
is_private (bool): whether the configs are private
"""

files_path = Path(path)

external_configs = []
last_modified = dt.datetime.now()
for config_file in files_path.glob("*.toml"):
config_json = toml.load(config_file)

if "project" in config_json:
# opmon spec
spec: DefinitionSpecSub = MonitoringSpec.from_dict(config_json)
else:
spec = AnalysisSpec.from_dict(config_json)
spec.experiment.is_private = spec.experiment.is_private or is_private

external_configs.append(
Config(
config_file.stem,
spec,
last_modified,
is_private=is_private,
)
)

outcomes = []
for outcome_file in files_path.glob(f"{OUTCOMES_DIR}/*/*.toml"):
outcomes.append(
Outcome(
slug=outcome_file.stem,
spec=OutcomeSpec.from_dict(toml.load(outcome_file)),
platform=outcome_file.parent.name,
commit_hash=None,
is_private=is_private,
)
)

default_configs = []
for default_config_file in files_path.glob(f"{DEFAULTS_DIR}/*.toml"):
default_config_json = toml.load(default_config_file)

if "project" in default_config_json:
# opmon spec
spec = MonitoringSpec.from_dict(default_config_json)
else:
spec = AnalysisSpec.from_dict(default_config_json)
spec.experiment.is_private = spec.experiment.is_private or is_private

default_configs.append(
DefaultConfig(
default_config_file.stem,
spec,
last_modified,
is_private=is_private,
)
)

definitions = []
for definitions_config_file in files_path.glob(f"{DEFINITIONS_DIR}/*.toml"):
definitions.append(
DefinitionConfig(
definitions_config_file.stem,
DefinitionSpec.from_dict(toml.load(definitions_config_file)),
last_modified,
platform=definitions_config_file.stem,
is_private=is_private,
)
)

functions_spec = None
for functions_file in files_path.glob(f"{DEFINITIONS_DIR}/{FUNCTIONS_FILE}"):
functions_spec = FunctionsSpec.from_dict(toml.load(functions_file))

return cls(
external_configs,
outcomes,
default_configs,
definitions,
functions_spec,
repos=[],
is_private=is_private,
)

@classmethod
def from_github_repo(
cls,
repo_url: Optional[str] = None,
is_private: bool = False,
path: Optional[str] = None,
depth: Optional[int] = None,
):
raise NotImplementedError(
"`from_github_repo` is not valid for non-repo-based LocalConfigCollection. "
+ "Use ConfigCollection for this feature."
)

@classmethod
def from_github_repos(
cls, repo_urls: Optional[List[str]] = None, is_private: bool = False
) -> "ConfigCollection":
raise NotImplementedError(
"`from_github_repos` is not valid for non-repo-based LocalConfigCollection. "
+ "Use ConfigCollection for this feature."
)

@classmethod
def from_local_repo(
cls, repo, path, is_private, main_branch, is_tmp_repo=False
) -> "ConfigCollection":
raise NotImplementedError(
"`from_local_repo` is not valid for non-repo-based LocalConfigCollection. "
+ "Use ConfigCollection for this feature."
)

@classmethod
def as_of(self, timestamp: datetime) -> "ConfigCollection":
raise NotImplementedError(
"`as_of` is not valid for non-repo-based LocalConfigCollection. "
+ "Use ConfigCollection for this feature."
)
21 changes: 21 additions & 0 deletions lib/metric-config-parser/metric_config_parser/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
ConfigCollection,
DefaultConfig,
DefinitionConfig,
LocalConfigCollection,
Outcome,
)
from metric_config_parser.data_source import DataSourceJoinRelationship
Expand Down Expand Up @@ -910,3 +911,23 @@ def test_get_segments_for_app_with_toml(self):

segment_slugs_fenix = [seg.name for seg in segments_fenix]
assert "fenix_segment" in segment_slugs_fenix

def test_local_config_collection_from_local_path(self):
config_collection = LocalConfigCollection.from_local_path(TEST_DIR / "data")
segments = [s.name for s in config_collection.get_segments_for_app("firefox_desktop")]
assert "regular_users_v3" in segments
assert len(segments) == 1

outcomes = [o.slug for o in config_collection.outcomes]
assert len(outcomes) == 0

test_metric = config_collection.get_metric_definition("active_hours", "firefox_desktop")
assert test_metric.name == "active_hours"
assert test_metric.select_expression == '{{agg_sum("active_hours_sum")}}'
assert test_metric.data_source.name == "clients_daily"

cc_jetstream = LocalConfigCollection.from_local_path(TEST_DIR / "data" / "jetstream")
config_collection.merge(cc_jetstream)

outcomes = [o.slug for o in config_collection.outcomes]
assert len(outcomes) == 4
2 changes: 1 addition & 1 deletion lib/metric-config-parser/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "mozilla-metric-config-parser"
version = "2024.10.2"
version = "2024.11.1"
authors = [{ name = "Mozilla Corporation", email = "[email protected]" }]
description = "Parses metric configuration files"
readme = "README.md"
Expand Down
Loading