From 25367a54d5e71f243d115d9dc77447f099597656 Mon Sep 17 00:00:00 2001 From: Paco Valdez Date: Wed, 16 Apr 2025 11:03:42 -0700 Subject: [PATCH 1/3] Add tests --- .gitignore | 2 - lkml2cube/parser/explores.py | 3 +- lkml2cube/parser/loader.py | 3 +- lkml2cube/parser/types.py | 11 ++ lkml2cube/parser/views.py | 29 +++- lkml2cube/tests/__init__.py | 0 lkml2cube/tests/e2e.py | 45 ++++++ lkml2cube/tests/samples/cubeml/orders.yml | 34 +++++ .../lkml/explores/orders_summary.model.lkml | 39 +++++ .../samples/lkml/views/line_items.view.lkml | 90 ++++++++++++ .../tests/samples/lkml/views/orders.view.lkml | 82 +++++++++++ .../samples/lkml/views/products.view.lkml | 52 +++++++ pdm.lock | 133 ++++++++++-------- pyproject.toml | 5 +- 14 files changed, 456 insertions(+), 72 deletions(-) create mode 100644 lkml2cube/tests/__init__.py create mode 100644 lkml2cube/tests/e2e.py create mode 100644 lkml2cube/tests/samples/cubeml/orders.yml create mode 100644 lkml2cube/tests/samples/lkml/explores/orders_summary.model.lkml create mode 100644 lkml2cube/tests/samples/lkml/views/line_items.view.lkml create mode 100644 lkml2cube/tests/samples/lkml/views/orders.view.lkml create mode 100644 lkml2cube/tests/samples/lkml/views/products.view.lkml diff --git a/.gitignore b/.gitignore index ad278db..2cecef0 100644 --- a/.gitignore +++ b/.gitignore @@ -162,8 +162,6 @@ cython_debug/ # default location to write files -cubes/ -views/ examples/ diff --git a/lkml2cube/parser/explores.py b/lkml2cube/parser/explores.py index 9e15802..2cc875c 100644 --- a/lkml2cube/parser/explores.py +++ b/lkml2cube/parser/explores.py @@ -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): diff --git a/lkml2cube/parser/loader.py b/lkml2cube/parser/loader.py index 9d6747f..43ea0e6 100644 --- a/lkml2cube/parser/loader.py +++ b/lkml2cube/parser/loader.py @@ -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): diff --git a/lkml2cube/parser/types.py b/lkml2cube/parser/types.py index 9a0aa78..679f757 100644 --- a/lkml2cube/parser/types.py +++ b/lkml2cube/parser/types.py @@ -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", diff --git a/lkml2cube/parser/views.py b/lkml2cube/parser/views.py index 3efe8de..4882f42 100644 --- a/lkml2cube/parser/views.py +++ b/lkml2cube/parser/views.py @@ -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: @@ -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") @@ -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", []): @@ -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"]: diff --git a/lkml2cube/tests/__init__.py b/lkml2cube/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lkml2cube/tests/e2e.py b/lkml2cube/tests/e2e.py new file mode 100644 index 0000000..335d47a --- /dev/null +++ b/lkml2cube/tests/e2e.py @@ -0,0 +1,45 @@ +import pytest +import yaml + +from os.path import join, dirname + +from lkml2cube.parser.loader import file_loader +from lkml2cube.parser.views import parse_view +from lkml2cube.parser.explores import parse_explores, generate_cube_joins + +# Dynamically calculate the root directory +rootdir = join(dirname(__file__), "samples") + + +class TestExamples: + def test_simple_view(self): + file_path = "lkml/views/orders.view.lkml" + # print(join(rootdir, file_path)) + lookml_model = file_loader(join(rootdir, file_path), rootdir) + + # lookml_model can't be None + # if None it means file was not found or couldn't be parsed + assert lookml_model is not None + + cube_def = parse_view(lookml_model) + cube_def = generate_cube_joins(cube_def, lookml_model) + + # Convert the generated cube definition to a dictionary + generated_yaml = yaml.safe_load(yaml.dump(cube_def, allow_unicode=True)) + + # print("Expected yaml:") + # print(yaml.dump(generated_yaml, allow_unicode=True)) + + file_path = "cubeml/orders.yml" + # print(join(rootdir, file_path)) + with open(join(rootdir, file_path)) as f: + cube_model = yaml.safe_load(f) + + # print(cube_model) + + # Compare the two dictionaries + assert ( + generated_yaml == cube_model + ), "Generated YAML does not match the expected YAML" + + assert True diff --git a/lkml2cube/tests/samples/cubeml/orders.yml b/lkml2cube/tests/samples/cubeml/orders.yml new file mode 100644 index 0000000..468abc1 --- /dev/null +++ b/lkml2cube/tests/samples/cubeml/orders.yml @@ -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"' \ No newline at end of file diff --git a/lkml2cube/tests/samples/lkml/explores/orders_summary.model.lkml b/lkml2cube/tests/samples/lkml/explores/orders_summary.model.lkml new file mode 100644 index 0000000..b3fce41 --- /dev/null +++ b/lkml2cube/tests/samples/lkml/explores/orders_summary.model.lkml @@ -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 + } +} diff --git a/lkml2cube/tests/samples/lkml/views/line_items.view.lkml b/lkml2cube/tests/samples/lkml/views/line_items.view.lkml new file mode 100644 index 0000000..4f3ff0e --- /dev/null +++ b/lkml2cube/tests/samples/lkml/views/line_items.view.lkml @@ -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" ;; + } + +} \ No newline at end of file diff --git a/lkml2cube/tests/samples/lkml/views/orders.view.lkml b/lkml2cube/tests/samples/lkml/views/orders.view.lkml new file mode 100644 index 0000000..4f24a0c --- /dev/null +++ b/lkml2cube/tests/samples/lkml/views/orders.view.lkml @@ -0,0 +1,82 @@ +# The name of this view in Looker is "Orders" +view: orders { + view_label: "Orders" + # 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']}}."ORDERS" + ;; + # 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" ;; + } + + dimension: item_id { + label: "Item ID" + description: "My description" + type: number + sql: ${TABLE}.item_id ;; + } + + # Here's what a typical dimension looks like in LookML. + # A dimension is a groupable field that can be used to filter query results. + # This dimension will be called "Order Status" in Explore. + + dimension: order_status { + type: string + sql: ${TABLE}."STATUS" ;; + } + + dimension: is_cancelled { + label: "Is Cancelled" + type: yesno + sql: case ${TABLE}."STATUS" when "CANCELLED" then true else false end ;; + } + + # A measure is a field that uses a SQL aggregate function. Here are defined count and count_distinct + # measures for this view, but you can also add measures of many different aggregates. + + measure: count { + type: count + } + + measure: order_count_distinct { + type: count_distinct + sql: ${id} ;; + } + + + # 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" ;; + } + + dimension_group: completed_at { + type: time + timeframes: [ + raw, + time, + date, + week, + month, + quarter, + year + ] + sql: ${TABLE}."COMPLETED_AT" ;; + } +} \ No newline at end of file diff --git a/lkml2cube/tests/samples/lkml/views/products.view.lkml b/lkml2cube/tests/samples/lkml/views/products.view.lkml new file mode 100644 index 0000000..aebde0f --- /dev/null +++ b/lkml2cube/tests/samples/lkml/views/products.view.lkml @@ -0,0 +1,52 @@ +# The name of this view in Looker is "Products" +view: products { + view_label: "Products" + # 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']}}."PRODUCTS" + ;; + # 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" ;; + } + + # Here's what a typical dimension looks like in LookML. + # A dimension is a groupable field that can be used to filter query results. + # This dimension will be called "Product Category" in Explore. + + dimension: product_category { + type: string + sql: ${TABLE}."PRODUCT_CATEGORY" ;; + } + + + # A measure is a field that uses a SQL aggregate function. Here are defined count + # measure for this view, but you can also add measures of many different aggregates. + + 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" ;; + } + +} \ No newline at end of file diff --git a/pdm.lock b/pdm.lock index 4b7a87e..d864187 100644 --- a/pdm.lock +++ b/pdm.lock @@ -4,8 +4,11 @@ [metadata] groups = ["default", "test"] strategy = ["cross_platform"] -lock_version = "4.4.1" -content_hash = "sha256:420dc0a897ee43816e72ea3065456f4a60cf7786e45e6826339201b09f93260b" +lock_version = "4.5.0" +content_hash = "sha256:55af3a669132a1facf2c999b3e1e72ec37b5a7003b0900534da9ab84d4b758d3" + +[[metadata.targets]] +requires_python = ">=3.10" [[package]] name = "click" @@ -14,6 +17,7 @@ requires_python = ">=3.7" summary = "Composable command line interface toolkit" dependencies = [ "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", ] files = [ {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, @@ -52,11 +56,11 @@ files = [ [[package]] name = "lkml" -version = "1.3.1" +version = "1.3.7" summary = "A speedy LookML parser implemented in pure Python." files = [ - {file = "lkml-1.3.1-py2.py3-none-any.whl", hash = "sha256:23fb8c340abe9380efa7682f1613bd448831e2525dbdb9f8d38fe39ce58f5434"}, - {file = "lkml-1.3.1.tar.gz", hash = "sha256:33d2bb95be349661ad4ef8508f93302bc11177e8553bafdc2420c405a36b5371"}, + {file = "lkml-1.3.7-py2.py3-none-any.whl", hash = "sha256:ce54c517f81fbd21d452038be9e2504fa02951a5bc30f7d7f1eb552c1f3f2b39"}, + {file = "lkml-1.3.7.tar.gz", hash = "sha256:51dc9f1b7e74cd7a00e0dbbf06fb573952015328f1f4a3a0730d444444a8ae7a"}, ] [[package]] @@ -94,12 +98,12 @@ files = [ [[package]] name = "pluggy" -version = "1.2.0" -requires_python = ">=3.7" +version = "1.5.0" +requires_python = ">=3.8" summary = "plugin and hook calling mechanisms for python" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [[package]] @@ -114,66 +118,80 @@ files = [ [[package]] name = "pytest" -version = "7.4.0" -requires_python = ">=3.7" +version = "8.3.5" +requires_python = ">=3.8" summary = "pytest: simple powerful testing with Python" dependencies = [ "colorama; sys_platform == \"win32\"", "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", "iniconfig", "packaging", - "pluggy<2.0,>=0.12", - "tomli>=1.0.0; python_version < \"3.11\"", + "pluggy<2,>=1.5", + "tomli>=1; python_version < \"3.11\"", ] files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, + {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, ] [[package]] name = "pyyaml" -version = "6.0.1" -requires_python = ">=3.6" +version = "6.0.2" +requires_python = ">=3.8" summary = "YAML parser and emitter for Python" files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "rich" -version = "13.7.1" -requires_python = ">=3.7.0" +version = "14.0.0" +requires_python = ">=3.8.0" summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" dependencies = [ "markdown-it-py>=2.2.0", "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.11\"", ] files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, ] [[package]] @@ -198,33 +216,32 @@ files = [ [[package]] name = "typer" -version = "0.9.0" -requires_python = ">=3.6" +version = "0.15.2" +requires_python = ">=3.7" summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." dependencies = [ - "click<9.0.0,>=7.1.1", + "click>=8.0.0", + "rich>=10.11.0", + "shellingham>=1.3.0", "typing-extensions>=3.7.4.3", ] files = [ - {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, - {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, + {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, + {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, ] [[package]] name = "typer" -version = "0.9.0" +version = "0.15.2" extras = ["all"] -requires_python = ">=3.6" +requires_python = ">=3.7" summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." dependencies = [ - "colorama<0.5.0,>=0.4.3", - "rich<14.0.0,>=10.11.0", - "shellingham<2.0.0,>=1.3.0", - "typer==0.9.0", + "typer==0.15.2", ] files = [ - {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, - {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, + {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, + {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 7b7cd9e..7b78a15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,8 @@ lkml2cube = "lkml2cube.main:app" requires = ["pdm-backend"] build-backend = "pdm.backend" -[tool.pdm.dev-dependencies] + +[dependency-groups] test = [ - "pytest>=7.4.0", + "pytest>=8.3.5", ] From 65e46145bb0f7985390f20291990204d6da4ee66 Mon Sep 17 00:00:00 2001 From: Paco Valdez Date: Wed, 16 Apr 2025 11:11:48 -0700 Subject: [PATCH 2/3] add workflow --- .github/workflows/run_tests.yml | 36 +++++++++++++++++++++++++++++++++ .gitignore | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..bcbc885 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2cecef0..d0c71dc 100644 --- a/.gitignore +++ b/.gitignore @@ -159,7 +159,7 @@ 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 examples/ From a95494be42c12cead278c10a859fbeafe8744c39 Mon Sep 17 00:00:00 2001 From: Paco Valdez Date: Wed, 16 Apr 2025 11:16:22 -0700 Subject: [PATCH 3/3] add workflow --- lkml2cube/tests/{e2e.py => test_e2e.py} | 2 -- 1 file changed, 2 deletions(-) rename lkml2cube/tests/{e2e.py => test_e2e.py} (98%) diff --git a/lkml2cube/tests/e2e.py b/lkml2cube/tests/test_e2e.py similarity index 98% rename from lkml2cube/tests/e2e.py rename to lkml2cube/tests/test_e2e.py index 335d47a..93f080e 100644 --- a/lkml2cube/tests/e2e.py +++ b/lkml2cube/tests/test_e2e.py @@ -41,5 +41,3 @@ def test_simple_view(self): assert ( generated_yaml == cube_model ), "Generated YAML does not match the expected YAML" - - assert True