<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Looker Python SDK Tutorial
Looking for a simple Quick Start? Check out the instructions in the README.
In this 15-20 minute tutorial, we'll build a real-world tool using the Looker Python SDK— albeit a basic one. We'll start from absolute scratch, and while you can follow along in the IDE of your choice, I recommend you follow along in this notebook. 
You can follow along right in this version, but any changes you make won't be saved if you refresh. To save your own version, "Copy to Drive" using the button just above this text block.
In this tutorial you will
Step1:  Configuring & Initializing the SDK
In this notebook, we'll use environment variables to configure the SDK. On your local machine or environment, you can choose to use either environment variables or a looker.ini file you create. Either is perfectly fine to do.
I actually prefer to use environment variables, as I often find myself deploying cloud functions or other lightweight apps where it's inconvenient to add a .ini file. If you do want to use a looker.ini, example looker.ini contents are below. If a file named looker.ini is in your working directory and contains the following code, the Looker SDK will automatically use it
Step2:  Now that we've set all the necessary environment variables, we should be able to initialize the Looker SDK. When initializing the SDK, you choose which version to use— 3.1 or 4.0. The 4.0 SDK has some new endpoints and bug fixes, but is still experimental.
Having set environment variables, we don't need to pass any args to the init31() function— It automatically checks for environment variables. If you're using a specific .ini file, you might want to say sdk = looker_sdk.init31(~/path/to/looker.ini).
Step3:  Making API Calls
We've now initialized an instance of the SDK using the Looker 3.1 API. We can reference it via sdk and use any of the SDK functions available.
Hello World (aka Reading Data)
The standard Looker "Hello World" equivalent is to get your own user object and print it. We'll do this first, before diving into some real scripting.
Step4:  Huzzah! You've just made your first Looker API calls, and should see your first name printed out twice after running the cell above. If you're getting errors, triple and quadruple check the environment variable settings and your API keys. 
If you are getting an error here, stop and resolve it before continuing. If this doesn't work, nothing will.
<font size=6>👀</font> Troubleshooting tip
Step5:  Running the cell above should create two identical users and return two lines, each with a different User ID but the same name. You're welcome to use either method you prefer going forward, but we'll show examples using models as it's the most foolproof and Lookery way of doing things.
<font size=6>👀</font> We assigned the sdk.create_user() call directly to a user variable, which we could then immediately reference. You don't need to assign calls to variables, but it's generally handy so that you can immediately reference the user you've created. If you wanted to create the user and just move on, you could have written
Step6:  2. Run query #1 and save results
Now we need to actually run this Look, to get our filter input values. This is also quite simple using the SDK!
Step7:  This might be a good time to mention the methods.py file. You may be wondering "how did you know what arguments to pass to sdk.run_look()? 
Most IDEs (Colaboratory included) will let you inspect any imported functions. Try mousing over run_look in the code block above, and you'll see it provides you with the definition of the function and all of its arguments. This is probably the best way to understand what you need to pass to a method.
If you don't have that capability, you can review the SDK's methods.py file, which lists every SDK method. run_look is here, on line 4427 (at time of writing).
This is super helpful to review. It tells us what arguments functions take, what types they accept, and has handy descriptions. Use it when you're confused!
<font size=6>👀</font> Make sure you're looking at the methods.py file for the right API version! That link is to the 3.1 methods.
3. If needed, transform results to use as a filter
We don't need to do too much to the data when running a look with result_format
Step8:  Before moving on, let's package our work up so far into a function to make things easier later on.
In this function, we'll parse the data just a tiny bit to turn it into a filter expression. Looker filters take "Looker Filter Expressions", documented here, which are strings. In this case, we'll just generate a comma separated list of values.
Step9:  Now we can just call this function whenever we want to grab data from a look in filter expression format.
Step10:  4. Create or get query #2
Awesome! So now we can easily get the data for our first query. The second half of this isn't as simple, since we actually need to take a query apart and edit it. Let's again assume that we're using a Look.
My first Look was a list of all the brands in the inventory table. My second look will be a list of Users and their age, filtered on users that have made more than 1 order. I want to add a filter to this Look of the selected brands generated by Look #1.
Step11:  For my example query, that returns {'order_items.order_count'
Step12:  That's pretty simple. You just reference the existing query ID we got from the Look, and pass it to the run_query function. But that query doesn't include the new filters we want to add yet, and to do that we actually need to create a new query. 
<font size=6>👀</font> For clarity's sake
Step13:  6. Run the new query
Now it's time to run this new query! Remember there were two options... We'll do them both below in separate blocks.
Step14:  These methods have the same outcome. Both blocks should have returned identical output. You may want to create_query rather than running inline if you want to create and run asynchronous queries and poll for their results using the create_query_task and query_task_results endpoints.
Congratulations!
You've successfully altered the filters of one Look based on the results of another Look, all using the Looker API, 100% dynamically. That means you could do this programmatically for as many Looks as you like, as many times as you like, and it wouldn't have to be done manually. Any time the data returned by Look #1 changes, your output from running this script will change accordingly. You can even change the Look ID for Look #1 to a brand new Look and it will adapt accordingly. 
Pretty cool!
7 // Extra Credit. Package it up
One awesome bonus step is to create a little application to make this more easily repeatable. Check this out
<ASSISTANT_TASK:>
Python Code:
pip install looker_sdk
import looker_sdk #Note that the pip install required a hyphen but the import is an underscore.
import os #We import os here in order to manage environment variables for the tutorial. You don't need to do this on a local system or anywhere you can more conveniently set environment variables.
import json #This is a handy library for doing JSON work.
Explanation: Looker Python SDK Tutorial
Looking for a simple Quick Start? Check out the instructions in the README.
In this 15-20 minute tutorial, we'll build a real-world tool using the Looker Python SDK— albeit a basic one. We'll start from absolute scratch, and while you can follow along in the IDE of your choice, I recommend you follow along in this notebook. 
You can follow along right in this version, but any changes you make won't be saved if you refresh. To save your own version, "Copy to Drive" using the button just above this text block.
In this tutorial you will:
Install and configure the Looker Python SDK.
Learn to read & write data using the SDK.
Understand the Methods and Models made available by the SDK and how they work.
Chain simple API calls together to create more complex logic.
Build a simple micro-application that lets you easily use the results of one Look as dynamic filter values for another Look.
Prerequisites:
*    Access to a Looker instance. We don't yet offer an open sandbox (soon!).
*    API Keys for that instance (Accessible from Admin > Users. If you are not an admin, you'll need to ask your admin).
*    Some Python experience. There aren't any complex concepts here but it's assumed you understand basic Python. 
If you've never used Jupyter Notebooks or Colaboratory before, it's pretty simple. This notebook just lets you intersperse text and Python code blocks and show the output nicely. Clone this notebook, and you'll have your own editable copy of it all to yourself. You can double-click into each of these "blocks" to edit them. 
Some blocks are markdown text (like this one) and others are code. The Python code blocks can be Run by hitting CMD+Enter or clicking the Play button on the left side of each block. The execution flow is linear. If you define a variable in block #1 and reference it in block #2, you must run block #1 before block #2 or the variable won't be available. If you're getting errors that seem odd, make sure you've run any necessary earlier blocks.
<font size=6>👀</font> Hot tip! Throughout the tutorial, you may see the <font size=6>👀</font> emoji denoting an aside or a handy tip. Keep your eyes peeled!
Installing requirements
We'll install the latest looker_sdk from pypi. 
If you're doing this on your local machine (ie: not in a jupyter notebook) we recommend you use pyenv to manage your environment. See the instructions here.
End of explanation
os.environ["LOOKERSDK_BASE_URL"] = "https://your.looker.com:19999" #If your looker URL has .cloud in it (hosted on GCP), do not include :19999 (ie: https://your.cloud.looker.com).
os.environ["LOOKERSDK_API_VERSION"] = "3.1" #3.1 is the default version. You can change this to 4.0 if you want.
os.environ["LOOKERSDK_VERIFY_SSL"] = "true" #Defaults to true if not set. SSL verification should generally be on unless you have a real good reason not to use it. Valid options: true, y, t, yes, 1.
os.environ["LOOKERSDK_TIMEOUT"] = "120" #Seconds till request timeout. Standard default is 120.
#Get the following values from your Users page in the Admin panel of your Looker instance > Users > Your user > Edit API keys. If you know your user id, you can visit https://your.looker.com/admin/users/<your_user_id>/edit.
os.environ["LOOKERSDK_CLIENT_ID"] =  "clientid" #No defaults.
os.environ["LOOKERSDK_CLIENT_SECRET"] = "clientsecret" #No defaults. This should be protected at all costs. Please do not leave it sitting here, even if you don't share this document.
print("All environment variables set.")
Explanation: Configuring & Initializing the SDK
In this notebook, we'll use environment variables to configure the SDK. On your local machine or environment, you can choose to use either environment variables or a looker.ini file you create. Either is perfectly fine to do.
I actually prefer to use environment variables, as I often find myself deploying cloud functions or other lightweight apps where it's inconvenient to add a .ini file. If you do want to use a looker.ini, example looker.ini contents are below. If a file named looker.ini is in your working directory and contains the following code, the Looker SDK will automatically use it:
```
[Looker]
Base URL for API. Do not include /api/* in the url
base_url=https://self-signed.looker.com:19999
API 3 client id
client_id=YourClientID
API 3 client secret
client_secret=YourClientSecret
Set to false if testing locally against self-signed certs. Otherwise leave True
verify_ssl=True
```
Make sure to never commit this .ini file to source control (ie: github) as it contains your API secret. You should immediately add looker.ini to your .gitignore file if working in a git repository.
<font size=6>👀</font> If you have a looker.ini file AND set environment variables, the environment variables will override the contents of the .ini file.
In this notebook, for convenience we'll use the os package to set environment variables on the fly. Edit the placeholders below with the information for your Looker instance and run the block.
Be careful not to share this notebook if you leave your API credentials in it. Also, just don't leave your API credentials in it!! Please.
End of explanation
sdk = looker_sdk.init31()
print('Looker SDK 3.1 initialized successfully.')
#Uncomment out the lines below if you want to instead initialize the 4.0 SDK. It's that easy— Just replace init31 with init40.
#sdk = looker_sdk.init40()
#print('Looker SDK 4.0 initialized successfully.')
Explanation: Now that we've set all the necessary environment variables, we should be able to initialize the Looker SDK. When initializing the SDK, you choose which version to use— 3.1 or 4.0. The 4.0 SDK has some new endpoints and bug fixes, but is still experimental.
Having set environment variables, we don't need to pass any args to the init31() function— It automatically checks for environment variables. If you're using a specific .ini file, you might want to say sdk = looker_sdk.init31(~/path/to/looker.ini).
End of explanation
my_user = sdk.me()
#Output is an instance of the User model, but can also be read like a python dict. This applies to all Looker API calls that return Models.
#Example: The following commands return identical output. Feel free to use whichever style is more comfortable for you.
print(my_user.first_name) #Model dot notation
print(my_user["first_name"]) #Dictionary
Explanation: Making API Calls
We've now initialized an instance of the SDK using the Looker 3.1 API. We can reference it via sdk and use any of the SDK functions available.
Hello World (aka Reading Data)
The standard Looker "Hello World" equivalent is to get your own user object and print it. We'll do this first, before diving into some real scripting.
End of explanation
#Example of creating a user using a Model instance (WriteUser)
user = sdk.create_user(
  body = looker_sdk.models.WriteUser(first_name="Jane", last_name="Doe")
)
print(user.id, user.first_name)
#Example of creating a user using a simple python dictionary
user = sdk.create_user(
    body={"first_name": "Jane", "last_name": "Doe"}
)
print(user.id, user.first_name)
Explanation: Huzzah! You've just made your first Looker API calls, and should see your first name printed out twice after running the cell above. If you're getting errors, triple and quadruple check the environment variable settings and your API keys. 
If you are getting an error here, stop and resolve it before continuing. If this doesn't work, nothing will.
<font size=6>👀</font> Troubleshooting tip: remember that if you're using an AWS-hosted Looker, you'll need to include :19999 at the end of your BASE_URL (https://your.looker.com:19999), but if you're on a GCP instance at a your.cloud.looker.com style URL, you should remove it.
Writing Data
Requesting information with API calls is pretty easy— You make a call like sdk.me(), it returns the right information using the right model, you print it out, pretty simple.
Writing information with API calls is slightly more complicated, as you need to provide the right information in the right format so Looker can understand it. This can be confusing, but the SDK has helpful model instances to make things less likely to error. Models are classes pre-built for all the kinds of objects you need to interact with in the Looker API— Users, Queries, Dashboards, etc. 
This can take some getting used to though, and you're welcome to use normal python dictionaries instead if you'd like. Check out the examples below. We use the WriteUser model to make the call simpler, but replicate the same call using a simple dict as well. 
You can see the WriteUser model here, for reference.
End of explanation
#Enter your Look ID. If your URL is https://your.cloud.looker.com/looks/25, your Look ID is 25.
look_id = 25
look = sdk.look(look_id=look_id) 
# This gives us a Look object. We'll print the ID of it to verify everything's working.
print(look.id)
#You actually don't need to do anything further for this case, using a Look. 
#If you wanted to use an Explore instead, you'd have to get the underlying query first, which might look like this:
#explore_id = "Q4pXny1FEtuxMuj9Atf0Gg" 
#If your URL looks like https://your.cloud.looker.com/explore/ecommerce_data/order_items?qid=Q4pXny1FEtuxMuj9Atf0Gg&origin_space=15&toggle=vis, your explore_id/QID is Q4pXny1FEtuxMuj9Atf0Gg.
#explore_query = sdk.query_for_slug(slug=explore_id)
#This would return a Query object that we could then run to get results in step 2 using the run_query endpoints.
Explanation: Running the cell above should create two identical users and return two lines, each with a different User ID but the same name. You're welcome to use either method you prefer going forward, but we'll show examples using models as it's the most foolproof and Lookery way of doing things.
<font size=6>👀</font> We assigned the sdk.create_user() call directly to a user variable, which we could then immediately reference. You don't need to assign calls to variables, but it's generally handy so that you can immediately reference the user you've created. If you wanted to create the user and just move on, you could have written:
sdk.create_user(
  body = looker_sdk.models.WriteUser(first_name="Jane", last_name="Doe")
)
Building application logic
It usually helps to mock up the flow of an App/script before diving into code. In this case, our mission is to take the output of one Look and pipe it into the filters of a second Look. That means it'll look something like this:
Get Query #1 from Look #1
Run Query #1 and save results (Our eventual filter input)
If needed, transform results to use as a filter
Get Query #2 from Look #2
Update Query #2 to include our new filter values
Run Query #2
Let's take it item by item.
1. Get query #1 from Look #1 (input query)
Our input query needs to be simple. It should return just one column, which will contain the list of values to be passed into Look #2's filters. You can add whatever filters you'd like. Mine is a list of clothing brands. Here's what it looks like:
End of explanation
#We'll use a try/except block here, to make debugging easier. 
#In general, this kind of thing isn't really necessary in notebooks as each cell is already isolated from the rest,
#but it's a good practice in larger scripts and certainly in applications where fatal errors can break the entire app.
#You should get into the habit of using them.
try:
  response = sdk.run_look(
    look_id=look.id,
    result_format= "json" # Options here are csv, json, json_detail, txt, html, md, xlsx, sql (returns the raw query), png, jpg. JSON is the easiest to work with in python, so we return it.
  )
  data = json.loads(response) #The response is just a string, so we have to use the json library to load it as a json dict.
  print(data) #If our query was successful we should see an array of rows.
except:
  raise Exception(f'Error running look {look.id}')
Explanation: 2. Run query #1 and save results
Now we need to actually run this Look, to get our filter input values. This is also quite simple using the SDK!
End of explanation
#Before we move on, here's a simple example of that. Let's print the first 10 rows.
#This script is set up to always only look at the first column, assuming our Look returns 1 column.
first_field = list(
    data[0].keys()
    )[0] #This looks at the first row of the data and returns the first field name. keys() returns a set, so we wrap it in list() to return an array.
    
for i in range(0,10):
  print(i,data[i][first_field])
#If we _know_ the name of the first field, why did we go to all this list(data[0].keys()[0]) trouble? Well, we know the name of the first field for ONE look. 
#This little trickery above makes it so that our script will always work for any Look, no matter what the name is, without having to edit the code.
Explanation: This might be a good time to mention the methods.py file. You may be wondering "how did you know what arguments to pass to sdk.run_look()? 
Most IDEs (Colaboratory included) will let you inspect any imported functions. Try mousing over run_look in the code block above, and you'll see it provides you with the definition of the function and all of its arguments. This is probably the best way to understand what you need to pass to a method.
If you don't have that capability, you can review the SDK's methods.py file, which lists every SDK method. run_look is here, on line 4427 (at time of writing).
This is super helpful to review. It tells us what arguments functions take, what types they accept, and has handy descriptions. Use it when you're confused!
<font size=6>👀</font> Make sure you're looking at the methods.py file for the right API version! That link is to the 3.1 methods.
3. If needed, transform results to use as a filter
We don't need to do too much to the data when running a look with result_format: "json". Our data object now contains an array of "rows", each represented as a dict, and we can iterate through that array to get each row's values.
End of explanation
def get_data_for_look(look_id):
  try:
    look = sdk.look(look_id=look_id)
  except:
    raise Exception(f'Look {look_id} not found.')
  print(f'Successfully got Look {look.id}')
  try:
    response = sdk.run_look(
        look_id=look.id,
        result_format = "json"
    )
    data = json.loads(response)
    first_field = list(
      data[0].keys()
    )[0]
    list_of_values = []
    for i in data:
      list_of_values.append(i[first_field])
    #Ultimately, we're going to want to pass Looker a filter expression that's a comma-separated-list of values.
    #Here, we use .join on the array of values to generate that filter expression. 
    string_list = ",".join(list_of_values)
    return({"filter_values": string_list, "first_field": first_field}) 
  except:
    raise Exception('Error running Look.')
Explanation: Before moving on, let's package our work up so far into a function to make things easier later on.
In this function, we'll parse the data just a tiny bit to turn it into a filter expression. Looker filters take "Looker Filter Expressions", documented here, which are strings. In this case, we'll just generate a comma separated list of values.
End of explanation
test = get_data_for_look(25)
#This should return successful.
Explanation: Now we can just call this function whenever we want to grab data from a look in filter expression format.
End of explanation
#Start off the same as before, by getting the Look. 
second_look_id = 26
second_look = sdk.look(look_id=second_look_id)
print(second_look.id) #just verifying we obtained the Look properly.
#Now we can extract the underlying query from the second Look, in order to modify it. We'll print it to see what it looks like.
second_query = second_look.query
print(second_query)
#We want to edit the filters, so let's start by inspecting the query and see how filters are set.
#We can see all the available keys in the query object by running the command below.
#👀 What are keys? Objects are key: value pairs. ex: {"name": "Izzy"}. The key is name, the value for that key is Izzy.
print(second_query.keys())
#Looks like there's a filters key, so we can run the following to see the filters:
print("Filters: ", second_query.filters)
Explanation: 4. Create or get query #2
Awesome! So now we can easily get the data for our first query. The second half of this isn't as simple, since we actually need to take a query apart and edit it. Let's again assume that we're using a Look.
My first Look was a list of all the brands in the inventory table. My second look will be a list of Users and their age, filtered on users that have made more than 1 order. I want to add a filter to this Look of the selected brands generated by Look #1.
End of explanation
response = sdk.run_query(query_id = second_query.id, result_format="json")
print(response)
Explanation: For my example query, that returns {'order_items.order_count': '&gt;1'}. Which matches up perfectly to the UI filter I'd already set on the Look. Awesome. 
5. Update query #2 to include our new filter values
We've successfully obtained and inspected the query that we want to alter. All that's left is to change the filters on it, and then run the final query.
If we wanted to run this query as is, without modifying it, we'd probably just call run_query, and pass in the query_id.
End of explanation
#Let's create a new variable, altered_query, and assign it to be identical to second_query to begin.
altered_query = second_query 
#Then, let's set a new key on the filters dict, which adds a new filter to the query. We'll name it the name of the field we want to filter on.
#This must exactly match the **fully scoped** field name in Looker (ie: view_name.field_name).
#We will then set that key's value equal to our data. In this case, we'll set it equal to that comma-separated string we generated earlier.
filter_data = get_data_for_look(25) # This is that function we built earlier.
field_name = filter_data['first_field']
filter_values = filter_data['filter_values']
altered_query.filters[field_name] = filter_values
#This should now print an object with the filters you've just added. Nice!
print(altered_query.filters)
#Before we can run this query, we need to do a little bit of pruning to it, since we copied it directly from an existing query.
#If you do not remove the ID and client_id, you'll get an error that this query already exists.
altered_query.client_id = None
altered_query.id = None
Explanation: That's pretty simple. You just reference the existing query ID we got from the Look, and pass it to the run_query function. But that query doesn't include the new filters we want to add yet, and to do that we actually need to create a new query. 
<font size=6>👀</font> For clarity's sake: You cannot alter existing Looker queries— You have to get a query, edit the query object returned, and then pass that query object back to create a brand new query with a new ID.
You have two options when creating new queries:
a. Create a brand new query object on the Looker instance using create_query, and then run that query using run_query, or create_query_task for async execution.
or
b. Use run_inline_query to run a query immediately from raw input.
Either way, the editing of the query object is the same. We'll show both methods.
End of explanation
#Option a. Run inline query. This is the simplest option for just getting the data now.
#This should return your newly filtered data from Look #2
response = sdk.run_inline_query(body=altered_query, result_format="json")
print(response)
#Option b. Creating a brand new query object, then running that query from the ID.
#You might want to do this if you're planning to run the query asynchronously using create_query_task.
new_query = sdk.create_query(body=altered_query)
response = sdk.run_query(query_id=new_query.id, result_format="json")
print(response)
Explanation: 6. Run the new query
Now it's time to run this new query! Remember there were two options... We'll do them both below in separate blocks.
End of explanation
#@title Look Filterer
#@markdown Enter a look ID (`first_look_id`) that returns one column of values you want to apply as filters to another Look (`second_look_id`).
#@markdown The first Look must return one column only. Once you've entered the look IDs, run this block.
#@markdown _👀  If you get an error, make sure you've run the earlier code blocks that initialize the Looker API and create the get_data_for_look() function._
first_look_id = 25 #@param {type:"integer"}
second_look_id =  26#@param {type:"integer"}
final_result_format = "json" #@param ["json", "json_detail", "csv", "png", "jpg", "txt", "html", "md"]
first_look = get_data_for_look(first_look_id)
filter_field = first_look['first_field']
filter_values = first_look['filter_values']
second_look = sdk.look(look_id=second_look_id)
second_query = second_look.query
altered_query = second_query
 
altered_query.filters[filter_field] = filter_values
altered_query.client_id = None
altered_query.id = None
response = sdk.run_inline_query(body=altered_query, result_format=final_result_format)
print(f"Results of look {second_look_id}  filtered with values from {first_look_id}:", response)
Explanation: These methods have the same outcome. Both blocks should have returned identical output. You may want to create_query rather than running inline if you want to create and run asynchronous queries and poll for their results using the create_query_task and query_task_results endpoints.
Congratulations!
You've successfully altered the filters of one Look based on the results of another Look, all using the Looker API, 100% dynamically. That means you could do this programmatically for as many Looks as you like, as many times as you like, and it wouldn't have to be done manually. Any time the data returned by Look #1 changes, your output from running this script will change accordingly. You can even change the Look ID for Look #1 to a brand new Look and it will adapt accordingly. 
Pretty cool!
7 // Extra Credit. Package it up
One awesome bonus step is to create a little application to make this more easily repeatable. Check this out:
End of explanation
<END_TASK>