Skip to content
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
36 changes: 36 additions & 0 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Run Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
# Checkout the repository
- name: Checkout code
uses: actions/checkout@v3

# Set up Python
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10" # Match the Python version specified in pyproject.toml

# Install PDM
- name: Install PDM
run: python -m pip install --upgrade pip pdm

# Install dependencies using PDM
- name: Install dependencies
run: pdm install --dev

# Run tests
- name: Run tests
run: pdm run pytest
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,9 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.pdm-python

.pdm*

# default location to write files
cubes/
views/
examples/


Expand Down
3 changes: 1 addition & 2 deletions lkml2cube/parser/explores.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import re
import traceback
import rich

from pprint import pformat

from lkml2cube.parser.views import parse_view
from lkml2cube.parser.types import console

snake_case = r"\{([a-zA-Z]+(?:_[a-zA-Z]+)*\.[a-zA-Z]+(?:_[a-zA-Z]+)*)\}"
console = rich.console.Console()


def snakify(s):
Expand Down
3 changes: 2 additions & 1 deletion lkml2cube/parser/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from os.path import abspath, dirname, join
from pathlib import Path

from lkml2cube.parser.types import console

visited_path = {}
console = rich.console.Console()


def update_namespace(namespace, new_file):
Expand Down
11 changes: 11 additions & 0 deletions lkml2cube/parser/types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import rich


# console = rich.console.Console()
class Console:
def print(self, s, *args):
print(s)


console = Console()

type_map = {
"zipcode": "string",
"string": "string",
Expand Down
29 changes: 22 additions & 7 deletions lkml2cube/parser/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import copy
import traceback
import rich

from pprint import pformat
from lkml2cube.parser.types import type_map, literal_unicode

console = rich.console.Console()
from lkml2cube.parser.types import type_map, literal_unicode, folded_unicode, console


def parse_view(lookml_model, raise_when_views_not_present=True):
cubes = []
cube_def = {"cubes": cubes}
rpl_table = lambda s: s.replace("${TABLE}", "{CUBE}").replace("${", "{")
convert_to_literal = lambda s: (
literal_unicode(rpl_table(s)) if "\n" in s else rpl_table(s)
)
sets = {}

if raise_when_views_not_present and "views" not in lookml_model:
Expand Down Expand Up @@ -102,10 +102,21 @@ def parse_view(lookml_model, raise_when_views_not_present=True):

cube_dimension = {
"name": dimension["name"],
"sql": rpl_table(dimension["sql"]),
"sql": convert_to_literal(dimension["sql"]),
"type": type_map[dimension["type"]],
}

if "primary_key" in dimension:
cube_dimension["primary_key"] = bool(
dimension["primary_key"] == "yes"
)

if "label" in dimension:
cube_dimension["title"] = dimension["label"]

if "description" in dimension:
cube_dimension["description"] = dimension["description"]

if "hidden" in dimension:
cube_dimension["public"] = not bool(dimension["hidden"] == "yes")

Expand All @@ -121,13 +132,17 @@ def parse_view(lookml_model, raise_when_views_not_present=True):
)
continue
if len(bins) < 2:
console.print(
f'Dimension type: {dimension["type"]} requires more than 1 tiers',
style="bold red",
)
pass
else:
tier_sql = f"CASE "
for i in range(0, len(bins) - 1):
tier_sql += f" WHEN {cube_dimension['sql']} >= {bins[i]} AND {cube_dimension['sql']} < {bins[i + 1]} THEN {bins[i]} "
tier_sql += "ELSE NULL END"
cube_dimension["sql"] = tier_sql
cube_dimension["sql"] = folded_unicode(tier_sql)
cube["dimensions"].append(cube_dimension)

for measure in view.get("measures", []):
Expand All @@ -145,7 +160,7 @@ def parse_view(lookml_model, raise_when_views_not_present=True):
cube_measure["public"] = not bool(measure["hidden"] == "yes")

