Skip to content
This repository has been archived by the owner on May 28, 2024. It is now read-only.

Commit

Permalink
Add CLI for validating configs
Browse files Browse the repository at this point in the history
  • Loading branch information
scholtzan committed Oct 18, 2022
1 parent 8f56edf commit 2d6cefb
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 9 deletions.
95 changes: 95 additions & 0 deletions metric_config_parser/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""CLI."""

import os
import sys
from pathlib import Path
from typing import Iterable

import click

from metric_config_parser.config import (
DEFINITIONS_DIR,
ConfigCollection,
DefinitionConfig,
Outcome,
entity_from_path,
)
from metric_config_parser.function import FunctionsSpec

METRIC_HUB_REPO = "https://github.com/mozilla/metric-hub"


@click.group()
def cli():
"""Initialize CLI."""
pass


@cli.command("validate")
@click.argument("path", type=click.Path(exists=True), nargs=-1)
@click.option(
"--config_repos",
"--config-repos",
help="URLs to public repos with configs",
multiple=True,
default=[METRIC_HUB_REPO],
)
@click.option(
"--private_config_repos",
"--private-config-repos",
help="URLs to private repos with configs",
multiple=True,
)
def validate(path: Iterable[os.PathLike], config_repos, private_config_repos):
"""Validate config files."""
dirty = False
config_collection = ConfigCollection.from_github_repos(config_repos).from_github_repos(
private_config_repos, is_private=True
)

# get updated definition files
for config_file in path:
config_file = Path(config_file)
if not config_file.is_file():
continue
if ".example" in config_file.suffixes:
print(f"Skipping example config {config_file}")
continue

if config_file.parent.name == DEFINITIONS_DIR:
entity = entity_from_path(config_file)
try:
if isinstance(entity, Outcome):
entity.validate(config_collection)
elif not isinstance(entity, FunctionsSpec):
entity.validate(config_collection, None) # type: ignore
except Exception as e:
dirty = True
print(e)
else:
print(f"{config_file} OK")

if isinstance(entity, DefinitionConfig):
config_collection.definitions.append(entity)

for config_file in path:
config_file = Path(config_file)
if config_file.parent.name == DEFINITIONS_DIR:
continue
if not config_file.is_file():
continue
if ".example" in config_file.suffixes:
print(f"Skipping example config {config_file}")
continue
print(f"Evaluating {config_file}...")
entity = entity_from_path(config_file)
try:
if not isinstance(entity, FunctionsSpec) and not isinstance(entity, Outcome):
entity.validate(config_collection, None) # type: ignore
except Exception as e:
dirty = True
print(e)
else:
print(f"{config_file} OK")

sys.exit(1 if dirty else 0)
20 changes: 12 additions & 8 deletions metric_config_parser/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def validate_config_settings(config_file: Path) -> None:
"parameters",
"alerts",
"dimensions",
"functions",
)

core_config_keys_specified = config.keys()
Expand Down Expand Up @@ -200,7 +201,7 @@ def validate(self, configs: "ConfigCollection", _experiment: Experiment = None)

def entity_from_path(
path: Path, is_private: bool = False
) -> Union[Config, Outcome, DefaultConfig, DefinitionConfig]:
) -> Union[Config, Outcome, DefaultConfig, DefinitionConfig, FunctionsSpec]:
is_outcome = path.parent.parent.name == OUTCOMES_DIR
is_default_config = path.parent.name == DEFAULTS_DIR
is_definition_config = path.parent.name == DEFINITIONS_DIR
Expand Down Expand Up @@ -233,13 +234,16 @@ def entity_from_path(
is_private=is_private,
)
elif is_definition_config:
return DefinitionConfig(
slug=slug,
spec=DefinitionSpec.from_dict(config_dict),
last_modified=dt.datetime.fromtimestamp(path.stat().st_mtime, UTC),
platform=slug,
is_private=is_private,
)
if path.name == FUNCTIONS_FILE:
return FunctionsSpec.from_dict(config_dict)
else:
return DefinitionConfig(
slug=slug,
spec=DefinitionSpec.from_dict(config_dict),
last_modified=dt.datetime.fromtimestamp(path.stat().st_mtime, UTC),
platform=slug,
is_private=is_private,
)

if "project" in config_dict:
# config is from opmon
Expand Down
3 changes: 3 additions & 0 deletions metric_config_parser/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class DataSource:

EXPERIMENT_COLUMN_TYPES = (None, "simple", "native", "glean")

def __attrs_post_init__(self):
object.__setattr__(self, "from_expression", self.from_expr_for(self.default_dataset))

@experiments_column_type.validator
def _check_experiments_column_type(self, attribute, value):
if value not in self.EXPERIMENT_COLUMN_TYPES:
Expand Down
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,9 @@ def text_from_file(path):
long_description=text_from_file("README.md"),
long_description_content_type="text/markdown",
python_requires=">=3.6",
version="2022.10.7",
entry_points="""
[console_scripts]
metric-config-parser=metric_config_parser.cli:cli
""",
version="2022.10.8",
)

0 comments on commit 2d6cefb

Please sign in to comment.