lookml-generator/generator/lookml.py

182 строки
6.3 KiB
Python
Исходник Обычный вид История

"""Generate lookml from namespaces."""
import logging
from pathlib import Path
2021-05-06 00:22:45 +03:00
from typing import Dict, Iterable, Optional
import click
import lkml
import yaml
from google.cloud import bigquery
2021-10-13 18:37:51 +03:00
from .dashboards import DASHBOARD_TYPES
from .explores import EXPLORE_TYPES
from .metrics_utils import LOOKER_METRIC_HUB_REPO, METRIC_HUB_REPO, MetricsConfigLoader
from .namespaces import _get_glean_apps
from .views import VIEW_TYPES, View, ViewDict
from .views.datagroups import generate_datagroups
FILE_HEADER = """
# *Do not manually modify this file*
#
# This file has been generated via https://github.com/mozilla/lookml-generator
# You can extend this view in the looker-spoke-default project (https://github.com/mozilla/looker-spoke-default)
"""
2021-05-06 00:22:45 +03:00
def _generate_views(
client, out_dir: Path, views: Iterable[View], v1_name: Optional[str]
) -> Iterable[Path]:
2021-04-14 14:56:17 +03:00
for view in views:
2021-05-06 20:41:10 +03:00
logging.info(
f"Generating lookml for view {view.name} in {view.namespace} of type {view.view_type}"
)
2021-04-14 14:56:17 +03:00
path = out_dir / f"{view.name}.view.lkml"
lookml = view.to_lookml(client, v1_name)
if lookml == {}:
continue
2022-11-03 21:09:16 +03:00
# lkml.dump may return None, in which case write an empty file
path.write_text(FILE_HEADER + (lkml.dump(lookml) or ""))
yield path
def _generate_explores(
client,
out_dir: Path,
namespace: str,
explores: dict,
views_dir: Path,
v1_name: Optional[
str
], # v1_name for Glean explores: see: https://mozilla.github.io/probe-scraper/#tag/library
) -> Iterable[Path]:
for explore_name, defn in explores.items():
logging.info(f"Generating lookml for explore {explore_name} in {namespace}")
explore = EXPLORE_TYPES[defn["type"]].from_dict(explore_name, defn, views_dir)
file_lookml = {
2021-04-16 04:42:11 +03:00
# Looker validates all included files,
# so if we're not explicit about files here, validation takes
# forever as looker re-validates all views for every explore (if we used *).
"includes": [
f"/looker-hub/{namespace}/views/{view}.view.lkml"
for view in explore.get_dependent_views()
],
"explores": explore.to_lookml(client, v1_name),
}
path = out_dir / (explore_name + ".explore.lkml")
2022-11-03 21:09:16 +03:00
# lkml.dump may return None, in which case write an empty file
path.write_text(FILE_HEADER + (lkml.dump(file_lookml) or ""))
yield path
2021-10-13 18:37:51 +03:00
def _generate_dashboards(
client,
dash_dir: Path,
namespace: str,
dashboards: dict,
2021-10-13 18:37:51 +03:00
):
for dashboard_name, dashboard_info in dashboards.items():
logging.info(f"Generating lookml for dashboard {dashboard_name} in {namespace}")
dashboard = DASHBOARD_TYPES[dashboard_info["type"]].from_dict(
namespace, dashboard_name, dashboard_info
)
dashboard_lookml = dashboard.to_lookml(client)
2021-10-13 18:37:51 +03:00
dash_path = dash_dir / f"{dashboard_name}.dashboard.lookml"
dash_path.write_text(FILE_HEADER + dashboard_lookml)
2021-10-13 18:37:51 +03:00
yield dash_path
2021-05-06 00:22:45 +03:00
def _get_views_from_dict(views: Dict[str, ViewDict], namespace: str) -> Iterable[View]:
2021-04-14 14:56:17 +03:00
for view_name, view_info in views.items():
2021-05-03 20:34:36 +03:00
yield VIEW_TYPES[view_info["type"]].from_dict( # type: ignore
2021-05-05 20:28:48 +03:00
namespace, view_name, view_info
2021-05-03 20:37:13 +03:00
)
2021-04-14 14:56:17 +03:00
2021-05-06 00:22:45 +03:00
def _glean_apps_to_v1_map(glean_apps):
return {d["name"]: d["v1_name"] for d in glean_apps}
def _lookml(namespaces, glean_apps, target_dir):
client = bigquery.Client()
namespaces_content = namespaces.read()
_namespaces = yaml.safe_load(namespaces_content)
target = Path(target_dir)
target.mkdir(parents=True, exist_ok=True)
# Write namespaces file to target directory, for use
# by the Glean Dictionary and other tools
with open(target / "namespaces.yaml", "w") as target_namespaces_file:
target_namespaces_file.write(namespaces_content)
2021-05-06 00:22:45 +03:00
v1_mapping = _glean_apps_to_v1_map(glean_apps)
2021-04-14 14:56:17 +03:00
for namespace, lookml_objects in _namespaces.items():
logging.info(f"\nGenerating namespace {namespace}")
view_dir = target / namespace / "views"
view_dir.mkdir(parents=True, exist_ok=True)
views = list(_get_views_from_dict(lookml_objects.get("views", {}), namespace))
logging.info(" Generating views")
2021-05-06 00:22:45 +03:00
v1_name: Optional[str] = v1_mapping.get(namespace)
for view_path in _generate_views(client, view_dir, views, v1_name):
logging.info(f" ...Generating {view_path}")
logging.info(" Generating datagroups")
generate_datagroups(views, target, namespace, client)
explore_dir = target / namespace / "explores"
explore_dir.mkdir(parents=True, exist_ok=True)
2021-04-14 14:56:17 +03:00
explores = lookml_objects.get("explores", {})
logging.info(" Generating explores")
for explore_path in _generate_explores(
client, explore_dir, namespace, explores, view_dir, v1_name
):
logging.info(f" ...Generating {explore_path}")
2021-10-13 18:37:51 +03:00
logging.info(" Generating dashboards")
dashboard_dir = target / namespace / "dashboards"
2021-10-13 18:37:51 +03:00
dashboard_dir.mkdir(parents=True, exist_ok=True)
dashboards = lookml_objects.get("dashboards", {})
for dashboard_path in _generate_dashboards(
client, dashboard_dir, namespace, dashboards
2021-10-13 18:37:51 +03:00
):
logging.info(f" ...Generating {dashboard_path}")
@click.command(help=__doc__)
@click.option(
"--namespaces",
default="namespaces.yaml",
type=click.File(),
help="Path to a yaml namespaces file",
)
@click.option(
"--app-listings-uri",
default="https://probeinfo.telemetry.mozilla.org/v2/glean/app-listings",
help="URI for probeinfo service v2 glean app listings",
)
@click.option(
"--target-dir",
default="looker-hub/",
type=click.Path(),
help="Path to a directory where lookml will be written",
)
@click.option(
"--metric-hub-repos",
"--metric-hub-repos",
multiple=True,
default=[METRIC_HUB_REPO, LOOKER_METRIC_HUB_REPO],
help="Repos to load metric configs from.",
)
def lookml(namespaces, app_listings_uri, target_dir, metric_hub_repos):
"""Generate lookml from namespaces."""
if metric_hub_repos:
MetricsConfigLoader.update_repos(metric_hub_repos)
glean_apps = _get_glean_apps(app_listings_uri)
return _lookml(namespaces, glean_apps, target_dir)