if measure["type"] != "count":
cube_measure["sql"] = rpl_table(measure["sql"])
cube_measure["sql"] = convert_to_literal(measure["sql"])
elif "drill_fields" in measure:
drill_members = []
for drill_field in measure["drill_fields"]:
Expand Down
Empty file added lkml2cube/tests/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions lkml2cube/tests/samples/cubeml/orders.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
cubes:
- description: Orders
dimensions:
- name: id
primary_key: true
sql: '{CUBE}."ID"'
type: number
- description: My description
name: item_id
sql: '{CUBE}.item_id'
title: Item ID
type: number
- name: order_status
sql: '{CUBE}."STATUS"'
type: string
- name: is_cancelled
sql: case {CUBE}."STATUS" when "CANCELLED" then true else false end
title: Is Cancelled
type: boolean
- name: created_at
sql: '{CUBE}."CREATED_AT"'
type: time
- name: completed_at
sql: '{CUBE}."COMPLETED_AT"'
type: time
joins: []
measures:
- name: count
type: count
- name: order_count_distinct
sql: '{id}'
type: count_distinct_approx
name: orders
sql_table: '{{_user_attributes[''ecom_database'']}}.{{_user_attributes[''ecom_schema'']}}."ORDERS"'
39 changes: 39 additions & 0 deletions lkml2cube/tests/samples/lkml/explores/orders_summary.model.lkml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
connection: "my_connection"

include: "/views/*.view.lkml" # include all views in the views/ folder in this project
# include: "/**/*.view.lkml" # include all views in this project
# include: "/**/*.dashboard.lookml" # include a LookML dashboard called my_dashboard


explore: orders {
label: "Orders Summary"

join: line_items {
relationship: one_to_many
sql_on: ${orders.id} = ${line_items.order_id} ;;
type: left_outer
}

join: products {
relationship: many_to_one
sql_on: ${line_items.product_id} = ${products.id} ;;
type: left_outer
}

}

explore: line_items {
label: "Line Items Summary"

join: orders {
relationship: many_to_one
sql_on: ${line_items.order_id} = ${orders.id} ;;
type: left_outer
}

join: products {
relationship: many_to_one
sql_on: ${line_items.product_id} = ${products.id} ;;
type: left_outer
}
}
90 changes: 90 additions & 0 deletions lkml2cube/tests/samples/lkml/views/line_items.view.lkml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# The name of this view in Looker is "Line Items"
view: line_items {
view_label: "Line Items"
# The sql_table_name parameter indicates the underlying database table
# to be used for all fields in this view.
sql_table_name: {{_user_attributes['ecom_database']}}.{{_user_attributes['ecom_schema']}}."LINE_ITEMS"
;;
# In order to join this view in an Explore,
# define primary_key: yes on a dimension that has no repeated values.

dimension: id {
primary_key: yes
type: number
sql: ${TABLE}."ID" ;;
}

# This table contains a foreign key to other tables.
# Joins are defined in explores
dimension: order_id {
hidden: yes
type: number
sql: ${TABLE}."ORDER_ID" ;;
}

dimension: product_id {
hidden: yes
type: number
sql: ${TABLE}."PRODUCT_ID" ;;
}

dimension: price {
type: number
sql: ${TABLE}."PRICE" ;;
}

dimension: quantity {
label: "Quantity"
type: number
sql: ${TABLE}."QUANTITY" ;;
}

# You can reference other dimensions while defining a dimension.
dimension: line_amount {
type: number
sql: ${quantity} * ${price};;
}

dimension: quantity_bins {
type: tier
style: integer
bins: [0,10,50,100]
sql: ${quantity} ;;
}

# A measure is a field that uses a SQL aggregate function. Here are defined sum and count
# measures for this view, but you can also add measures of many different aggregates.

measure: total_quantity {
type: sum
sql: ${quantity} ;;
}

measure: total_amount {
type: sum
sql: ${line_amount} ;;
}

measure: count {
type: count
}


# Dates and timestamps can be represented in Looker using a dimension group of type: time.
# Looker converts dates and timestamps to the specified timeframes within the dimension group.

dimension_group: created_at {
type: time
timeframes: [
raw,
time,
date,
week,
month,
quarter,
year
]
sql: ${TABLE}."CREATED_AT" ;;
}

}
Loading