# Table of Contents - [lkml — lkml documentation](#lkml-lkml-documentation) - [Installation — lkml documentation](#installation-lkml-documentation) - [Simple LookML parsing — lkml documentation](#simple-lookml-parsing-lkml-documentation) - [Parsing from the command line — lkml documentation](#parsing-from-the-command-line-lkml-documentation) - [Advanced LookML parsing — lkml documentation](#advanced-lookml-parsing-lkml-documentation) - [Index — lkml documentation](#index-lkml-documentation) - [Unknown](#unknown) - [Search — lkml documentation](#search-lkml-documentation) - [lkml — lkml documentation](#lkml-lkml-documentation) - [API reference — lkml documentation](#api-reference-lkml-documentation) - [Python Module Index — lkml documentation](#python-module-index-lkml-documentation) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) - [Unknown](#unknown) --- # lkml — lkml documentation lkml[¶](#lkml "Link to this heading") ====================================== A speedy LookML parser and serializer implemented in pure Python. Why should you use lkml? * Tested on **over 160K lines of LookML** from public repositories on GitHub * Parses a typical view or model file in < **10 ms** (excludes I/O time) * Written in pure, modern Python 3.7 with **no external dependencies** * A **full unit test suite** with excellent coverage Interested in contributing to lkml? Check out the [contributor guidelines](https://github.com/joshtemple/lkml/blob/master/CONTRIBUTING.md) . Have a question, feature request, or bug report? Submit an issue [on GitHub](https://github.com/joshtemple/lkml/issues/new) . * [Installation](install.html) * [Simple LookML parsing](simple.html) * [How LookML is represented by lkml](simple.html#how-lookml-is-represented-by-lkml) * [Simple LookML generation](simple.html#simple-lookml-generation) * [Parsing from the command line](cli.html) * [Parsing in debug mode](cli.html#parsing-in-debug-mode) * [Advanced LookML parsing](advanced.html) * [The parse tree](advanced.html#the-parse-tree) * [Traversing and modifying the parse tree](advanced.html#traversing-and-modifying-the-parse-tree) * [Generating LookML from the parse tree](advanced.html#generating-lookml-from-the-parse-tree) * [How does lkml build the parse tree?](advanced.html#how-does-lkml-build-the-parse-tree) * [API reference](lkml.html) * [Module contents](lkml.html#module-lkml) * [lkml.keys module](lkml.html#module-lkml.keys) * [lkml.lexer module](lkml.html#module-lkml.lexer) * [lkml.parser module](lkml.html#module-lkml.parser) * [lkml.simple module](lkml.html#module-lkml.simple) * [lkml.tokens module](lkml.html#module-lkml.tokens) * [lkml.tree module](lkml.html#module-lkml.tree) * [lkml.visitors module](lkml.html#module-lkml.visitors) Indices and tables[¶](#indices-and-tables "Link to this heading") ------------------------------------------------------------------ * [Index](genindex.html) * [Module Index](py-modindex.html) * [Search Page](search.html) [lkml](#) ========== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](#) * Next: [Installation](install.html "next chapter") ### Quick search --- # Installation — lkml documentation Installation[¶](#installation "Link to this heading") ====================================================== lkml is installed via [pip](https://pypi.org/project/lkml/) with the following command: pip install lkml Once you’ve installed lkml successfully, check out [Simple LookML parsing](simple.html) . [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](#) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) * Previous: [lkml](index.html "previous chapter") * Next: [Simple LookML parsing](simple.html "next chapter") ### Quick search --- # Simple LookML parsing — lkml documentation Simple LookML parsing[¶](#simple-lookml-parsing "Link to this heading") ======================================================================== Parsing a LookML string into Python is easy. Pass the LookML string into [`lkml.load()`](lkml.html#lkml.load "lkml.load") . \>>> import lkml \>>> from pprint import pprint \>>> lookml \= """ ... dimension: order\_id { ... sql: ${TABLE}.order\_id ;; ... } ... ... dimension\_group: created { ... group\_label: "Order Date" ... type: time ... timeframes: \[hour, date, week, month, year\] ... sql: ${TABLE}.created\_at ;; ... } ... ... """ \>>> result \= lkml.load(lookml) \>>> pprint(result) {'dimension\_groups': \[{'group\_label': 'Order Date',\ 'name': 'created',\ 'sql': '${TABLE}.created\_at',\ 'timeframes': \['hour', 'date', 'week', 'month', 'year'\],\ 'type': 'time'}\], 'dimensions': \[{'name': 'order\_id', 'sql': '${TABLE}.order\_id'}\]} [`lkml.load()`](lkml.html#lkml.load "lkml.load") also supports parsing LookML directly from files: with open('orders.view.lkml', 'r') as file: result \= lkml.load(file) How LookML is represented by lkml[¶](#how-lookml-is-represented-by-lkml "Link to this heading") ------------------------------------------------------------------------------------------------ When using [`lkml.load()`](lkml.html#lkml.load "lkml.load") , LookML is parsed into a JSON-like, nested dictionary format. From here on, we’ll refer to LookML field names (e.g. `sql_table_name`, `view`, or `join`) as **keys**. Blocks with keys like `dimension` and `view` become dictionaries. lkml adds a key called `name` if the block has a name, like the name of the dimension or view. \>>> lookml \= """ ... dimension: order\_id { hidden: yes } ... """ \>>> result \= lkml.load(lookml) \>>> pprint(result) {'dimensions': \[{'hidden': 'yes', 'name': 'order\_id'}\]} Keys with literal or quoted values like `hidden: yes` become keys and values in their parent dictionary: `{"hidden": "yes"}` Fields that can be repeated (e.g. `view`, `dimension`, or `join`) are combined into a list: \>>> lookml \= """ ... dimension: order\_id { sql: ${TABLE}.order\_id ;; } ... dimension: amount { sql: ${TABLE}.amount ;; } ... dimension: status { sql: ${TABLE}.status ;; } ... """ \>>> result \= lkml.load(lookml) \>>> pprint(result) {'dimensions': \[{'name': 'order\_id', 'sql': '${TABLE}.order\_id'},\ {'name': 'amount', 'sql': '${TABLE}.amount'},\ {'name': 'status', 'sql': '${TABLE}.status'}\]} Here’s an example of some LookML that has been parsed into a dictionary. Note that the repeated key `join` has been transformed into a plural key `joins`: a list of dictionaries representing each join: { "connection": "bigquery", "explores": \[\ {\ "label": "Explore",\ "joins": \[\ {\ "relationship": "one\_to\_many",\ "type": "inner",\ "sql\_on": "${orders.order\_id} = ${order\_items.order\_id}",\ "name": "order\_items"\ },\ {\ "relationship": "one\_to\_one",\ "type": "inner",\ "sql\_on": "${orders.order\_id} = ${orders\_\_extra.order\_id}",\ "name": "orders\_\_extra"\ }\ \],\ "name": "orders"\ },\ \] } Note Simple parsing will not retain any comments in the LookML. For round-trip parsing that preserves comments and whitespace, see the section on advanced parsing below. Simple LookML generation[¶](#simple-lookml-generation "Link to this heading") ------------------------------------------------------------------------------ It’s also possible to generate LookML strings from Python objects using [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") : \>>> lookml \= { ... "includes": \["\*.view"\], ... "explores": \[\ ... {\ ... "label": "Orders, Items and Users",\ ... "view\_name": "order\_items",\ ... "joins": \[\ ... {\ ... "view\_label": "Orders",\ ... "relationship": "many\_to\_one",\ ... "sql\_on": "${order\_facts.order\_id} = ${order\_items.order\_id} ",\ ... "name": "order\_facts",\ ... }\ ... \],\ ... "name": "order\_items",\ ... }\ ... \], ... } \>>> print(lkml.dump(lookml)) include: "\*.view" explore: order\_items { label: "Orders, Items and Users" view\_name: order\_items join: order\_facts { view\_label: "Orders" relationship: many\_to\_one sql\_on: ${order\_facts.order\_id} = ${order\_items.order\_id} ;; } } [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") follows best practices for formatting the generated LookML. Formatting is not currently configurable. For more control over formatting and whitespace, read [Advanced LookML parsing](advanced.html) . Warning lkml does not validate the LookML it generates. [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") ’s only standard is that the serialized output could be successfully parsed by [`lkml.load()`](lkml.html#lkml.load "lkml.load") . It’s entirely possible to generate invalid LookML if the input is malformed. When generating LookML, lkml descends through the dictionary, writing LookML based on the **keys and values** it finds. * **If the value is a dictionary**, lkml creates a block. Dictionaries can have an optional key called `name` (in this case, the name of this dimension is `price`), as well as a number of key/value pairs. To name a block, include the `name` key in the dictionary to be serialized. Here’s an example of a dictionary we might provide to [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") : { "dimension": { "type": "number", "label": "Unit Price", "sql": "${TABLE}.price", "name": "price" } } And here’s the resulting block of LookML that is generated: dimension: price { type: number label: "Unit Price" sql: ${TABLE}.price ;; } * **If the value is a list**, lkml checks the key against a list of known repeatable keys. In the example above, we used a nested dictionary to represent a dimension block. However, LookML allows multiple blocks with the same key (e.g. `dimension`, `view`, `set`, etc.). Since Python dictionaries cannot have duplicate keys, we represent these repeated keys in our dictionary as a single key/value pair, where the key is a pluralized version of the original key (`dimensions` instead of `dimension`), and the value is a list of objects that represent each individual field. For example, multiple joins on an explore should be represented as follows: "joins": \[\ {\ "relationship": "many\_to\_one",\ "type": "inner",\ "sql\_on": "${view\_one.dimension} = ${view\_two.dimension}",\ "name": "view\_two"\ },\ {\ "relationship": "one\_to\_many",\ "type": "inner",\ "sql\_on": "${view\_one.dimension} = ${view\_three.dimension}",\ "name": "view\_three"\ }\ \] If the key is \_not\_ in the list of known repeated keys, `lkml` creates a list. Here’s an example of a list in LookML. fields: \[orders.price, orders.ordered\_date, orders.order\_id\] * **If the value is a string**, lkml creates a quoted or unquoted string based on the key. For example, the value for `label` would be quoted, but the value for `hidden` would not. Values with keys like `sql_table_name` or `html` that indicate an expression automatically have a trailing space and `;;` appended. Let’s say we’ve parsed the example view from **“Parsing LookML in Python”** above. We’ve parsed it into a dictionary and now we want to modify it. We want to change the type of the dimension order\_id from number to string. Using lkml, it’s easy to modify the value of type in Python and dump it to LookML. First, we’ll modify the value of type in the parsed dictionary: parsed\['views'\]\[0\]\['dimensions'\]\[0\]\['type'\] \= 'string' Next, we’ll dump the dictionary back to LookML in a new file: with open('path/to/new.view.lkml', 'w+') as file: lkml.dump(parsed, file) Here’s the output. view: { sql\_table\_name: analytics.orders ;; dimension: order\_id { primary\_key: yes type: string sql: ${TABLE}.order\_id ;; } } [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](#) * [How LookML is represented by lkml](#how-lookml-is-represented-by-lkml) * [Simple LookML generation](#simple-lookml-generation) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) * Previous: [Installation](install.html "previous chapter") * Next: [Parsing from the command line](cli.html "next chapter") ### Quick search --- # Parsing from the command line — lkml documentation Parsing from the command line[¶](#parsing-from-the-command-line "Link to this heading") ======================================================================================== lkml can also be used as a command-line tool. It accepts a single argument: the path to the LookML file to be parsed. When called from the command line, lkml emits the parsed result as a JSON string: lkml orders.view.lkml If you would like to save the result to a file, you can pipe the output as follows. lkml path/to/file.view.lkml \> path/to/result.json Parsing in debug mode[¶](#parsing-in-debug-mode "Link to this heading") ------------------------------------------------------------------------ Providing the `-v` or `--verbose` argument at the command line turns on debug, or verbose mode. In debug mode, lkml will emit its attempts to parse each bit of the LookML string (called a token). Here’s an example of the output: lkml \-v orders.view.lkml lkml.parser DEBUG: Check StreamStartToken() \== StreamStartToken lkml.parser DEBUG: Check LiteralToken(view) \== CommentToken or WhitespaceToken lkml.parser DEBUG: Try to parse \[expression\] \= (block / pair / list)\* lkml.parser DEBUG: . Check LiteralToken(view) \== CommentToken or WhitespaceToken lkml.parser DEBUG: . Check LiteralToken(view) \== StreamEndToken or BlockEndToken lkml.parser DEBUG: . Try to parse \[block\] \= key literal? '{' expression '}' lkml.parser DEBUG: . . Try to parse \[key\] \= literal ':' lkml.parser DEBUG: . . . Check LiteralToken(view) \== CommentToken or WhitespaceToken lkml.parser DEBUG: . . . Check LiteralToken(view) \== LiteralToken lkml.parser DEBUG: . . . Check ValueToken() \== CommentToken or WhitespaceToken lkml.parser DEBUG: . . . Check ValueToken() \== ValueToken lkml.parser DEBUG: . . . Check WhitespaceToken(' ') \== CommentToken or WhitespaceToken lkml.parser DEBUG: . . . Check LiteralToken(view\_name) \== CommentToken or WhitespaceToken lkml.parser DEBUG: . . Successfully parsed key. lkml iterates through each token in the input and attempts to match it to a line of **grammar**. If lkml tries all known grammar options and doesn’t find a match, it will throw a syntax error and exit. Debug mode helps you understand what lkml is expecting in the input and why it wasn’t matched. [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](#) * [Parsing in debug mode](#parsing-in-debug-mode) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) * Previous: [Simple LookML parsing](simple.html "previous chapter") * Next: [Advanced LookML parsing](advanced.html "next chapter") ### Quick search --- # Advanced LookML parsing — lkml documentation Advanced LookML parsing[¶](#advanced-lookml-parsing "Link to this heading") ============================================================================ [`lkml.load()`](lkml.html#lkml.load "lkml.load") and [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") provide a simple interface between LookML and Python primitive data structures. However, [`lkml.load()`](lkml.html#lkml.load "lkml.load") discards information about comments and whitespace, making lossless modification of LookML impossible. For example, let’s say we wanted to programmatically add a description to the dimension in this snippet of LookML: \# Inventory-related dimensions here dimension: days\_in\_inventory { sql: ${TABLE}.days\_in\_inventory ;; } If we parse this LookML with [`lkml.load()`](lkml.html#lkml.load "lkml.load") , we’ll lose the comment and any information about the surrounding whitespace: \>>> text \= """ ... \# Inventory-related dimensions here ... ... dimension: days\_in\_inventory { sql: ${TABLE}.days\_in\_inventory ;; } ... """ \>>> parsed \= lkml.load(text) \>>> parsed {'dimensions': \[{'sql': '${TABLE}.days\_in\_inventory', 'name': 'days\_in\_inventory'}\]} Writing this dictionary back to LookML with [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") yields the following: \>>> print(lkml.dump(parsed)) dimension: days\_in\_inventory { sql: ${TABLE}.days\_in\_inventory ;; } The comment is missing and the whitespace has been overriden by [`lkml.dump()`](lkml.html#lkml.dump "lkml.dump") ’s opinionated formatting. If we want to preserve the exact whitespace and comments surrounding this dimension, we’ll need to dive under the hood of lkml and directly modify the **parse tree**. The parse tree[¶](#the-parse-tree "Link to this heading") ---------------------------------------------------------- The parse tree is an immutable tree structure generated by lkml that holds the relevant information about the parsed LookML. Each node in the tree is either a **syntax node** (a node with **children**) or a **syntax token** (a leaf node). _class_ lkml.tree.SyntaxNode Abstract base class for members of the parse tree that have child nodes. _abstract_ accept(_visitor: [Visitor](lkml.html#lkml.tree.Visitor "lkml.tree.Visitor") _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)") Accepts a Visitor that can interact with the node. The visitor pattern allows for flexible algorithms that can traverse the tree without needing to be defined as methods on the tree itself. _abstract property_ children_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)") \[[SyntaxNode](lkml.html#lkml.tree.SyntaxNode "lkml.tree.SyntaxNode")\ , ...\] | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") _ Returns all child SyntaxNodes, but not SyntaxTokens. _abstract property_ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") _ Returns the line number of the first SyntaxToken in the node _class_ lkml.tree.SyntaxToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") \= None_, _prefix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") \= ''_, _suffix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") \= ''_) Stores a text value with optional prefix or suffix trivia. For example, a syntax token might represent meaningful punctuation like a curly brace or the type or value of a LookML field. A syntax token can also store trivia, comments or whitespace that precede or follow the token value. The parser attempts to assign these prefixes and suffixes intelligently to the corresponding tokens. value The text represented by the token. Type: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") prefix Comments or whitespace preceding the token. Type: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") suffix Comments or whitespace following the token. Type: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") You can think of syntax tokens as the fundamental pieces of text that make up LookML. Whitespace and comments are collectively referred to as **trivia** and are stored in syntax tokens in their **prefix** and **suffix** attributes. ### Types of nodes[¶](#types-of-nodes "Link to this heading") All lkml parse trees begin with a [`lkml.tree.DocumentNode`](lkml.html#lkml.tree.DocumentNode "lkml.tree.DocumentNode") , the root node of the tree. A `DocumentNode` has a single attribute, `container`, a [`lkml.tree.ContainerNode`](lkml.html#lkml.tree.ContainerNode "lkml.tree.ContainerNode") , which stores all of the top-level nodes in the document. Children of the `ContainerNode` can be instances of [`lkml.tree.BlockNode`](lkml.html#lkml.tree.BlockNode "lkml.tree.BlockNode") , [`lkml.tree.ListNode`](lkml.html#lkml.tree.ListNode "lkml.tree.ListNode") , or [`lkml.tree.PairNode`](lkml.html#lkml.tree.PairNode "lkml.tree.PairNode") . Block nodes store children in container nodes of their own, and list nodes may have block nodes or pair nodes as children. ### Creating an example node[¶](#creating-an-example-node "Link to this heading") In lkml, `hidden: yes` is represented as a `PairNode`. A `PairNode` has two attributes, `type` and `value`, where each are `SyntaxTokens`. You’ll notice the `PairNode` also stores a special kind of `SyntaxToken` to represent the colon “:” between the type and the value. _class_ lkml.tree.PairNode(_type: [SyntaxToken](lkml.html#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") _, _value: [SyntaxToken](lkml.html#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") _, _colon: [Colon](lkml.html#lkml.tree.Colon "lkml.tree.Colon") \= Colon(value=':', line\_number=None, prefix='', suffix=' ')_) A simple LookML field, e.g. `hidden: yes`. type The field type, the value that precedes the colon. Type: [lkml.tree.SyntaxToken](lkml.html#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") value The field value, the value that follows the colon. Type: [lkml.tree.SyntaxToken](lkml.html#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") colon An optional Colon SyntaxToken. If not supplied, a default colon is created with a single space suffix after the colon. Type: [lkml.tree.Colon](lkml.html#lkml.tree.Colon "lkml.tree.Colon") We could build a `PairNode` for `hidden: yes` from scratch as follows: \>>> from lkml.tree import PairNode, SyntaxToken \>>> node \= PairNode( ... type\=SyntaxToken('hidden'), ... value\=SyntaxToken('yes') ... ) \>>> print(str(node)) hidden: yes We could include this simple pair node as a child in a block or container node, the beginnings of a more complex piece of LookML. ### Generating the parse tree[¶](#generating-the-parse-tree "Link to this heading") Creating nodes and tokens by hand is tedious, so it’s more likely that you will be parsing a LookML string into a parse tree with [`lkml.parse()`](lkml.html#lkml.parse "lkml.parse") . \>>> lkml.parse('hidden: yes') DocumentNode(container=ContainerNode(), prefix='', suffix='') This tree can be analyzed with a visitor or modified with a transformer. To learn more about the parse tree and the different kinds of nodes, read the full API reference for the [lkml.tree module](lkml.html#tree-ref) . Traversing and modifying the parse tree[¶](#traversing-and-modifying-the-parse-tree "Link to this heading") ------------------------------------------------------------------------------------------------------------ The parse tree follows a design pattern called the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) . The visitor pattern allows us to define flexible algorithms that interact with the tree without having to implement those algorithms as methods on the tree’s node classes. Each node implements a method called `accept`, that accepts a [`lkml.tree.Visitor`](lkml.html#lkml.tree.Visitor "lkml.tree.Visitor") instance and passes itself to the corresponding `visit_` method on the visitor. Here’s the `accept` method for a `ListNode`. ListNode.accept(_visitor: [Visitor](lkml.html#lkml.tree.Visitor "lkml.tree.Visitor") _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)") Accepts a visitor and calls the visitor’s list method on itself. def accept(self, visitor: Visitor) \-> Any: """Accepts a visitor and calls the visitor's list method on itself.""" return visitor.visit\_list(self) In our visitor, we can define `visit_list` however we want—giving us tons of flexibility over how we design the visitor. ### A simple visitor class[¶](#a-simple-visitor-class "Link to this heading") For example, we could write a linting visitor that traverses the parse tree and throws an error if it finds a dimension without a description: from lkml.visitors import BasicVisitor class DescriptionVisitor(BasicVisitor): def visit\_block(self, block: BlockNode): """For each block, check if it's a dimension and if it has a description.""" if block.type.value \== 'dimension': child\_types \= \[node.type.value for node in block.container.items\] if 'description' not in child\_types: raise KeyError(f'Dimension {block.name.value} does not have a description') \# Assume we already have a parse tree to visit tree.accept(DescriptionVisitor()) [`lkml.visitors.BasicVisitor`](lkml.html#lkml.visitors.BasicVisitor "lkml.visitors.BasicVisitor") , will traverse the tree but do nothing. We can simply override that default behavior for `visit_block` so we can inspect the dimensions, which are `BlockNodes`. For each block that is a dimension, we iterate through its children and throw an error if a child with the description type is not present. ### Modifying the parse tree with transformers[¶](#modifying-the-parse-tree-with-transformers "Link to this heading") Because syntax nodes and tokens are immutable, you can’t change them once created, you may only replace or remove them. This makes modifying the parse tree challenging, because the entire tree needs to be rebuilt for each change. lkml includes a basic transformer, [`lkml.visitors.BasicTransformer`](lkml.html#lkml.visitors.BasicTransformer "lkml.visitors.BasicTransformer") , which like the visitors, traverses the tree. However, the transformer visits and replaces each node’s children, allowing the immutable parse tree to be rebuilt with modifications. As an example, let’s write a transformer that injects a user attribute into each `sql_table_name` field. This is something that could easily be solved with regex, but let’s write a transformer as an example instead: from dataclasses import replace from lkml.visitors import BasicTransformer class TableNameTransformer(BasicTransformer): def visit\_pair(self, node: PairNode) \-> PairNode: """Visit each pair and replace the SQL table schema with a user attribute.""" if node.type.value \== 'sql\_table\_name': try: schema, table\_name \= node.value.value.split('.') \# Sometimes the table name won't have a schema except ValueError: table\_name \= node.value.value new\_value: str \= '{{ \_user\_attributes\["dbt\_schema"\] }}.' + table\_name new\_node: PairNode \= replace(node, value\=ExpressionSyntaxToken(new\_value)) return new\_node else: return node \# Assume we already have a parse tree to visit tree.accept(TableNameTransformer()) This transformer traverses the parse tree and modifies all `PairNodes` that have the `sql_table_name` type, injecting a user attribute into the expression. We rely on the dataclasses function [`dataclasses.replace()`](https://docs.python.org/3/library/dataclasses.html#dataclasses.replace "(in Python v3.13)") , which allows us to copy an immutable node (all lkml nodes are frozen, immutable dataclasses) with modifications—in this case, to the `value` attribute of the `PairNode`. Generating LookML from the parse tree[¶](#generating-lookml-from-the-parse-tree "Link to this heading") -------------------------------------------------------------------------------------------------------- Generating LookML from the parse tree is simple because each node class defines its own `__str__` method to serialize its contents. To generate a LookML string from any part of the tree, just cast it with `str`: tree: DocumentNode str(tree) How does lkml build the parse tree?[¶](#how-does-lkml-build-the-parse-tree "Link to this heading") --------------------------------------------------------------------------------------------------- lkml is made up of two components, a [lexer](https://en.wikipedia.org/wiki/Lexical_analysis) and a parser. The parser is a [recursive descent parser](https://en.wikipedia.org/wiki/Recursive_descent_parser) with backtracking. First, the lexer scans through the input string character by character and generates a stream of relevant tokens. The lexer skips over whitespace when it’s not relevant. For example, the input string: "sql: ${TABLE}.order\_date ;;" would be broken into the tuple of tokens: ( LiteralToken(sql), ValueToken(), ExpressionBlockToken(${TABLE}.order\_date), ExpressionBlockEndToken() ) Next, the parser scans through the stream of tokens. It marks its position in the stream, then attempts to identify a matching rule in the grammar. If the rule is made up of other rules (this is a called a non-terminal), it descends recursively through the constituent rules looking for tokens that match. If it doesn’t find a match for a rule, it backtracks to a previously marked point in the stream and tries the next available rule. If the parser runs out of rules to try, it raises a syntax error. As the parser finds matches, it adds the relevant token values to its syntax tree, which is eventually returned to the user if the input parses successfully. [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](#) * [The parse tree](#the-parse-tree) * [Traversing and modifying the parse tree](#traversing-and-modifying-the-parse-tree) * [Generating LookML from the parse tree](#generating-lookml-from-the-parse-tree) * [How does lkml build the parse tree?](#how-does-lkml-build-the-parse-tree) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) * Previous: [Parsing from the command line](cli.html "previous chapter") * Next: [API reference](lkml.html "next chapter") ### Quick search --- # Index — lkml documentation Index ===== [**A**](#A) | [**B**](#B) | [**C**](#C) | [**D**](#D) | [**E**](#E) | [**F**](#F) | [**I**](#I) | [**J**](#J) | [**L**](#L) | [**M**](#M) | [**N**](#N) | [**P**](#P) | [**Q**](#Q) | [**R**](#R) | [**S**](#S) | [**T**](#T) | [**U**](#U) | [**V**](#V) | [**W**](#W) A - | | | | --- | --- | | * [accept() (lkml.tree.BlockNode method)](lkml.html#lkml.tree.BlockNode.accept)
* [(lkml.tree.ContainerNode method)](lkml.html#lkml.tree.ContainerNode.accept)

* [(lkml.tree.DocumentNode method)](lkml.html#lkml.tree.DocumentNode.accept)

* [(lkml.tree.ListNode method)](lkml.html#lkml.tree.ListNode.accept)

* [(lkml.tree.PairNode method)](lkml.html#lkml.tree.PairNode.accept)

* [(lkml.tree.SyntaxNode method)](lkml.html#lkml.tree.SyntaxNode.accept)

* [(lkml.tree.SyntaxToken method)](lkml.html#lkml.tree.SyntaxToken.accept) | * [advance() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.advance)
* [(lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.advance)

* [append() (lkml.parser.CommaSeparatedValues method)](lkml.html#lkml.parser.CommaSeparatedValues.append) | B - | | | | --- | --- | | * [backtrack\_if\_none() (in module lkml.parser)](lkml.html#lkml.parser.backtrack_if_none)

* [base\_indent (lkml.simple.DictParser attribute)](lkml.html#lkml.simple.DictParser.base_indent)

* [BasicTransformer (class in lkml.visitors)](lkml.html#lkml.visitors.BasicTransformer) | * [BasicVisitor (class in lkml.visitors)](lkml.html#lkml.visitors.BasicVisitor)

* [BlockEndToken (class in lkml.tokens)](lkml.html#lkml.tokens.BlockEndToken)

* [BlockNode (class in lkml.tree)](lkml.html#lkml.tree.BlockNode)

* [BlockStartToken (class in lkml.tokens)](lkml.html#lkml.tokens.BlockStartToken) | C - | | | | --- | --- | | * [check() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.check)

* [check\_for\_expression\_block() (lkml.lexer.Lexer static method)](lkml.html#lkml.lexer.Lexer.check_for_expression_block)

* [children (lkml.tree.BlockNode property)](lkml.html#lkml.tree.BlockNode.children)
* [(lkml.tree.ContainerNode property)](lkml.html#lkml.tree.ContainerNode.children)

* [(lkml.tree.DocumentNode property)](lkml.html#lkml.tree.DocumentNode.children)

* [(lkml.tree.ListNode property)](lkml.html#lkml.tree.ListNode.children)

* [(lkml.tree.PairNode property)](lkml.html#lkml.tree.PairNode.children)

* [(lkml.tree.SyntaxNode property)](lkml.html#lkml.tree.SyntaxNode.children)

* [cli() (in module lkml)](lkml.html#lkml.cli)

* [Colon (class in lkml.tree)](lkml.html#lkml.tree.Colon)

* [colon (lkml.tree.BlockNode attribute)](lkml.html#id0)
, [\[1\]](lkml.html#lkml.tree.BlockNode.colon)
* [(lkml.tree.ListNode attribute)](lkml.html#id11)
, [\[1\]](lkml.html#lkml.tree.ListNode.colon)

* [(lkml.tree.PairNode attribute)](lkml.html#id17)
, [\[1\]](lkml.html#lkml.tree.PairNode.colon) | * [Comma (class in lkml.tree)](lkml.html#lkml.tree.Comma)

* [CommaSeparatedValues (class in lkml.parser)](lkml.html#lkml.parser.CommaSeparatedValues)

* [CommaToken (class in lkml.tokens)](lkml.html#lkml.tokens.CommaToken)

* [CommentToken (class in lkml.tokens)](lkml.html#lkml.tokens.CommentToken)

* [consume() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.consume)
* [(lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.consume)

* [consume\_token\_value() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.consume_token_value)

* [consume\_trivia() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.consume_trivia)

* [container (lkml.tree.BlockNode attribute)](lkml.html#id1)
, [\[1\]](lkml.html#lkml.tree.BlockNode.container)
* [(lkml.tree.DocumentNode attribute)](lkml.html#id8)
, [\[1\]](lkml.html#lkml.tree.DocumentNode.container)

* [ContainerNode (class in lkml.tree)](lkml.html#lkml.tree.ContainerNode)

* [ContentToken (class in lkml.tokens)](lkml.html#lkml.tokens.ContentToken) | D - | | | | --- | --- | | * [decrease\_level() (lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.decrease_level)

* [depth (lkml.parser.Parser attribute)](lkml.html#lkml.parser.Parser.depth)
* [(lkml.simple.DictVisitor attribute)](lkml.html#lkml.simple.DictVisitor.depth)

* [DictParser (class in lkml.simple)](lkml.html#lkml.simple.DictParser) | * [DictVisitor (class in lkml.simple)](lkml.html#lkml.simple.DictVisitor)

* [DocumentNode (class in lkml.tree)](lkml.html#lkml.tree.DocumentNode)

* [DoubleSemicolon (class in lkml.tree)](lkml.html#lkml.tree.DoubleSemicolon)

* [dump() (in module lkml)](lkml.html#lkml.dump) | E - | | | | --- | --- | | * [expand\_list() (lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.expand_list)

* [expr\_suffix (lkml.tree.ExpressionSyntaxToken attribute)](lkml.html#lkml.tree.ExpressionSyntaxToken.expr_suffix) | * [ExpressionBlockEndToken (class in lkml.tokens)](lkml.html#lkml.tokens.ExpressionBlockEndToken)

* [ExpressionBlockToken (class in lkml.tokens)](lkml.html#lkml.tokens.ExpressionBlockToken)

* [ExpressionSyntaxToken (class in lkml.tree)](lkml.html#lkml.tree.ExpressionSyntaxToken) | F - | | | | --- | --- | | * [flatten() (in module lkml.simple)](lkml.html#lkml.simple.flatten) | * [format\_value() (lkml.tree.QuotedSyntaxToken method)](lkml.html#lkml.tree.QuotedSyntaxToken.format_value)
* [(lkml.tree.SyntaxToken method)](lkml.html#lkml.tree.SyntaxToken.format_value) | I - | | | | --- | --- | | * [id (lkml.tokens.BlockEndToken attribute)](lkml.html#lkml.tokens.BlockEndToken.id)
* [(lkml.tokens.BlockStartToken attribute)](lkml.html#lkml.tokens.BlockStartToken.id)

* [(lkml.tokens.CommaToken attribute)](lkml.html#lkml.tokens.CommaToken.id)

* [(lkml.tokens.CommentToken attribute)](lkml.html#lkml.tokens.CommentToken.id)

* [(lkml.tokens.ExpressionBlockEndToken attribute)](lkml.html#lkml.tokens.ExpressionBlockEndToken.id)

* [(lkml.tokens.ExpressionBlockToken attribute)](lkml.html#lkml.tokens.ExpressionBlockToken.id)

* [(lkml.tokens.InlineWhitespaceToken attribute)](lkml.html#lkml.tokens.InlineWhitespaceToken.id)

* [(lkml.tokens.LinebreakToken attribute)](lkml.html#lkml.tokens.LinebreakToken.id)

* [(lkml.tokens.ListEndToken attribute)](lkml.html#lkml.tokens.ListEndToken.id)

* [(lkml.tokens.ListStartToken attribute)](lkml.html#lkml.tokens.ListStartToken.id)

* [(lkml.tokens.LiteralToken attribute)](lkml.html#lkml.tokens.LiteralToken.id)

* [(lkml.tokens.QuotedLiteralToken attribute)](lkml.html#lkml.tokens.QuotedLiteralToken.id)

* [(lkml.tokens.StreamEndToken attribute)](lkml.html#lkml.tokens.StreamEndToken.id)

* [(lkml.tokens.StreamStartToken attribute)](lkml.html#lkml.tokens.StreamStartToken.id)

* [(lkml.tokens.Token attribute)](lkml.html#lkml.tokens.Token.id)

* [(lkml.tokens.ValueToken attribute)](lkml.html#lkml.tokens.ValueToken.id)

* [(lkml.tokens.WhitespaceToken attribute)](lkml.html#lkml.tokens.WhitespaceToken.id) | * [increase\_level() (lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.increase_level)

* [indent (lkml.simple.DictParser property)](lkml.html#lkml.simple.DictParser.indent)

* [index (lkml.lexer.Lexer attribute)](lkml.html#lkml.lexer.Lexer.index)
* [(lkml.parser.Parser attribute)](lkml.html#lkml.parser.Parser.index)

* [InlineWhitespaceToken (class in lkml.tokens)](lkml.html#lkml.tokens.InlineWhitespaceToken)

* [is\_plural\_key() (lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.is_plural_key)

* [items (lkml.tree.ContainerNode attribute)](lkml.html#id6)
, [\[1\]](lkml.html#lkml.tree.ContainerNode.items)
* [(lkml.tree.ListNode attribute)](lkml.html#id12)
, [\[1\]](lkml.html#lkml.tree.ListNode.items)

* [items\_to\_str() (in module lkml.tree)](lkml.html#lkml.tree.items_to_str) | J - * [jump\_to\_index() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.jump_to_index) L - | | | | --- | --- | | * [latest\_node (lkml.simple.DictParser attribute)](lkml.html#lkml.simple.DictParser.latest_node)

* [leading\_comma (lkml.parser.CommaSeparatedValues attribute)](lkml.html#lkml.parser.CommaSeparatedValues.leading_comma)
* [(lkml.tree.ListNode attribute)](lkml.html#lkml.tree.ListNode.leading_comma)

* [left\_brace (lkml.tree.BlockNode attribute)](lkml.html#id2)
, [\[1\]](lkml.html#lkml.tree.BlockNode.left_brace)

* [left\_bracket (lkml.tree.ListNode attribute)](lkml.html#id13)
, [\[1\]](lkml.html#lkml.tree.ListNode.left_bracket)

* [LeftBracket (class in lkml.tree)](lkml.html#lkml.tree.LeftBracket)

* [LeftCurlyBrace (class in lkml.tree)](lkml.html#lkml.tree.LeftCurlyBrace)

* [level (lkml.simple.DictParser attribute)](lkml.html#lkml.simple.DictParser.level)

* [Lexer (class in lkml.lexer)](lkml.html#lkml.lexer.Lexer)

* [line\_number (lkml.lexer.Lexer attribute)](lkml.html#lkml.lexer.Lexer.line_number)
* [(lkml.tree.BlockNode property)](lkml.html#lkml.tree.BlockNode.line_number)

* [(lkml.tree.DocumentNode property)](lkml.html#lkml.tree.DocumentNode.line_number)

* [(lkml.tree.ListNode property)](lkml.html#lkml.tree.ListNode.line_number)

* [(lkml.tree.PairNode property)](lkml.html#lkml.tree.PairNode.line_number)

* [(lkml.tree.SyntaxNode property)](lkml.html#lkml.tree.SyntaxNode.line_number)

* [(lkml.tree.SyntaxToken attribute)](lkml.html#lkml.tree.SyntaxToken.line_number)

* [line\_number() (lkml.tree.ContainerNode method)](lkml.html#lkml.tree.ContainerNode.line_number)

* [LinebreakToken (class in lkml.tokens)](lkml.html#lkml.tokens.LinebreakToken)

* [ListEndToken (class in lkml.tokens)](lkml.html#lkml.tokens.ListEndToken)

* [ListNode (class in lkml.tree)](lkml.html#lkml.tree.ListNode) | * [ListStartToken (class in lkml.tokens)](lkml.html#lkml.tokens.ListStartToken)

* [LiteralToken (class in lkml.tokens)](lkml.html#lkml.tokens.LiteralToken)

* lkml
* [module](lkml.html#module-lkml)

* lkml.keys
* [module](lkml.html#module-lkml.keys)

* lkml.lexer
* [module](lkml.html#module-lkml.lexer)

* lkml.parser
* [module](lkml.html#module-lkml.parser)

* lkml.simple
* [module](lkml.html#module-lkml.simple)

* lkml.tokens
* [module](lkml.html#module-lkml.tokens)

* lkml.tree
* [module](lkml.html#module-lkml.tree)

* lkml.visitors
* [module](lkml.html#module-lkml.visitors)

* [load() (in module lkml)](lkml.html#lkml.load)

* [log\_debug (lkml.parser.Parser attribute)](lkml.html#lkml.parser.Parser.log_debug)

* [LookMlVisitor (class in lkml.visitors)](lkml.html#lkml.visitors.LookMlVisitor) | M - * module * [lkml](lkml.html#module-lkml) * [lkml.keys](lkml.html#module-lkml.keys) * [lkml.lexer](lkml.html#module-lkml.lexer) * [lkml.parser](lkml.html#module-lkml.parser) * [lkml.simple](lkml.html#module-lkml.simple) * [lkml.tokens](lkml.html#module-lkml.tokens) * [lkml.tree](lkml.html#module-lkml.tree) * [lkml.visitors](lkml.html#module-lkml.visitors) N - | | | | --- | --- | | * [name (lkml.tree.BlockNode attribute)](lkml.html#id3)
, [\[1\]](lkml.html#lkml.tree.BlockNode.name) | * [newline\_indent (lkml.simple.DictParser property)](lkml.html#lkml.simple.DictParser.newline_indent) | P - | | | | --- | --- | | * [PairNode (class in lkml.tree)](lkml.html#lkml.tree.PairNode)

* [parent\_key (lkml.simple.DictParser attribute)](lkml.html#lkml.simple.DictParser.parent_key)

* [parse() (in module lkml)](lkml.html#lkml.parse)
* [(lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse)

* [(lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.parse)

* [parse\_any() (lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.parse_any)

* [parse\_args() (in module lkml)](lkml.html#lkml.parse_args)

* [parse\_block() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_block)
* [(lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.parse_block)

* [parse\_comma() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_comma)

* [parse\_container() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_container)

* [parse\_csv() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_csv)

* [parse\_key() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_key)

* [parse\_list() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_list)
* [(lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.parse_list) | * [parse\_pair() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_pair)
* [(lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.parse_pair)

* [parse\_token() (lkml.simple.DictParser static method)](lkml.html#lkml.simple.DictParser.parse_token)

* [parse\_value() (lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.parse_value)

* [Parser (class in lkml.parser)](lkml.html#lkml.parser.Parser)

* [peek() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.peek)
* [(lkml.parser.Parser method)](lkml.html#lkml.parser.Parser.peek)

* [peek\_multiple() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.peek_multiple)

* [pluralize() (in module lkml.keys)](lkml.html#lkml.keys.pluralize)

* [prefix (lkml.simple.DictParser property)](lkml.html#lkml.simple.DictParser.prefix)
* [(lkml.tree.DocumentNode attribute)](lkml.html#id9)
, [\[1\]](lkml.html#lkml.tree.DocumentNode.prefix)

* [(lkml.tree.ExpressionSyntaxToken attribute)](lkml.html#lkml.tree.ExpressionSyntaxToken.prefix)

* [(lkml.tree.SyntaxToken attribute)](lkml.html#id20)
, [\[1\]](lkml.html#lkml.tree.SyntaxToken.prefix)

* [progress (lkml.parser.Parser attribute)](lkml.html#lkml.parser.Parser.progress) | Q - | | | | --- | --- | | * [QuotedLiteralToken (class in lkml.tokens)](lkml.html#lkml.tokens.QuotedLiteralToken) | * [QuotedSyntaxToken (class in lkml.tree)](lkml.html#lkml.tree.QuotedSyntaxToken) | R - | | | | --- | --- | | * [resolve\_filters() (lkml.simple.DictParser method)](lkml.html#lkml.simple.DictParser.resolve_filters)

* [right\_brace (lkml.tree.BlockNode attribute)](lkml.html#id4)
, [\[1\]](lkml.html#lkml.tree.BlockNode.right_brace) | * [right\_bracket (lkml.tree.ListNode attribute)](lkml.html#id14)
, [\[1\]](lkml.html#lkml.tree.ListNode.right_bracket)

* [RightBracket (class in lkml.tree)](lkml.html#lkml.tree.RightBracket)

* [RightCurlyBrace (class in lkml.tree)](lkml.html#lkml.tree.RightCurlyBrace) | S - | | | | --- | --- | | * [scan() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.scan)

* [scan\_comment() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.scan_comment)

* [scan\_expression\_block() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.scan_expression_block)

* [scan\_literal() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.scan_literal)

* [scan\_quoted\_literal() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.scan_quoted_literal)

* [scan\_whitespace() (lkml.lexer.Lexer method)](lkml.html#lkml.lexer.Lexer.scan_whitespace) | * [singularize() (in module lkml.keys)](lkml.html#lkml.keys.singularize)

* [StreamEndToken (class in lkml.tokens)](lkml.html#lkml.tokens.StreamEndToken)

* [StreamStartToken (class in lkml.tokens)](lkml.html#lkml.tokens.StreamStartToken)

* [suffix (lkml.tree.DocumentNode attribute)](lkml.html#id10)
, [\[1\]](lkml.html#lkml.tree.DocumentNode.suffix)
* [(lkml.tree.SyntaxToken attribute)](lkml.html#id21)
, [\[1\]](lkml.html#lkml.tree.SyntaxToken.suffix)

* [SyntaxNode (class in lkml.tree)](lkml.html#lkml.tree.SyntaxNode)

* [SyntaxToken (class in lkml.tree)](lkml.html#lkml.tree.SyntaxToken) | T - | | | | --- | --- | | * [text (lkml.lexer.Lexer attribute)](lkml.html#lkml.lexer.Lexer.text)

* [Token (class in lkml.tokens)](lkml.html#lkml.tokens.Token)

* [tokens (lkml.lexer.Lexer attribute)](lkml.html#lkml.lexer.Lexer.tokens)
* [(lkml.parser.Parser attribute)](lkml.html#lkml.parser.Parser.tokens)

* [top\_level (lkml.tree.ContainerNode attribute)](lkml.html#id7)
, [\[1\]](lkml.html#lkml.tree.ContainerNode.top_level) | * [trailing\_comma (lkml.parser.CommaSeparatedValues attribute)](lkml.html#lkml.parser.CommaSeparatedValues.trailing_comma)
* [(lkml.tree.ListNode attribute)](lkml.html#id15)
, [\[1\]](lkml.html#lkml.tree.ListNode.trailing_comma)

* [TriviaToken (class in lkml.tokens)](lkml.html#lkml.tokens.TriviaToken)

* [type (lkml.tree.BlockNode attribute)](lkml.html#id5)
, [\[1\]](lkml.html#lkml.tree.BlockNode.type)
* [(lkml.tree.ListNode attribute)](lkml.html#id16)
, [\[1\]](lkml.html#lkml.tree.ListNode.type)

* [(lkml.tree.PairNode attribute)](lkml.html#id18)
, [\[1\]](lkml.html#lkml.tree.PairNode.type) | U - * [update\_tree() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.update_tree) V - | | | | --- | --- | | * [value (lkml.tokens.Token attribute)](lkml.html#lkml.tokens.Token.value)
* [(lkml.tree.Colon attribute)](lkml.html#lkml.tree.Colon.value)

* [(lkml.tree.Comma attribute)](lkml.html#lkml.tree.Comma.value)

* [(lkml.tree.DoubleSemicolon attribute)](lkml.html#lkml.tree.DoubleSemicolon.value)

* [(lkml.tree.LeftBracket attribute)](lkml.html#lkml.tree.LeftBracket.value)

* [(lkml.tree.LeftCurlyBrace attribute)](lkml.html#lkml.tree.LeftCurlyBrace.value)

* [(lkml.tree.PairNode attribute)](lkml.html#id19)
, [\[1\]](lkml.html#lkml.tree.PairNode.value)

* [(lkml.tree.RightBracket attribute)](lkml.html#lkml.tree.RightBracket.value)

* [(lkml.tree.RightCurlyBrace attribute)](lkml.html#lkml.tree.RightCurlyBrace.value)

* [(lkml.tree.SyntaxToken attribute)](lkml.html#id22)
, [\[1\]](lkml.html#lkml.tree.SyntaxToken.value)

* [values (lkml.parser.CommaSeparatedValues property)](lkml.html#lkml.parser.CommaSeparatedValues.values)

* [ValueToken (class in lkml.tokens)](lkml.html#lkml.tokens.ValueToken)

* [visit() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.visit)
* [(lkml.tree.Visitor method)](lkml.html#lkml.tree.Visitor.visit)

* [(lkml.visitors.BasicTransformer method)](lkml.html#lkml.visitors.BasicTransformer.visit)

* [(lkml.visitors.BasicVisitor method)](lkml.html#lkml.visitors.BasicVisitor.visit)

* [visit\_block() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.visit_block)
* [(lkml.tree.Visitor method)](lkml.html#lkml.tree.Visitor.visit_block)

* [(lkml.visitors.BasicTransformer method)](lkml.html#lkml.visitors.BasicTransformer.visit_block)

* [(lkml.visitors.BasicVisitor method)](lkml.html#lkml.visitors.BasicVisitor.visit_block) | * [visit\_container() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.visit_container)
* [(lkml.tree.Visitor method)](lkml.html#lkml.tree.Visitor.visit_container)

* [(lkml.visitors.BasicTransformer method)](lkml.html#lkml.visitors.BasicTransformer.visit_container)

* [(lkml.visitors.BasicVisitor method)](lkml.html#lkml.visitors.BasicVisitor.visit_container)

* [visit\_list() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.visit_list)
* [(lkml.tree.Visitor method)](lkml.html#lkml.tree.Visitor.visit_list)

* [(lkml.visitors.BasicTransformer method)](lkml.html#lkml.visitors.BasicTransformer.visit_list)

* [(lkml.visitors.BasicVisitor method)](lkml.html#lkml.visitors.BasicVisitor.visit_list)

* [visit\_pair() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.visit_pair)
* [(lkml.tree.Visitor method)](lkml.html#lkml.tree.Visitor.visit_pair)

* [(lkml.visitors.BasicTransformer method)](lkml.html#lkml.visitors.BasicTransformer.visit_pair)

* [(lkml.visitors.BasicVisitor method)](lkml.html#lkml.visitors.BasicVisitor.visit_pair)

* [visit\_token() (lkml.simple.DictVisitor method)](lkml.html#lkml.simple.DictVisitor.visit_token)
* [(lkml.tree.Visitor method)](lkml.html#lkml.tree.Visitor.visit_token)

* [(lkml.visitors.BasicTransformer method)](lkml.html#lkml.visitors.BasicTransformer.visit_token)

* [(lkml.visitors.BasicVisitor method)](lkml.html#lkml.visitors.BasicVisitor.visit_token)

* [Visitor (class in lkml.tree)](lkml.html#lkml.tree.Visitor) | W - * [WhitespaceToken (class in lkml.tokens)](lkml.html#lkml.tokens.WhitespaceToken) [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) ### Quick search --- # Unknown .. lkml documentation master file, created by sphinx-quickstart on Wed Jan 6 19:01:01 2021. You can adapt this file completely to your liking, but it should at least contain the root \`toctree\` directive. .. highlight:: python lkml ==== A speedy LookML parser and serializer implemented in pure Python. Why should you use lkml? \* Tested on \*\*over 160K lines of LookML\*\* from public repositories on GitHub \* Parses a typical view or model file in < \*\*10 ms\*\* (excludes I/O time) \* Written in pure, modern Python 3.7 with \*\*no external dependencies\*\* \* A \*\*full unit test suite\*\* with excellent coverage Interested in contributing to lkml? Check out the \`contributor guidelines \`\_. Have a question, feature request, or bug report? Submit an issue \`on GitHub \`\_. .. toctree:: :maxdepth: 2 install simple cli advanced lkml Indices and tables ------------------ \* :ref:\`genindex\` \* :ref:\`modindex\` \* :ref:\`search\` --- # Search — lkml documentation Search ====== Searching for multiple words only shows matches that contain all words. [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) --- # lkml — lkml documentation lkml[¶](#lkml "Link to this heading") ====================================== A speedy LookML parser and serializer implemented in pure Python. Why should you use lkml? * Tested on **over 160K lines of LookML** from public repositories on GitHub * Parses a typical view or model file in < **10 ms** (excludes I/O time) * Written in pure, modern Python 3.7 with **no external dependencies** * A **full unit test suite** with excellent coverage Interested in contributing to lkml? Check out the [contributor guidelines](https://github.com/joshtemple/lkml/blob/master/CONTRIBUTING.md) . Have a question, feature request, or bug report? Submit an issue [on GitHub](https://github.com/joshtemple/lkml/issues/new) . * [Installation](install.html) * [Simple LookML parsing](simple.html) * [How LookML is represented by lkml](simple.html#how-lookml-is-represented-by-lkml) * [Simple LookML generation](simple.html#simple-lookml-generation) * [Parsing from the command line](cli.html) * [Parsing in debug mode](cli.html#parsing-in-debug-mode) * [Advanced LookML parsing](advanced.html) * [The parse tree](advanced.html#the-parse-tree) * [Traversing and modifying the parse tree](advanced.html#traversing-and-modifying-the-parse-tree) * [Generating LookML from the parse tree](advanced.html#generating-lookml-from-the-parse-tree) * [How does lkml build the parse tree?](advanced.html#how-does-lkml-build-the-parse-tree) * [API reference](lkml.html) * [Module contents](lkml.html#module-lkml) * [lkml.keys module](lkml.html#module-lkml.keys) * [lkml.lexer module](lkml.html#module-lkml.lexer) * [lkml.parser module](lkml.html#module-lkml.parser) * [lkml.simple module](lkml.html#module-lkml.simple) * [lkml.tokens module](lkml.html#module-lkml.tokens) * [lkml.tree module](lkml.html#module-lkml.tree) * [lkml.visitors module](lkml.html#module-lkml.visitors) Indices and tables[¶](#indices-and-tables "Link to this heading") ------------------------------------------------------------------ * [Index](genindex.html) * [Module Index](py-modindex.html) * [Search Page](search.html) [lkml](#) ========== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](#) * Next: [Installation](install.html "next chapter") ### Quick search --- # API reference — lkml documentation API reference[¶](#api-reference "Link to this heading") ======================================================== Module contents[¶](#module-lkml "Link to this heading") -------------------------------------------------------- A speedy LookML parser and serializer implemented in pure Python. lkml.cli()[¶](#lkml.cli "Link to this definition") Command-line entry point for lkml. lkml.dump(_obj: [dict](https://docs.python.org/3/library/stdtypes.html#dict "(in Python v3.13)") _, _file\_object: [IO](https://docs.python.org/3/library/typing.html#typing.IO "(in Python v3.13)") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") \= None_) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.dump "Link to this definition") Serialize a Python dictionary into LookML. Parameters: * **obj** – The Python dictionary to be serialized to LookML * **file\_object** – An optional file object to save the LookML string to Returns: A LookML string if no file\_object is passed lkml.load(_stream: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") | [IO](https://docs.python.org/3/library/typing.html#typing.IO "(in Python v3.13)") _) → [dict](https://docs.python.org/3/library/stdtypes.html#dict "(in Python v3.13)") [¶](#lkml.load "Link to this definition") Parse LookML into a Python dictionary. Parameters: **stream** – File object or string containing LookML to be parsed Raises: [**TypeError**](https://docs.python.org/3/library/exceptions.html#TypeError "(in Python v3.13)") – If stream is neither a string or a file object lkml.parse(_text: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _) → [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode") [¶](#lkml.parse "Link to this definition") Parse LookML into a parse tree. Parameters: **text** – The LookML string to be parsed. Returns: A document node, the root of the parse tree. lkml.parse\_args(_args: [Sequence](https://docs.python.org/3/library/typing.html#typing.Sequence "(in Python v3.13)") _) → [Namespace](https://docs.python.org/3/library/argparse.html#argparse.Namespace "(in Python v3.13)") [¶](#lkml.parse_args "Link to this definition") Parse command-line arguments. lkml.keys module[¶](#module-lkml.keys "Link to this heading") -------------------------------------------------------------- Defines constant sequences of LookML keys and helper methods. lkml.keys.pluralize(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.keys.pluralize "Link to this definition") Converts a singular key like “explore” to a plural key, e.g. ‘explores’. lkml.keys.singularize(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.keys.singularize "Link to this definition") Converts a plural key like “explores” to a singular key, e.g. ‘explore’. lkml.lexer module[¶](#module-lkml.lexer "Link to this heading") ---------------------------------------------------------------- Splits a LookML string into a sequence of tokens. _class_ lkml.lexer.Lexer(_text: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _)[¶](#lkml.lexer.Lexer "Link to this definition") Splits a LookML string into a sequence of tokens. text[¶](#lkml.lexer.Lexer.text "Link to this definition") Raw LookML to parse, padded with null character to denote end of stream index[¶](#lkml.lexer.Lexer.index "Link to this definition") Position of lexer in characters as it traverses the text tokens[¶](#lkml.lexer.Lexer.tokens "Link to this definition") Sequence of tokens that contain the relevant chunks of text line\_number[¶](#lkml.lexer.Lexer.line_number "Link to this definition") Position of lexer in lines as it traverses the text advance(_length: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") \= 1_) → [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.lexer.Lexer.advance "Link to this definition") Moves the index forward by n characters. Parameters: **length** – The number of positions forward to move the index. _static_ check\_for\_expression\_block(_string: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _) → [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") [¶](#lkml.lexer.Lexer.check_for_expression_block "Link to this definition") Returns True if the input string is an expression block. consume() → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.lexer.Lexer.consume "Link to this definition") Returns the current index character and advances the index 1 character. peek() → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.lexer.Lexer.peek "Link to this definition") Returns the character at the current index of the text being lexed. peek\_multiple(_length: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.lexer.Lexer.peek_multiple "Link to this definition") Returns the next n characters from the current index in the text being lexed. Parameters: **length** – The number of characters to return scan() → [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)") \[[Token](#lkml.tokens.Token "lkml.tokens.Token")\ , ...\][¶](#lkml.lexer.Lexer.scan "Link to this definition") Tokenizes LookML into a sequence of tokens. This method skips through the text being lexed until it finds a character that indicates the start of a new token. It consumes the relevant characters and adds the tokens to a sequence until it reaches the end of the text. scan\_comment() → [CommentToken](#lkml.tokens.CommentToken "lkml.tokens.CommentToken") [¶](#lkml.lexer.Lexer.scan_comment "Link to this definition") Returns a token from a comment. The initial pound (#) character is consumed in the scan method, so this method only scans for a newline or end of file to indicate the end of the token. The pound character is added back to the beginning to the token to emphasize the importance of any leading whitespace that follows. Example \>>> lexer \= Lexer(" Disregard this line\\n") \>>> lexer.scan\_comment() CommentToken(# Disregard this line) scan\_expression\_block() → [ExpressionBlockToken](#lkml.tokens.ExpressionBlockToken "lkml.tokens.ExpressionBlockToken") [¶](#lkml.lexer.Lexer.scan_expression_block "Link to this definition") Returns an token from an expression block string. This method strips any trailing whitespace from the expression string, since Looker usually adds an extra space before the ;; terminal. Example \>>> lexer \= Lexer("SELECT \* FROM ${TABLE} ;;") \>>> lexer.scan\_expression\_block() ExpressionBlockToken(SELECT \* FROM ${TABLE}) scan\_literal() → [LiteralToken](#lkml.tokens.LiteralToken "lkml.tokens.LiteralToken") [¶](#lkml.lexer.Lexer.scan_literal "Link to this definition") Returns a token from a literal string. Example \>>> lexer \= Lexer("yes") \>>> lexer.scan\_literal() LiteralToken(yes) scan\_quoted\_literal() → [QuotedLiteralToken](#lkml.tokens.QuotedLiteralToken "lkml.tokens.QuotedLiteralToken") [¶](#lkml.lexer.Lexer.scan_quoted_literal "Link to this definition") Returns a token from a quoted literal string. The initial double quote character is consumed in the scan method, so this method only scans for the trailing quote to indicate the end of the token. Example \>>> lexer \= Lexer('Label"') \>>> lexer.scan\_quoted\_literal() QuotedLiteralToken(Label) scan\_whitespace() → [WhitespaceToken](#lkml.tokens.WhitespaceToken "lkml.tokens.WhitespaceToken") [¶](#lkml.lexer.Lexer.scan_whitespace "Link to this definition") Returns a token from one or more whitespace characters. > Example: > > \>>> lexer \= Lexer(" > > Hello”) > > \>>> lexer.scan\_whitespace() > LinebreakToken(' ‘, 1) lkml.parser module[¶](#module-lkml.parser "Link to this heading") ------------------------------------------------------------------ Parses a sequence of tokenized LookML into a parse tree. _class_ lkml.parser.CommaSeparatedValues(_\_values: list \= _, _trailing\_comma: ~lkml.tree.Comma | None \= None_, _leading\_comma: ~lkml.tree.Comma | None \= None_)[¶](#lkml.parser.CommaSeparatedValues "Link to this definition") Helper class to store a series of values and a flag for a trailing comma. append(_value_)[¶](#lkml.parser.CommaSeparatedValues.append "Link to this definition") Add a value to the private \_values list. leading\_comma_: [Comma](#lkml.tree.Comma "lkml.tree.Comma") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") _ _\= None_[¶](#lkml.parser.CommaSeparatedValues.leading_comma "Link to this definition") trailing\_comma_: [Comma](#lkml.tree.Comma "lkml.tree.Comma") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") _ _\= None_[¶](#lkml.parser.CommaSeparatedValues.trailing_comma "Link to this definition") _property_ values_: [tuple](https://docs.python.org/3/library/stdtypes.html#tuple "(in Python v3.13)") _[¶](#lkml.parser.CommaSeparatedValues.values "Link to this definition") Return the private \_values list, cast to a tuple. _class_ lkml.parser.Parser(_stream: [Sequence](https://docs.python.org/3/library/typing.html#typing.Sequence "(in Python v3.13)") \[[Token](#lkml.tokens.Token "lkml.tokens.Token")\ \]_)[¶](#lkml.parser.Parser "Link to this definition") Parses a sequence of tokenized LookML into a parse tree. This parser is a recursive descent parser which uses the grammar listed below (in PEG format). Each grammar rule aligns with a corresponding method (e.g. parse\_expression). Grammar: * `expression` ← `(block / pair / list)*` * `block` ← `key literal? "{" expression "}"` * `pair` ← `key value` * `list` ← `key "[" csv? "]"` * `csv` ← `(literal / quoted_literal) ("," (literal / quoted_literal))* ","?` * `value` ← `literal / quoted_literal / expression_block` * `key` ← `literal ":"` * `expression_block` ← `[^;]* ";;"` * `quoted_literal` ← `'"' [^\"]+ '"'` * `literal` ← `[0-9A-Za-z_]+` tokens[¶](#lkml.parser.Parser.tokens "Link to this definition") A sequence of tokens to be parsed. index[¶](#lkml.parser.Parser.index "Link to this definition") The position in the token sequence being parsed. progress[¶](#lkml.parser.Parser.progress "Link to this definition") The farthest index of progress during parsing. depth[¶](#lkml.parser.Parser.depth "Link to this definition") The level of recursion into nested expressions. log\_debug[¶](#lkml.parser.Parser.log_debug "Link to this definition") A flag indicating that debug messages should be logged. This flag exits to turn off logging flow entirely, which provides a small performance gain compared to parsing at a non-debug logging level. advance(_length: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") \= 1_)[¶](#lkml.parser.Parser.advance "Link to this definition") Moves the index forward by n characters. Parameters: **length** – The number of positions forward to move the index. check(_\*token\_types: [Type](https://docs.python.org/3/library/typing.html#typing.Type "(in Python v3.13)") \[[Token](#lkml.tokens.Token "lkml.tokens.Token")\ \]_, _skip\_trivia: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") \= False_) → [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") [¶](#lkml.parser.Parser.check "Link to this definition") Compares the current index token type to specified token types. Parameters: * **\*token\_types** – A variable number of token types to check against. * **skip\_trivia** – Ignore trivia tokens when searching for a match. Raises: [**TypeError**](https://docs.python.org/3/library/exceptions.html#TypeError "(in Python v3.13)") – If one or more of the token\_types are not actual token types Returns: True if the current token matches one of the token\_types. consume() → [Token](#lkml.tokens.Token "lkml.tokens.Token") [¶](#lkml.parser.Parser.consume "Link to this definition") Returns the current index character and advances the index by 1 token. consume\_token\_value() → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.parser.Parser.consume_token_value "Link to this definition") Returns the value of the current index token, advancing the index 1 token. consume\_trivia(_only\_newlines: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") \= False_) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.parser.Parser.consume_trivia "Link to this definition") Returns all continuous trivia values. jump\_to\_index(_index: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.parser.Parser.jump_to_index "Link to this definition") Sets the parser index to a specified value. parse() → [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode") [¶](#lkml.parser.Parser.parse "Link to this definition") Main method of this class and a wrapper for the container parser. Returns: A document node, the root node of the LookML parse tree. parse\_block() → [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_block "Link to this definition") Returns a node that represents a LookML block. Grammar: `block` ← `key literal? "{" expression "}"` Returns: A node with the parsed block or None if the grammar doesn’t match. parse\_comma() → [Comma](#lkml.tree.Comma "lkml.tree.Comma") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_comma "Link to this definition") parse\_container() → [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode") [¶](#lkml.parser.Parser.parse_container "Link to this definition") Returns a container node that contains any number of children. Grammar: `expression` ← `(block / pair / list)*` Returns: A node with the parsed container or None if the grammar doesn’t match. Raises: [**SyntaxError**](https://docs.python.org/3/library/exceptions.html#SyntaxError "(in Python v3.13)") – If unable to find a matching grammar rule for the stream parse\_csv() → [CommaSeparatedValues](#lkml.parser.CommaSeparatedValues "lkml.parser.CommaSeparatedValues") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_csv "Link to this definition") Returns a CSV object that represents comma-separated LookML values. Returns: A container with the parsed values or None if the grammar doesn’t match Grammar: `csv` ← `","? (literal / quoted_literal) ("," (literal / quoted_literal))* ","?` parse\_key() → [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)") \[[SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ , [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ \] | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_key "Link to this definition") Returns a syntax token that represents a literal key and colon character. Grammar: `key` ← `literal ":"` Returns: A tuple of syntax tokens with the parsed key and colon or None if the grammar doesn’t match. parse\_list() → [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_list "Link to this definition") Returns a node that represents a LookML list. Grammar: `list` ← `key "[" csv? "]"` Returns: A node with the parsed list or None if the grammar doesn’t match parse\_pair() → [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_pair "Link to this definition") Returns a dictionary that represents a LookML key/value pair. Grammar: `pair` ← `key value` Returns: A dictionary with the parsed pair or None if the grammar doesn’t match. parse\_value(_parse\_prefix: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") \= False_, _parse\_suffix: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") \= False_) → [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.parser.Parser.parse_value "Link to this definition") Returns a syntax token that represents a value. Grammar: `value` ← `literal / quoted_literal / expression_block` Returns: A syntax token with the parsed value or None if the grammar doesn’t match. peek() → [Token](#lkml.tokens.Token "lkml.tokens.Token") [¶](#lkml.parser.Parser.peek "Link to this definition") Returns the token at the current index. lkml.parser.backtrack\_if\_none(_fn_)[¶](#lkml.parser.backtrack_if_none "Link to this definition") Decorates parsing methods to backtrack to a previous position on failure. This method sets a marker at the current position before attempting to run a parsing method. If the parsing method fails and returns None, it resets the index to the marker. It also keeps track of the farthest index of progress in case all parsing methods fail and we need to return a SyntaxError to the user with a character number. Parameters: **fn** (_Callable_) – The method to be decorated for backtracking. lkml.simple module[¶](#module-lkml.simple "Link to this heading") ------------------------------------------------------------------ Interface classes between the parse tree and a data structure of primitives. These classes facilitate parsing and generation to and from simple data structures like lists and dictionaries, and allow users to parse and generate LookML without needing to interact with the parse tree. _class_ lkml.simple.DictParser[¶](#lkml.simple.DictParser "Link to this definition") Parses a Python dictionary into a parse tree. Review the grammar specified for the Parser class to understand how LookML is represented. The grammar details the differences between blocks, pairs, keys, and values. parent\_key[¶](#lkml.simple.DictParser.parent_key "Link to this definition") The name of the key at the previous level in a LookML block. level[¶](#lkml.simple.DictParser.level "Link to this definition") The number of indentations appropriate for the current position. base\_indent[¶](#lkml.simple.DictParser.base_indent "Link to this definition") Whitespace representing one tab. latest\_node[¶](#lkml.simple.DictParser.latest_node "Link to this definition") The type of the last node to be parsed. decrease\_level() → [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.simple.DictParser.decrease_level "Link to this definition") Decreases the indent level of the current line by one tab. expand\_list(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _values: [Sequence](https://docs.python.org/3/library/typing.html#typing.Sequence "(in Python v3.13)") _) → [List](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.13)") \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ \][¶](#lkml.simple.DictParser.expand_list "Link to this definition") Expands and parses a list of values for a repeatable key. Parameters: * **key** – A repeatable LookML field type (e.g. “views” or “dimension\_groups”) * **values** – A sequence of objects to be parsed Returns: A list of block, list, or pair nodes, depending on the list’s contents. increase\_level() → [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.simple.DictParser.increase_level "Link to this definition") Increases the indent level of the current line by one tab. This also resets the latest node, mainly for formatting reasons. _property_ indent_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _[¶](#lkml.simple.DictParser.indent "Link to this definition") Returns the level-adjusted indent. is\_plural\_key(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _) → [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") [¶](#lkml.simple.DictParser.is_plural_key "Link to this definition") Returns True if the key is a repeatable key. For example, dimension can be repeated, but sql cannot be. The key allowed\_value is a special case and changes behavior depending on its parent key. If its parent key is access\_grant, it is a list and cannot be repeated. Otherwise, it can be repeated. The parent key query is also a special case, where children are kept as lists. See issue #53. Parameters: **key** – The name of the key to test. _property_ newline\_indent_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _[¶](#lkml.simple.DictParser.newline_indent "Link to this definition") Returns a newline plus the current indent. parse(_obj: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ \]_) → [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode") [¶](#lkml.simple.DictParser.parse "Link to this definition") Parses a primitive representation of LookML into a parse tree. parse\_any(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") | [list](https://docs.python.org/3/library/stdtypes.html#list "(in Python v3.13)") | [tuple](https://docs.python.org/3/library/stdtypes.html#tuple "(in Python v3.13)") | [dict](https://docs.python.org/3/library/stdtypes.html#dict "(in Python v3.13)") _) → [List](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.13)") \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ \] | [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode") | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode") | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode") [¶](#lkml.simple.DictParser.parse_any "Link to this definition") Dynamically serializes a Python object based on its type. Parameters: * **key** – A LookML field type (e.g. “suggestions” or “hidden”) * **value** – A string, tuple, or list to serialize Raises: [**TypeError**](https://docs.python.org/3/library/exceptions.html#TypeError "(in Python v3.13)") – If input value is not of a valid type Returns: A generator of serialized string chunks parse\_block(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _items: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ \]_, _name: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") \= None_) → [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode") [¶](#lkml.simple.DictParser.parse_block "Link to this definition") Serializes a dictionary to a LookML block. Parameters: * **key** – A LookML field type (e.g. “dimension”) * **fields** – A dictionary to serialize (e.g. {“sql”: “${TABLE}.order\_id”}) * **name** – An optional name of the block (e.g. “order\_id”) Returns: A generator of serialized string chunks parse\_list(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _values: [Sequence](https://docs.python.org/3/library/typing.html#typing.Sequence "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ | [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)")\ \]_) → [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode") [¶](#lkml.simple.DictParser.parse_list "Link to this definition") Serializes a sequence to a LookML block. Parameters: * **key** – A LookML field type (e.g. “fields”) * **values** – A sequence to serialize (e.g. \[“orders.order\_id”, “orders.item”\]) Returns: A generator of serialized string chunks parse\_pair(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _) → [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode") [¶](#lkml.simple.DictParser.parse_pair "Link to this definition") Serializes a key and value to a LookML pair. Parameters: * **key** – A LookML field type (e.g. “hidden”) * **value** – The value string (e.g. “yes”) Returns: A generator of serialized string chunks _static_ parse\_token(_key: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _force\_quote: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)") \= False_, _prefix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") \= ''_, _suffix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") \= ''_) → [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") [¶](#lkml.simple.DictParser.parse_token "Link to this definition") Parses a value into a token, quoting it if required by the key or forced. Parameters: * **key** – A LookML field type (e.g. “hidden”) * **value** – The value string (e.g. “yes”) * **force\_quote** – True if value should always be quoted Returns: A generator of serialized string chunks _property_ prefix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _[¶](#lkml.simple.DictParser.prefix "Link to this definition") Returns the currently appropriate, preceding whitespace. resolve\_filters(_values: [List](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.13)") \[[dict](https://docs.python.org/3/library/stdtypes.html#dict "(in Python v3.13)")\ \]_) → [List](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.13)") \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ \] | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode") [¶](#lkml.simple.DictParser.resolve_filters "Link to this definition") Parse the key `filters` according to the context. In LookML, the `filters` key is wildly inconsistent and can have three different syntaxes. This method determines the syntax that should be used based on the context and parses the appropriate node. Parameters: **values** – The contents of the `filters` block. Provides context to resolve. Returns: A block or list node depending on the resolution. _class_ lkml.simple.DictVisitor[¶](#lkml.simple.DictVisitor "Link to this definition") Creates a primitive representation of the parse tree. Traverses the parse tree and transforms each node type into a dict. Each dict is combined into one nested dict. Also handles the grouping of fields with plural keys like `dimension` or `view` into lists. depth[¶](#lkml.simple.DictVisitor.depth "Link to this definition") Tracks the level of nesting. update\_tree(_target: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") _, _update: [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") _) → [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)") [¶](#lkml.simple.DictVisitor.update_tree "Link to this definition") Add one dictionary to an existing dictionary, handling certain repeated keys. This method is primarily responsible for handling repeated keys in LookML like dimension or set, which can exist more than once in LookML but cannot be repeated in a Python dictionary. This method checks the list of valid repeated keys and combines the values of that key in target and/or update into a list and assigns a plural key (e.g. dimensions instead of dimension). Parameters: * **target** – Existing dictionary of parsed LookML * **update** – New dictionary to be added to target Raises: * [**KeyError**](https://docs.python.org/3/library/exceptions.html#KeyError "(in Python v3.13)") – If update has more than one key * [**KeyError**](https://docs.python.org/3/library/exceptions.html#KeyError "(in Python v3.13)") – If the key in update already exists and would overwrite existing visit(_document: [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode") _) → [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ \][¶](#lkml.simple.DictVisitor.visit "Link to this definition") visit\_block(_node: [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode") _) → [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)")\ \][¶](#lkml.simple.DictVisitor.visit_block "Link to this definition") Creates a dict from a block node by visiting its children. visit\_container(_node: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode") _) → [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ \][¶](#lkml.simple.DictVisitor.visit_container "Link to this definition") Creates a dict from a container node by visiting its children. visit\_list(_node: [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode") _) → [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [List](https://docs.python.org/3/library/typing.html#typing.List "(in Python v3.13)")\ \][¶](#lkml.simple.DictVisitor.visit_list "Link to this definition") Creates a dict from a list node by visiting its children. visit\_pair(_node: [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode") _) → [Dict](https://docs.python.org/3/library/typing.html#typing.Dict "(in Python v3.13)") \[[str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ , [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \][¶](#lkml.simple.DictVisitor.visit_pair "Link to this definition") Creates a dict from pair node by visiting its type and value tokens. visit\_token(_token: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken") _) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") [¶](#lkml.simple.DictVisitor.visit_token "Link to this definition") Creates a string from a syntax token. lkml.simple.flatten(_sequence: [list](https://docs.python.org/3/library/stdtypes.html#list "(in Python v3.13)") _) → [list](https://docs.python.org/3/library/stdtypes.html#list "(in Python v3.13)") [¶](#lkml.simple.flatten "Link to this definition") Flattens a singly-nested list of lists into a list of items. lkml.tokens module[¶](#module-lkml.tokens "Link to this heading") ------------------------------------------------------------------ Tokens used by the lexer to tokenize LookML. _class_ lkml.tokens.BlockEndToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.BlockEndToken "Link to this definition") Represents the end of a block. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= '}'_[¶](#lkml.tokens.BlockEndToken.id "Link to this definition") _class_ lkml.tokens.BlockStartToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.BlockStartToken "Link to this definition") Represents the start of a block. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= '{'_[¶](#lkml.tokens.BlockStartToken.id "Link to this definition") _class_ lkml.tokens.CommaToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.CommaToken "Link to this definition") Separates elements in a list. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= ','_[¶](#lkml.tokens.CommaToken.id "Link to this definition") _class_ lkml.tokens.CommentToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.CommentToken "Link to this definition") Represents a comment. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= ''_[¶](#lkml.tokens.CommentToken.id "Link to this definition") _class_ lkml.tokens.ContentToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.ContentToken "Link to this definition") Base class for LookML tokens that contain a string of content. _class_ lkml.tokens.ExpressionBlockEndToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.ExpressionBlockEndToken "Link to this definition") Represents the end of an expression block. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= ';;'_[¶](#lkml.tokens.ExpressionBlockEndToken.id "Link to this definition") _class_ lkml.tokens.ExpressionBlockToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.ExpressionBlockToken "Link to this definition") Contains the value of an expression block. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= ''_[¶](#lkml.tokens.ExpressionBlockToken.id "Link to this definition") _class_ lkml.tokens.InlineWhitespaceToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.InlineWhitespaceToken "Link to this definition") Represents one or more whitespace characters. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= ''_[¶](#lkml.tokens.InlineWhitespaceToken.id "Link to this definition") _class_ lkml.tokens.LinebreakToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.LinebreakToken "Link to this definition") Represents a newline character. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= ''_[¶](#lkml.tokens.LinebreakToken.id "Link to this definition") _class_ lkml.tokens.ListEndToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.ListEndToken "Link to this definition") Represents the end of a list. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= '\]'_[¶](#lkml.tokens.ListEndToken.id "Link to this definition") _class_ lkml.tokens.ListStartToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)") _)[¶](#lkml.tokens.ListStartToken "Link to this definition") Represents the start of a list. id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)") _ _\= '\['_[¶](#lkml.tokens.ListStartToken.id "Link to this definition")\ \ _class_ lkml.tokens.LiteralToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.LiteralToken "Link to this definition")\ \ Contains the value of an unquoted literal.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#lkml.tokens.LiteralToken.id "Link to this definition")\ \ _class_ lkml.tokens.QuotedLiteralToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.QuotedLiteralToken "Link to this definition")\ \ Contains the value of a quoted literal.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#lkml.tokens.QuotedLiteralToken.id "Link to this definition")\ \ _class_ lkml.tokens.StreamEndToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.StreamEndToken "Link to this definition")\ \ Represents the end of a stream of characters.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#lkml.tokens.StreamEndToken.id "Link to this definition")\ \ _class_ lkml.tokens.StreamStartToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.StreamStartToken "Link to this definition")\ \ Represents the start of a stream of characters.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#lkml.tokens.StreamStartToken.id "Link to this definition")\ \ _class_ lkml.tokens.Token(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.Token "Link to this definition")\ \ Base class for LookML tokens, lexed from LookML strings.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#lkml.tokens.Token.id "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _[¶](#lkml.tokens.Token.value "Link to this definition")\ \ _class_ lkml.tokens.TriviaToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.TriviaToken "Link to this definition")\ \ Represents a comment or whitespace.\ \ _class_ lkml.tokens.ValueToken(_line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.ValueToken "Link to this definition")\ \ Separates a key from a value.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ':'_[¶](#lkml.tokens.ValueToken.id "Link to this definition")\ \ _class_ lkml.tokens.WhitespaceToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ _)[¶](#lkml.tokens.WhitespaceToken "Link to this definition")\ \ Represents one or more whitespace characters.\ \ id_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#lkml.tokens.WhitespaceToken.id "Link to this definition")\ \ lkml.tree module[¶](#module-lkml.tree "Link to this heading")\ \ --------------------------------------------------------------\ \ Node and token classes that make up the parse tree.\ \ _class_ lkml.tree.BlockNode(_type: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _, _left\_brace: [LeftCurlyBrace](#lkml.tree.LeftCurlyBrace "lkml.tree.LeftCurlyBrace")\ \= LeftCurlyBrace(value='{', line\_number=None, prefix='', suffix='\\n')_, _right\_brace: [RightCurlyBrace](#lkml.tree.RightCurlyBrace "lkml.tree.RightCurlyBrace")\ \= RightCurlyBrace(value='}', line\_number=None, prefix='\\n', suffix='')_, _colon: [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ \= Colon(value=':', line\_number=None, prefix='', suffix=' ')_, _name: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ \= None_, _container: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ \= ContainerNode()_)[¶](#lkml.tree.BlockNode "Link to this definition")\ \ A LookML block, enclosed in curly braces. Like `view` or `dimension`.\ \ type[¶](#lkml.tree.BlockNode.type "Link to this definition")\ \ The field type, the value that precedes the colon.\ \ Type:\ \ [lkml.tree.SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ \ left\_brace[¶](#lkml.tree.BlockNode.left_brace "Link to this definition")\ \ A syntax token for the opening brace “{“.\ \ Type:\ \ [lkml.tree.LeftCurlyBrace](#lkml.tree.LeftCurlyBrace "lkml.tree.LeftCurlyBrace")\ \ right\_brace[¶](#lkml.tree.BlockNode.right_brace "Link to this definition")\ \ A syntax token for the closing brace “}”.\ \ Type:\ \ [lkml.tree.RightCurlyBrace](#lkml.tree.RightCurlyBrace "lkml.tree.RightCurlyBrace")\ \ colon[¶](#lkml.tree.BlockNode.colon "Link to this definition")\ \ An optional Colon SyntaxToken. If not supplied, a default colon is created with a single space suffix after the colon.\ \ Type:\ \ [lkml.tree.Colon](#lkml.tree.Colon "lkml.tree.Colon")\ | None\ \ name[¶](#lkml.tree.BlockNode.name "Link to this definition")\ \ An optional name token, the value that follows the colon.\ \ Type:\ \ [lkml.tree.SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ | None\ \ container[¶](#lkml.tree.BlockNode.container "Link to this definition")\ \ A container node that holds the block’s child nodes.\ \ Type:\ \ [lkml.tree.ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ \ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.BlockNode.accept "Link to this definition")\ \ Accepts a visitor and calls the visitor’s block method on itself.\ \ _property_ children_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ , ...\]_[¶](#lkml.tree.BlockNode.children "Link to this definition")\ \ Returns all child SyntaxNodes, but not SyntaxTokens.\ \ colon_: [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _ _\= Colon(value=':', line\_number=None, prefix='', suffix=' ')_[¶](#id0 "Link to this definition")\ \ container_: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ _ _\= ContainerNode()_[¶](#id1 "Link to this definition")\ \ left\_brace_: [LeftCurlyBrace](#lkml.tree.LeftCurlyBrace "lkml.tree.LeftCurlyBrace")\ _ _\= LeftCurlyBrace(value='{', line\_number=None, prefix='', suffix='\\n')_[¶](#id2 "Link to this definition")\ \ _property_ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.BlockNode.line_number "Link to this definition")\ \ Returns the line number of the first SyntaxToken in the node\ \ name_: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _ _\= None_[¶](#id3 "Link to this definition")\ \ right\_brace_: [RightCurlyBrace](#lkml.tree.RightCurlyBrace "lkml.tree.RightCurlyBrace")\ _ _\= RightCurlyBrace(value='}', line\_number=None, prefix='\\n', suffix='')_[¶](#id4 "Link to this definition")\ \ type_: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _[¶](#id5 "Link to this definition")\ \ _class_ lkml.tree.Colon(_value: 'str' \= ':'_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.Colon "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ':'_[¶](#lkml.tree.Colon.value "Link to this definition")\ \ _class_ lkml.tree.Comma(_value: 'str' \= ','_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.Comma "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ','_[¶](#lkml.tree.Comma.value "Link to this definition")\ \ _class_ lkml.tree.ContainerNode(_items: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ , ...\]_, _top\_level: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)")\ \= False_)[¶](#lkml.tree.ContainerNode "Link to this definition")\ \ A sequence of nodes, either at the top level of a document, or within a block.\ \ items[¶](#lkml.tree.ContainerNode.items "Link to this definition")\ \ A tuple of the contained nodes.\ \ Type:\ \ Tuple\[[lkml.tree.BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [lkml.tree.PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ | [lkml.tree.ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ , …\]\ \ top\_level[¶](#lkml.tree.ContainerNode.top_level "Link to this definition")\ \ If the container is the top level of the LookML document.\ \ Type:\ \ [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)")\ \ Raises:\ \ [**KeyError**](https://docs.python.org/3/library/exceptions.html#KeyError "(in Python v3.13)")\ – If a key already exists in the tree and would be overwritten.\ \ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.ContainerNode.accept "Link to this definition")\ \ Accepts a visitor and calls the visitor’s container method on itself.\ \ _property_ children_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ , ...\]_[¶](#lkml.tree.ContainerNode.children "Link to this definition")\ \ Returns all child SyntaxNodes, but not SyntaxTokens.\ \ items_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ | [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ | [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ , ...\]_[¶](#id6 "Link to this definition")\ \ line\_number() → [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ [¶](#lkml.tree.ContainerNode.line_number "Link to this definition")\ \ Returns the line number of the first SyntaxToken in the node\ \ top\_level_: [bool](https://docs.python.org/3/library/functions.html#bool "(in Python v3.13)")\ _ _\= False_[¶](#id7 "Link to this definition")\ \ _class_ lkml.tree.DocumentNode(_container: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ _, _prefix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \= ''_, _suffix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \= ''_)[¶](#lkml.tree.DocumentNode "Link to this definition")\ \ The root node of the parse tree.\ \ container[¶](#lkml.tree.DocumentNode.container "Link to this definition")\ \ The top-level container node.\ \ Type:\ \ [lkml.tree.ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ \ prefix[¶](#lkml.tree.DocumentNode.prefix "Link to this definition")\ \ Leading whitespace or comments before the document.\ \ Type:\ \ [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \ suffix[¶](#lkml.tree.DocumentNode.suffix "Link to this definition")\ \ Trailing whitespace or comments after the document.\ \ Type:\ \ [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.DocumentNode.accept "Link to this definition")\ \ Accepts a visitor and calls the visitor’s visit method on itself.\ \ _property_ children_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ \]_[¶](#lkml.tree.DocumentNode.children "Link to this definition")\ \ Returns all child SyntaxNodes, but not SyntaxTokens.\ \ container_: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ _[¶](#id8 "Link to this definition")\ \ _property_ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.DocumentNode.line_number "Link to this definition")\ \ Returns the line number of the first SyntaxToken in the node\ \ prefix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#id9 "Link to this definition")\ \ suffix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#id10 "Link to this definition")\ \ _class_ lkml.tree.DoubleSemicolon(_value: 'str' \= ';;'_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.DoubleSemicolon "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ';;'_[¶](#lkml.tree.DoubleSemicolon.value "Link to this definition")\ \ _class_ lkml.tree.ExpressionSyntaxToken(_value: 'str'_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ' '_, _suffix: 'str' \= ''_, _expr\_suffix: 'str' \= ' '_)[¶](#lkml.tree.ExpressionSyntaxToken "Link to this definition")\ \ expr\_suffix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ' '_[¶](#lkml.tree.ExpressionSyntaxToken.expr_suffix "Link to this definition")\ \ prefix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ' '_[¶](#lkml.tree.ExpressionSyntaxToken.prefix "Link to this definition")\ \ _class_ lkml.tree.LeftBracket(_value: 'str' \= '\['_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.LeftBracket "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= '\['_[¶](#lkml.tree.LeftBracket.value "Link to this definition")\ \ _class_ lkml.tree.LeftCurlyBrace(_value: 'str' \= '{'_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.LeftCurlyBrace "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= '{'_[¶](#lkml.tree.LeftCurlyBrace.value "Link to this definition")\ \ _class_ lkml.tree.ListNode(_type: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _, _items: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ , ...\] | [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ , ...\]_, _left\_bracket: [LeftBracket](#lkml.tree.LeftBracket "lkml.tree.LeftBracket")\ _, _right\_bracket: [RightBracket](#lkml.tree.RightBracket "lkml.tree.RightBracket")\ _, _colon: [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ \= Colon(value=':', line\_number=None, prefix='', suffix=' ')_, _leading\_comma: [Comma](#lkml.tree.Comma "lkml.tree.Comma")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ \= None_, _trailing\_comma: [Comma](#lkml.tree.Comma "lkml.tree.Comma")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ \= None_)[¶](#lkml.tree.ListNode "Link to this definition")\ \ A LookML list, enclosed in square brackets. Like `fields` or `filters`.\ \ type[¶](#lkml.tree.ListNode.type "Link to this definition")\ \ The field type, the value that precedes the colon.\ \ Type:\ \ [lkml.tree.SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ \ items[¶](#lkml.tree.ListNode.items "Link to this definition")\ \ A tuple of pair nodes or syntax tokens, depending on the list style.\ \ Type:\ \ Tuple\[[lkml.tree.PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ , …\] | Tuple\[[lkml.tree.SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ , …\]\ \ left\_bracket[¶](#lkml.tree.ListNode.left_bracket "Link to this definition")\ \ A syntax token for the opening bracket “\[“.\ \ Type:\ \ [lkml.tree.LeftBracket](#lkml.tree.LeftBracket "lkml.tree.LeftBracket")\ \ right\_bracket[¶](#lkml.tree.ListNode.right_bracket "Link to this definition")\ \ A syntax token for the closing bracket “\]”.\ \ Type:\ \ [lkml.tree.RightBracket](#lkml.tree.RightBracket "lkml.tree.RightBracket")\ \ colon[¶](#lkml.tree.ListNode.colon "Link to this definition")\ \ An optional Colon SyntaxToken. If not supplied, a default colon is created with a single space suffix after the colon.\ \ Type:\ \ [lkml.tree.Colon](#lkml.tree.Colon "lkml.tree.Colon")\ \ trailing\_comma[¶](#lkml.tree.ListNode.trailing_comma "Link to this definition")\ \ Include a trailing comma after the last item.\ \ Type:\ \ [lkml.tree.Comma](#lkml.tree.Comma "lkml.tree.Comma")\ | None\ \ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.ListNode.accept "Link to this definition")\ \ Accepts a visitor and calls the visitor’s list method on itself.\ \ _property_ children_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ , ...\]_[¶](#lkml.tree.ListNode.children "Link to this definition")\ \ Returns all child SyntaxNodes, but not SyntaxTokens.\ \ colon_: [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ _ _\= Colon(value=':', line\_number=None, prefix='', suffix=' ')_[¶](#id11 "Link to this definition")\ \ items_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ , ...\] | [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ , ...\]_[¶](#id12 "Link to this definition")\ \ leading\_comma_: [Comma](#lkml.tree.Comma "lkml.tree.Comma")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _ _\= None_[¶](#lkml.tree.ListNode.leading_comma "Link to this definition")\ \ left\_bracket_: [LeftBracket](#lkml.tree.LeftBracket "lkml.tree.LeftBracket")\ _[¶](#id13 "Link to this definition")\ \ _property_ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.ListNode.line_number "Link to this definition")\ \ Returns the line number of the first SyntaxToken in the node\ \ right\_bracket_: [RightBracket](#lkml.tree.RightBracket "lkml.tree.RightBracket")\ _[¶](#id14 "Link to this definition")\ \ trailing\_comma_: [Comma](#lkml.tree.Comma "lkml.tree.Comma")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _ _\= None_[¶](#id15 "Link to this definition")\ \ type_: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _[¶](#id16 "Link to this definition")\ \ _class_ lkml.tree.PairNode(_type: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _, _value: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _, _colon: [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ \= Colon(value=':', line\_number=None, prefix='', suffix=' ')_)[¶](#lkml.tree.PairNode "Link to this definition")\ \ A simple LookML field, e.g. `hidden: yes`.\ \ type[¶](#lkml.tree.PairNode.type "Link to this definition")\ \ The field type, the value that precedes the colon.\ \ Type:\ \ [lkml.tree.SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ \ value[¶](#lkml.tree.PairNode.value "Link to this definition")\ \ The field value, the value that follows the colon.\ \ Type:\ \ [lkml.tree.SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ \ colon[¶](#lkml.tree.PairNode.colon "Link to this definition")\ \ An optional Colon SyntaxToken. If not supplied, a default colon is created with a single space suffix after the colon.\ \ Type:\ \ [lkml.tree.Colon](#lkml.tree.Colon "lkml.tree.Colon")\ \ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.PairNode.accept "Link to this definition")\ \ Accepts a visitor and calls the visitor’s pair method on itself.\ \ _property_ children_: [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.PairNode.children "Link to this definition")\ \ Returns all child SyntaxNodes, but not SyntaxTokens.\ \ colon_: [Colon](#lkml.tree.Colon "lkml.tree.Colon")\ _ _\= Colon(value=':', line\_number=None, prefix='', suffix=' ')_[¶](#id17 "Link to this definition")\ \ _property_ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.PairNode.line_number "Link to this definition")\ \ Returns the line number of the first SyntaxToken in the node\ \ type_: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _[¶](#id18 "Link to this definition")\ \ value_: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _[¶](#id19 "Link to this definition")\ \ _class_ lkml.tree.QuotedSyntaxToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ \= None_, _prefix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \= ''_, _suffix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \= ''_)[¶](#lkml.tree.QuotedSyntaxToken "Link to this definition")\ \ format\_value() → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ [¶](#lkml.tree.QuotedSyntaxToken.format_value "Link to this definition")\ \ Returns the value itself, subclasses may modify the value first.\ \ _class_ lkml.tree.RightBracket(_value: 'str' \= '\]'_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.RightBracket "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= '\]'_[¶](#lkml.tree.RightBracket.value "Link to this definition")\ \ _class_ lkml.tree.RightCurlyBrace(_value: 'str' \= '}'_, _line\_number: 'Optional\[int\]' \= None_, _prefix: 'str' \= ''_, _suffix: 'str' \= ''_)[¶](#lkml.tree.RightCurlyBrace "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= '}'_[¶](#lkml.tree.RightCurlyBrace.value "Link to this definition")\ \ _class_ lkml.tree.SyntaxNode[¶](#lkml.tree.SyntaxNode "Link to this definition")\ \ Abstract base class for members of the parse tree that have child nodes.\ \ _abstract_ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.SyntaxNode.accept "Link to this definition")\ \ Accepts a Visitor that can interact with the node.\ \ The visitor pattern allows for flexible algorithms that can traverse the tree without needing to be defined as methods on the tree itself.\ \ _abstract property_ children_: [Tuple](https://docs.python.org/3/library/typing.html#typing.Tuple "(in Python v3.13)")\ \[[SyntaxNode](#lkml.tree.SyntaxNode "lkml.tree.SyntaxNode")\ , ...\] | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.SyntaxNode.children "Link to this definition")\ \ Returns all child SyntaxNodes, but not SyntaxTokens.\ \ _abstract property_ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _[¶](#lkml.tree.SyntaxNode.line_number "Link to this definition")\ \ Returns the line number of the first SyntaxToken in the node\ \ _class_ lkml.tree.SyntaxToken(_value: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _, _line\_number: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ \= None_, _prefix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \= ''_, _suffix: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \= ''_)[¶](#lkml.tree.SyntaxToken "Link to this definition")\ \ Stores a text value with optional prefix or suffix trivia.\ \ For example, a syntax token might represent meaningful punctuation like a curly brace or the type or value of a LookML field. A syntax token can also store trivia, comments or whitespace that precede or follow the token value. The parser attempts to assign these prefixes and suffixes intelligently to the corresponding tokens.\ \ value[¶](#lkml.tree.SyntaxToken.value "Link to this definition")\ \ The text represented by the token.\ \ Type:\ \ [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \ prefix[¶](#lkml.tree.SyntaxToken.prefix "Link to this definition")\ \ Comments or whitespace preceding the token.\ \ Type:\ \ [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \ suffix[¶](#lkml.tree.SyntaxToken.suffix "Link to this definition")\ \ Comments or whitespace following the token.\ \ Type:\ \ [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ \ accept(_visitor: [Visitor](#lkml.tree.Visitor "lkml.tree.Visitor")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.SyntaxToken.accept "Link to this definition")\ \ Accepts a visitor and calls the visitor’s token method on itself.\ \ format\_value() → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ [¶](#lkml.tree.SyntaxToken.format_value "Link to this definition")\ \ Returns the value itself, subclasses may modify the value first.\ \ line\_number_: [int](https://docs.python.org/3/library/functions.html#int "(in Python v3.13)")\ | [None](https://docs.python.org/3/library/constants.html#None "(in Python v3.13)")\ _ _\= None_[¶](#lkml.tree.SyntaxToken.line_number "Link to this definition")\ \ prefix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#id20 "Link to this definition")\ \ suffix_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _ _\= ''_[¶](#id21 "Link to this definition")\ \ value_: [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ _[¶](#id22 "Link to this definition")\ \ _class_ lkml.tree.Visitor[¶](#lkml.tree.Visitor "Link to this definition")\ \ Abstract base class for visitors that interact with the parse tree.\ \ _abstract_ visit(_document: [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.Visitor.visit "Link to this definition")\ \ _abstract_ visit\_block(_node: [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.Visitor.visit_block "Link to this definition")\ \ _abstract_ visit\_container(_node: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.Visitor.visit_container "Link to this definition")\ \ _abstract_ visit\_list(_node: [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.Visitor.visit_list "Link to this definition")\ \ _abstract_ visit\_pair(_node: [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.Visitor.visit_pair "Link to this definition")\ \ _abstract_ visit\_token(_token: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _) → [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ [¶](#lkml.tree.Visitor.visit_token "Link to this definition")\ \ lkml.tree.items\_to\_str(_\*items: [Any](https://docs.python.org/3/library/typing.html#typing.Any "(in Python v3.13)")\ _) → [str](https://docs.python.org/3/library/stdtypes.html#str "(in Python v3.13)")\ [¶](#lkml.tree.items_to_str "Link to this definition")\ \ Converts each item to a string and joins them together.\ \ lkml.visitors module[¶](#module-lkml.visitors "Link to this heading")\ \ ----------------------------------------------------------------------\ \ _class_ lkml.visitors.BasicTransformer[¶](#lkml.visitors.BasicTransformer "Link to this definition")\ \ Bases: [`Visitor`](#lkml.tree.Visitor "lkml.tree.Visitor")\ \ Visitor class that returns a new tree, modifying the tree as needed.\ \ visit(_node: [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode")\ _) → [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode")\ [¶](#lkml.visitors.BasicTransformer.visit "Link to this definition")\ \ visit\_block(_node: [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ _) → [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ [¶](#lkml.visitors.BasicTransformer.visit_block "Link to this definition")\ \ visit\_container(_node: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ _) → [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ [¶](#lkml.visitors.BasicTransformer.visit_container "Link to this definition")\ \ visit\_list(_node: [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ _) → [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ [¶](#lkml.visitors.BasicTransformer.visit_list "Link to this definition")\ \ visit\_pair(_node: [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ _) → [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ [¶](#lkml.visitors.BasicTransformer.visit_pair "Link to this definition")\ \ visit\_token(_token: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _) → [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ [¶](#lkml.visitors.BasicTransformer.visit_token "Link to this definition")\ \ _class_ lkml.visitors.BasicVisitor[¶](#lkml.visitors.BasicVisitor "Link to this definition")\ \ Bases: [`Visitor`](#lkml.tree.Visitor "lkml.tree.Visitor")\ \ Visitor class that calls the `_visit` method for every node type.\ \ This class doesn’t actually do anything when visiting a tree other than traverse the nodes. It’s meant to be used as a base class for building more useful and complex visitors. For example, override any of the `visit_` methods for node-type specific behavior.\ \ visit(_document: [DocumentNode](#lkml.tree.DocumentNode "lkml.tree.DocumentNode")\ _)[¶](#lkml.visitors.BasicVisitor.visit "Link to this definition")\ \ visit\_block(_node: [BlockNode](#lkml.tree.BlockNode "lkml.tree.BlockNode")\ _)[¶](#lkml.visitors.BasicVisitor.visit_block "Link to this definition")\ \ visit\_container(_node: [ContainerNode](#lkml.tree.ContainerNode "lkml.tree.ContainerNode")\ _)[¶](#lkml.visitors.BasicVisitor.visit_container "Link to this definition")\ \ visit\_list(_node: [ListNode](#lkml.tree.ListNode "lkml.tree.ListNode")\ _)[¶](#lkml.visitors.BasicVisitor.visit_list "Link to this definition")\ \ visit\_pair(_node: [PairNode](#lkml.tree.PairNode "lkml.tree.PairNode")\ _)[¶](#lkml.visitors.BasicVisitor.visit_pair "Link to this definition")\ \ visit\_token(_token: [SyntaxToken](#lkml.tree.SyntaxToken "lkml.tree.SyntaxToken")\ _)[¶](#lkml.visitors.BasicVisitor.visit_token "Link to this definition")\ \ _class_ lkml.visitors.LookMlVisitor[¶](#lkml.visitors.LookMlVisitor "Link to this definition")\ \ Bases: [`BasicVisitor`](#lkml.visitors.BasicVisitor "lkml.visitors.BasicVisitor")\ \ Converts a parse tree into a string by casting every node.\ \ [lkml](index.html)\ \ ===================\ \ A speedy LookML parser and serializer implemented in pure Python.\ \ ### Navigation\ \ * [Installation](install.html)\ \ * [Simple LookML parsing](simple.html)\ \ * [Parsing from the command line](cli.html)\ \ * [Advanced LookML parsing](advanced.html)\ \ * [API reference](#)\ * [Module contents](#module-lkml)\ \ * [lkml.keys module](#module-lkml.keys)\ \ * [lkml.lexer module](#module-lkml.lexer)\ \ * [lkml.parser module](#module-lkml.parser)\ \ * [lkml.simple module](#module-lkml.simple)\ \ * [lkml.tokens module](#module-lkml.tokens)\ \ * [lkml.tree module](#module-lkml.tree)\ \ * [lkml.visitors module](#module-lkml.visitors)\ \ \ ### Related Topics\ \ * [Documentation overview](index.html)\ * Previous: [Advanced LookML parsing](advanced.html "previous chapter")\ \ \ ### Quick search --- # Python Module Index — lkml documentation Python Module Index =================== [**l**](#cap-l) | | | | | --- | --- | --- | | | | | | | **l** | | | ![-](_static/minus.png) | [`lkml`](lkml.html#module-lkml) | | | | [`lkml.keys`](lkml.html#module-lkml.keys) | | | | [`lkml.lexer`](lkml.html#module-lkml.lexer) | | | | [`lkml.parser`](lkml.html#module-lkml.parser) | | | | [`lkml.simple`](lkml.html#module-lkml.simple) | | | | [`lkml.tokens`](lkml.html#module-lkml.tokens) | | | | [`lkml.tree`](lkml.html#module-lkml.tree) | | | | [`lkml.visitors`](lkml.html#module-lkml.visitors) | | [lkml](index.html) =================== A speedy LookML parser and serializer implemented in pure Python. ### Navigation * [Installation](install.html) * [Simple LookML parsing](simple.html) * [Parsing from the command line](cli.html) * [Advanced LookML parsing](advanced.html) * [API reference](lkml.html) ### Related Topics * [Documentation overview](index.html) ### Quick search --- # Unknown Installation ============ lkml is installed via \`pip \`\_ with the following command: .. code-block:: bash pip install lkml Once you've installed lkml successfully, check out :doc:\`simple\`. --- # Unknown Parsing from the command line ============================= lkml can also be used as a command-line tool. It accepts a single argument: the path to the LookML file to be parsed. When called from the command line, lkml emits the parsed result as a JSON string: .. code-block:: bash lkml orders.view.lkml If you would like to save the result to a file, you can pipe the output as follows. .. code-block:: bash lkml path/to/file.view.lkml > path/to/result.json Parsing in debug mode --------------------- Providing the \`\`-v\`\` or \`\`--verbose\`\` argument at the command line turns on debug, or verbose mode. In debug mode, lkml will emit its attempts to parse each bit of the LookML string (called a token). Here's an example of the output: .. code-block:: bash lkml -v orders.view.lkml lkml.parser DEBUG: Check StreamStartToken() == StreamStartToken lkml.parser DEBUG: Check LiteralToken(view) == CommentToken or WhitespaceToken lkml.parser DEBUG: Try to parse \[expression\] = (block / pair / list)\* lkml.parser DEBUG: . Check LiteralToken(view) == CommentToken or WhitespaceToken lkml.parser DEBUG: . Check LiteralToken(view) == StreamEndToken or BlockEndToken lkml.parser DEBUG: . Try to parse \[block\] = key literal? '{' expression '}' lkml.parser DEBUG: . . Try to parse \[key\] = literal ':' lkml.parser DEBUG: . . . Check LiteralToken(view) == CommentToken or WhitespaceToken lkml.parser DEBUG: . . . Check LiteralToken(view) == LiteralToken lkml.parser DEBUG: . . . Check ValueToken() == CommentToken or WhitespaceToken lkml.parser DEBUG: . . . Check ValueToken() == ValueToken lkml.parser DEBUG: . . . Check WhitespaceToken(' ') == CommentToken or WhitespaceToken lkml.parser DEBUG: . . . Check LiteralToken(view\_name) == CommentToken or WhitespaceToken lkml.parser DEBUG: . . Successfully parsed key. lkml iterates through each token in the input and attempts to match it to a line of \*\*grammar\*\*. If lkml tries all known grammar options and doesn't find a match, it will throw a syntax error and exit. Debug mode helps you understand what lkml is expecting in the input and why it wasn't matched. --- # Unknown .. testsetup:: import lkml Advanced LookML parsing ======================= :py:func:\`lkml.load\` and :py:func:\`lkml.dump\` provide a simple interface between LookML and Python primitive data structures. However, :py:func:\`lkml.load\` discards information about comments and whitespace, making lossless modification of LookML impossible. For example, let's say we wanted to programmatically add a description to the dimension in this snippet of LookML: .. code-block:: # Inventory-related dimensions here dimension: days\_in\_inventory { sql: ${TABLE}.days\_in\_inventory ;; } If we parse this LookML with :py:func:\`lkml.load\`, we'll lose the comment and any information about the surrounding whitespace: .. doctest:: >>> text = """ ... # Inventory-related dimensions here ... ... dimension: days\_in\_inventory { sql: ${TABLE}.days\_in\_inventory ;; } ... """ >>> parsed = lkml.load(text) >>> parsed {'dimensions': \[{'sql': '${TABLE}.days\_in\_inventory', 'name': 'days\_in\_inventory'}\]} Writing this dictionary back to LookML with :py:func:\`lkml.dump\` yields the following: .. doctest:: >>> print(lkml.dump(parsed)) dimension: days\_in\_inventory { sql: ${TABLE}.days\_in\_inventory ;; } The comment is missing and the whitespace has been overriden by :py:func:\`lkml.dump\`'s opinionated formatting. If we want to preserve the exact whitespace and comments surrounding this dimension, we'll need to dive under the hood of lkml and directly modify the \*\*parse tree\*\*. The parse tree -------------- The parse tree is an \`immutable\` tree structure generated by lkml that holds the relevant information about the parsed LookML. Each node in the tree is either a \*\*syntax node\*\* (a node with \*\*children\*\*) or a \*\*syntax token\*\* (a leaf node). .. autoclass:: lkml.tree.SyntaxNode :members: :noindex: .. autoclass:: lkml.tree.SyntaxToken :noindex: You can think of syntax tokens as the fundamental pieces of text that make up LookML. Whitespace and comments are collectively referred to as \*\*trivia\*\* and are stored in syntax tokens in their \*\*prefix\*\* and \*\*suffix\*\* attributes. Types of nodes ^^^^^^^^^^^^^^ All lkml parse trees begin with a :py:class:\`lkml.tree.DocumentNode\`, the root node of the tree. A \`\`DocumentNode\`\` has a single attribute, \`\`container\`\`, a :py:class:\`lkml.tree.ContainerNode\`, which stores all of the top-level nodes in the document. Children of the \`\`ContainerNode\`\` can be instances of :py:class:\`lkml.tree.BlockNode\`, :py:class:\`lkml.tree.ListNode\`, or :py:class:\`lkml.tree.PairNode\`. Block nodes store children in container nodes of their own, and list nodes may have block nodes or pair nodes as children. Creating an example node ^^^^^^^^^^^^^^^^^^^^^^^^ In lkml, \`\`hidden: yes\`\` is represented as a \`\`PairNode\`\`. A \`\`PairNode\`\` has two attributes, \`\`type\`\` and \`\`value\`\`, where each are \`\`SyntaxTokens\`\`. You'll notice the \`\`PairNode\`\` also stores a special kind of \`\`SyntaxToken\`\` to represent the colon ":" between the type and the value. .. autoclass:: lkml.tree.PairNode :noindex: We could build a \`\`PairNode\`\` for \`\`hidden: yes\`\` from scratch as follows: .. doctest:: >>> from lkml.tree import PairNode, SyntaxToken >>> node = PairNode( ... type=SyntaxToken('hidden'), ... value=SyntaxToken('yes') ... ) >>> print(str(node)) hidden: yes We could include this simple pair node as a child in a block or container node, the beginnings of a more complex piece of LookML. Generating the parse tree ^^^^^^^^^^^^^^^^^^^^^^^^^ Creating nodes and tokens by hand is tedious, so it's more likely that you will be parsing a LookML string into a parse tree with :py:func:\`lkml.parse\`. .. doctest:: >>> lkml.parse('hidden: yes') DocumentNode(container=ContainerNode(), prefix='', suffix='') This tree can be analyzed with a visitor or modified with a transformer. To learn more about the parse tree and the different kinds of nodes, read the full API reference for the :ref:\`tree-ref\`. Traversing and modifying the parse tree --------------------------------------- The parse tree follows a design pattern called the \`visitor pattern \`\_. The visitor pattern allows us to define flexible algorithms that interact with the tree without having to implement those algorithms as methods on the tree's node classes. Each node implements a method called \`\`accept\`\`, that accepts a :py:class:\`lkml.tree.Visitor\` instance and passes itself to the corresponding \`\`visit\_\`\` method on the visitor. Here's the \`\`accept\`\` method for a \`\`ListNode\`\`. .. automethod:: lkml.tree.ListNode.accept :noindex: :: def accept(self, visitor: Visitor) -> Any: """Accepts a visitor and calls the visitor's list method on itself.""" return visitor.visit\_list(self) In our visitor, we can define \`\`visit\_list\`\` however we want---giving us tons of flexibility over how we design the visitor. A simple visitor class ^^^^^^^^^^^^^^^^^^^^^^ For example, we could write a linting visitor that traverses the parse tree and throws an error if it finds a dimension without a description:: from lkml.visitors import BasicVisitor class DescriptionVisitor(BasicVisitor): def visit\_block(self, block: BlockNode): """For each block, check if it's a dimension and if it has a description.""" if block.type.value == 'dimension': child\_types = \[node.type.value for node in block.container.items\] if 'description' not in child\_types: raise KeyError(f'Dimension {block.name.value} does not have a description') # Assume we already have a parse tree to visit tree.accept(DescriptionVisitor()) :py:class:\`lkml.visitors.BasicVisitor\`, will traverse the tree but do nothing. We can simply override that default behavior for \`\`visit\_block\`\` so we can inspect the dimensions, which are \`\`BlockNodes\`\`. For each block that is a dimension, we iterate through its children and throw an error if a child with the description type is not present. Modifying the parse tree with transformers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Because syntax nodes and tokens are immutable, you can't change them once created, you may only replace or remove them. This makes modifying the parse tree challenging, because the entire tree needs to be rebuilt for each change. lkml includes a basic transformer, :py:class:\`lkml.visitors.BasicTransformer\`, which like the visitors, traverses the tree. However, the transformer visits and replaces each node's children, allowing the immutable parse tree to be rebuilt with modifications. As an example, let's write a transformer that injects a user attribute into each \`\`sql\_table\_name\`\` field. This is something that could easily be solved with regex, but let's write a transformer as an example instead:: from dataclasses import replace from lkml.visitors import BasicTransformer class TableNameTransformer(BasicTransformer): def visit\_pair(self, node: PairNode) -> PairNode: """Visit each pair and replace the SQL table schema with a user attribute.""" if node.type.value == 'sql\_table\_name': try: schema, table\_name = node.value.value.split('.') # Sometimes the table name won't have a schema except ValueError: table\_name = node.value.value new\_value: str = '{{ \_user\_attributes\["dbt\_schema"\] }}.' + table\_name new\_node: PairNode = replace(node, value=ExpressionSyntaxToken(new\_value)) return new\_node else: return node # Assume we already have a parse tree to visit tree.accept(TableNameTransformer()) This transformer traverses the parse tree and modifies all \`\`PairNodes\`\` that have the \`\`sql\_table\_name\`\` type, injecting a user attribute into the expression. We rely on the dataclasses function :py:func:\`dataclasses.replace\`, which allows us to copy an immutable node (all lkml nodes are frozen, immutable dataclasses) with modifications---in this case, to the \`\`value\`\` attribute of the \`\`PairNode\`\`. Generating LookML from the parse tree ------------------------------------- Generating LookML from the parse tree is simple because each node class defines its own \`\`\_\_str\_\_\`\` method to serialize its contents. To generate a LookML string from any part of the tree, just cast it with \`\`str\`\`:: tree: DocumentNode str(tree) How does lkml build the parse tree? ----------------------------------- lkml is made up of two components, a \`lexer \`\_ and a parser. The parser is a \`recursive descent parser \`\_ with backtracking. First, the lexer scans through the input string character by character and generates a stream of relevant tokens. The lexer skips over whitespace when it's not relevant. For example, the input string:: "sql: ${TABLE}.order\_date ;;" would be broken into the tuple of tokens:: ( LiteralToken(sql), ValueToken(), ExpressionBlockToken(${TABLE}.order\_date), ExpressionBlockEndToken() ) Next, the parser scans through the stream of tokens. It marks its position in the stream, then attempts to identify a matching rule in the grammar. If the rule is made up of other rules (this is a called a non-terminal), it descends recursively through the constituent rules looking for tokens that match. If it doesn't find a match for a rule, it backtracks to a previously marked point in the stream and tries the next available rule. If the parser runs out of rules to try, it raises a syntax error. As the parser finds matches, it adds the relevant token values to its syntax tree, which is eventually returned to the user if the input parses successfully. --- # Unknown Simple LookML parsing ===================== Parsing a LookML string into Python is easy. Pass the LookML string into :py:func:\`lkml.load\`. .. doctest:: >>> import lkml >>> from pprint import pprint >>> lookml = """ ... dimension: order\_id { ... sql: ${TABLE}.order\_id ;; ... } ... ... dimension\_group: created { ... group\_label: "Order Date" ... type: time ... timeframes: \[hour, date, week, month, year\] ... sql: ${TABLE}.created\_at ;; ... } ... ... """ >>> result = lkml.load(lookml) >>> pprint(result) {'dimension\_groups': \[{'group\_label': 'Order Date', 'name': 'created', 'sql': '${TABLE}.created\_at', 'timeframes': \['hour', 'date', 'week', 'month', 'year'\], 'type': 'time'}\], 'dimensions': \[{'name': 'order\_id', 'sql': '${TABLE}.order\_id'}\]} :py:func:\`lkml.load\` also supports parsing LookML directly from files:: with open('orders.view.lkml', 'r') as file: result = lkml.load(file) How LookML is represented by lkml --------------------------------- When using :py:func:\`lkml.load\`, LookML is parsed into a JSON-like, nested dictionary format. From here on, we'll refer to LookML field names (e.g. \`\`sql\_table\_name\`\`, \`\`view\`\`, or \`\`join\`\`) as \*\*keys\*\*. Blocks with keys like \`\`dimension\`\` and \`\`view\`\` become dictionaries. lkml adds a key called \`\`name\`\` if the block has a name, like the name of the dimension or view. .. doctest:: >>> lookml = """ ... dimension: order\_id { hidden: yes } ... """ >>> result = lkml.load(lookml) >>> pprint(result) {'dimensions': \[{'hidden': 'yes', 'name': 'order\_id'}\]} Keys with literal or quoted values like \`\`hidden: yes\`\` become keys and values in their parent dictionary: \`\`{"hidden": "yes"}\`\` Fields that can be repeated (e.g. \`\`view\`\`, \`\`dimension\`\`, or \`\`join\`\`) are combined into a list: .. doctest:: >>> lookml = """ ... dimension: order\_id { sql: ${TABLE}.order\_id ;; } ... dimension: amount { sql: ${TABLE}.amount ;; } ... dimension: status { sql: ${TABLE}.status ;; } ... """ >>> result = lkml.load(lookml) >>> pprint(result) {'dimensions': \[{'name': 'order\_id', 'sql': '${TABLE}.order\_id'}, {'name': 'amount', 'sql': '${TABLE}.amount'}, {'name': 'status', 'sql': '${TABLE}.status'}\]} Here's an example of some LookML that has been parsed into a dictionary. Note that the repeated key \`\`join\`\` has been transformed into a plural key \`\`joins\`\`: a list of dictionaries representing each join:: { "connection": "bigquery", "explores": \[ { "label": "Explore", "joins": \[ { "relationship": "one\_to\_many", "type": "inner", "sql\_on": "${orders.order\_id} = ${order\_items.order\_id}", "name": "order\_items" }, { "relationship": "one\_to\_one", "type": "inner", "sql\_on": "${orders.order\_id} = ${orders\_\_extra.order\_id}", "name": "orders\_\_extra" } \], "name": "orders" }, \] } .. NOTE:: Simple parsing will not retain any comments in the LookML. For round-trip parsing that preserves comments and whitespace, see the section on advanced parsing below. Simple LookML generation ------------------------ It's also possible to generate LookML strings from Python objects using :py:func:\`lkml.dump\`: .. doctest:: >>> lookml = { ... "includes": \["\*.view"\], ... "explores": \[ ... { ... "label": "Orders, Items and Users", ... "view\_name": "order\_items", ... "joins": \[ ... { ... "view\_label": "Orders", ... "relationship": "many\_to\_one", ... "sql\_on": "${order\_facts.order\_id} = ${order\_items.order\_id} ", ... "name": "order\_facts", ... } ... \], ... "name": "order\_items", ... } ... \], ... } >>> print(lkml.dump(lookml)) include: "\*.view" explore: order\_items { label: "Orders, Items and Users" view\_name: order\_items join: order\_facts { view\_label: "Orders" relationship: many\_to\_one sql\_on: ${order\_facts.order\_id} = ${order\_items.order\_id} ;; } } :py:func:\`lkml.dump\` follows best practices for formatting the generated LookML. Formatting is not currently configurable. For more control over formatting and whitespace, read :doc:\`advanced\`. .. WARNING:: lkml does not validate the LookML it generates. :py:func:\`lkml.dump\`'s only standard is that the serialized output could be successfully parsed by :py:func:\`lkml.load\`. It's entirely possible to generate invalid LookML if the input is malformed. When generating LookML, lkml descends through the dictionary, writing LookML based on the \*\*keys and values\*\* it finds. \* \*\*If the value is a dictionary\*\*, lkml creates a block. Dictionaries can have an optional key called \`\`name\`\` (in this case, the name of this dimension is \`\`price\`\`), as well as a number of key/value pairs. To name a block, include the \`\`name\`\` key in the dictionary to be serialized. Here's an example of a dictionary we might provide to :py:func:\`lkml.dump\`:: { "dimension": { "type": "number", "label": "Unit Price", "sql": "${TABLE}.price", "name": "price" } } And here's the resulting block of LookML that is generated: .. code-block:: dimension: price { type: number label: "Unit Price" sql: ${TABLE}.price ;; } \* \*\*If the value is a list\*\*, lkml checks the key against a list of known repeatable keys. In the example above, we used a nested dictionary to represent a dimension block. However, LookML allows multiple blocks with the same key (e.g. \`\`dimension\`\`, \`\`view\`\`, \`\`set\`\`, etc.). Since Python dictionaries cannot have duplicate keys, we represent these repeated keys in our dictionary as a single key/value pair, where the key is a pluralized version of the original key (\`\`dimensions\`\` instead of \`\`dimension\`\`), and the value is a list of objects that represent each individual field. For example, multiple joins on an explore should be represented as follows:: "joins": \[ { "relationship": "many\_to\_one", "type": "inner", "sql\_on": "${view\_one.dimension} = ${view\_two.dimension}", "name": "view\_two" }, { "relationship": "one\_to\_many", "type": "inner", "sql\_on": "${view\_one.dimension} = ${view\_three.dimension}", "name": "view\_three" } \] If the key is \_not\_ in the list of known repeated keys, \`\`lkml\`\` creates a list. Here's an example of a list in LookML. .. code-block:: fields: \[orders.price, orders.ordered\_date, orders.order\_id\] \* \*\*If the value is a string\*\*, lkml creates a quoted or unquoted string based on the key. For example, the value for \`\`label\`\` would be quoted, but the value for \`\`hidden\`\` would not. Values with keys like \`\`sql\_table\_name\`\` or \`\`html\`\` that indicate an expression automatically have a trailing space and \`\`;;\`\` appended. Let's say we've parsed the example view from \*\*"Parsing LookML in Python"\*\* above. We've parsed it into a dictionary and now we want to modify it. We want to change the \`type\` of the dimension \`order\_id\` from \`number\` to \`string\`. Using \`lkml\`, it's easy to modify the value of \`type\` in Python and dump it to LookML. First, we'll modify the value of \`type\` in the parsed dictionary:: parsed\['views'\]\[0\]\['dimensions'\]\[0\]\['type'\] = 'string' Next, we'll dump the dictionary back to LookML in a new file:: with open('path/to/new.view.lkml', 'w+') as file: lkml.dump(parsed, file) Here's the output. .. code-block:: view: { sql\_table\_name: analytics.orders ;; dimension: order\_id { primary\_key: yes type: string sql: ${TABLE}.order\_id ;; } } --- # Unknown .. testsetup:: import lkml from lkml.parser import Parser from lkml.lexer import Lexer API reference ============= Module contents --------------- .. automodule:: lkml :members: :undoc-members: lkml.keys module ---------------- .. automodule:: lkml.keys :members: :undoc-members: lkml.lexer module ----------------- .. automodule:: lkml.lexer :members: :undoc-members: lkml.parser module ------------------ .. automodule:: lkml.parser :members: :undoc-members: lkml.simple module ------------------ .. automodule:: lkml.simple :members: :undoc-members: lkml.tokens module ------------------ .. automodule:: lkml.tokens :members: :undoc-members: .. \_tree-ref: lkml.tree module ---------------- .. automodule:: lkml.tree :members: :undoc-members: lkml.visitors module -------------------- .. automodule:: lkml.visitors :members: :undoc-members: :show-inheritance: ---