Using the Looker API to generate PDFs

Update: This article is now out of date. For the latest info on how to render and download a dashboard’s PDF using the Looker API, please see our Looker SDK Examples repo on Github.


Downloading a PDF version of your dashboard is great and is very easy to do via the Looker UI. In this article, we make use of the Looker API (and the python SDK client) to do this.

Step 1:

Authenticate into the Looker API.

import looker

base_url = 'https://learn.looker.com:19999/api/3.0'
client_id = ''
client_secret = ''

# instantiate Auth API
unauthenticated_client = looker.ApiClient(base_url)
unauthenticated_authApi = looker.ApiAuthApi(unauthenticated=_client)

# authenticate client
token = unauthenticated_authApi.login(client_id=client_id, client_secret=client_secret)
client = looker.ApiClient(base_url, 'Authorization', 'token ' + token.access_token)

Step 2:

Create a new task to render the desired dashboard to a PDF using the create_dashboard_render_task endpoint. Please take note of the format of the dashboard_filters, which is a string and expects the filters in query URL format. e.g.: "My Filter=New York&My Other Filter=Brooklyn".

# instantiate render task API
renderTask = looker.RenderTaskApi(api_client=client)

height = 842
width = 595
output_format  = 'pdf'
dashboard_id = 241
body = {
         "dashboard_style": "tiled",
         "dashboard_filters": {
            "Created Date=12 months ago for 12 months"
         }
       }

# fire a render task and get its ID
task_response = renderTask.create_dashboard_render_task(dashboard_id, output_format, body, width, height)
task_id = task_response.id

###Step 3:
Use the render_task(id) endpoint to confirm the the render task has finished. Consequently, we can get the produced document using the render_task_results endpoint as follows:

# get the produced results
results = renderTask.render_task_results(task_id, _preload_content = False)
data = results.data

# write it to PDF
with open('output.pdf', 'wb+') as f:
    f.write(data)

PS: the _preload_content = false parameter is used to tell Python not to parse the body of the response as text.

10 13 5,485
13 REPLIES 13

I am not able to locate any Looker Python SDK which has a valid reference to looker.ApiClient.
Can you pass on the location where to download this this Looker Python SDK?

(edit)
Perhaps I misunderstood. Looks like this should be generated based off this article

The Looker API is a collection of “RESTful” operations that enables you to tap into the power of the Looker data platform in your own applications. The Looker API can be invoked using plain old HTTP(S) requests. Any development tool, language, or application that can make HTTP requests and ingest JSON responses should be able to use the Looker API. Web geeks who live and breathe HTTP and/or AJAX XHR will feel at home using just the “raw” Looker API HTTP URL endpoints. GET, POST, PUT, PATCH, DEL…

I’m not all that familiar with swagger, but it’s confusing to me why this client would have to be generated. Especially considering all the dependencies required in order to get it done.

For now I’m going to attempt to interact with the API directly via http, instead.

Hi evan,

As you surmised, we don’t currently have a ready-made Python client SDK package to access the Looker API available for download. You can generate a Python client SDK from the Looker API swagger metadata, as covered in the article you linked.

This does introduce some additional steps that you, our customers, have to go through to get started using the Looker API, and for that I apologize. The swagger codegen step does allow a wide variety of programming languages to access the Looker REST API without any additional effort from us.

Some folks who are comfortable making HTTP REST requests skip the whole SDK step and just write their code to make HTTP requests to the Looker API URLs directly. The downside to that is you don’t get the data types built for you, and you have to think in terms of HTTP requests all the time. The Swagger generated client-SDKs for the Looker API offer the convenience of accessing the Looker API like a function call library so you don’t have to get down and dirty with HTTP.

Providing pre-packaged Looker client SDK downloads is high on my personal wish list, but until I can find a couple of free weekends I’m not likely to make much progress on this front. 😒

If/when we do offer pre-packaged client SDKs for the Looker API, it will definitely be announced in an article here on this forum. Watch this space! 😉

Sincerely,
-Danny

mikeghen1
Participant I

Can this be done with the Ruby SDK?

@mikeghen1 Yes, the Looker client SDK for Ruby can download PDFs. https://rubygems.org/gems/looker-sdk

-Danny

mikeghen1
Participant I

Thanks, I found the gem already.

I don’t see any documentation on how to use the SDK to do that. Is there documentation for how to use that gem to do things other than manage users?

Hi @mikeghen1,

Here is our API doc on how to create a render task for a dashboard. But, I have played with this before and here is an example where I create a .png file for a look

#!/usr/bin/env ruby
require 'looker-sdk'

sdk = LookerSDK::Client.new(
  :client_id => ENV['API_ID'],
  :client_secret => ENV['API_KEY'],
  :api_endpoint => "https://self-signed.looker.com:19999/api/3.0",
  :connection_options => {:ssl => {:verify => false}}
)

png = sdk.create_look_render_task(164, "png", {}, {:query => {:width => 1000, :height => 1000}})

id = png[:id]

until sdk.render_task(id)[:status] == 'success' do
end

results = sdk.render_task_results(id)

File.open('look.png', 'w') { |file| file.write(results) }

As you can see, we first have to create a render task. Then, we have to wait until that task is completed which is what we do here:

until sdk.render_task(id)[:status] == 'success' do
end

Then, once it returns true, we get the results, store it in a variable, and save it to a file. Let me know if this helps! And of course, you will have to make some changes to your ruby script.

Cheers,
Vincent

mikeghen1
Participant I

Thanks Vincent!

mikeghen1
Participant I

I followed the instructions to generate the PDF with Python and got this error:

Traceback (most recent call last):
  File "generate_pdf.py", line 38, in <module>
    results = renderTask.render_task_results(task_id, _preload_content=False)
  File "/Users/mikeghen/Documents/REDACTED/render_task_api.py", line 561, in render_task_results
    " to method render_task_results" % key
TypeError: Got an unexpected keyword argument '_preload_content' to method render_task_results

I generated my Python looker package using the instructions provided here: Generating Client SDKs for the Looker API. (Doing that was a pain 😩 I don’t understand why Looker only provides a Ruby SDK) I confirmed it worked with a few other API calls before I tried to do this PDF, render task thing.

When I just remove _preload_content I get:

Traceback (most recent call last):
  File "generate_pdf.py", line 46, in <module>
    data = results.data
AttributeError: 'str' object has no attribute 'data'

Looks like if I just pass results rather than results.data this works out OK without _preload_content:

results = renderTask.render_task_results(task_id)

# write it to PDF
with open('output.pdf', 'wb+') as f:
    f.write(results)

I also got an error when trying to supply a _preload_content argument to render_task_results.

In order to get PDF downloads working using the Swagger-generated Python SDK, I had to do the following:

Using Python 3.x you’ll see these line in the Swagger-generated rest.py file:

168         # In the python 3, the response.data is bytes.                                                             
169         # we need to decode it to string.                                                                          
170         if sys.version_info > (3,):                                                                                
171             r.data = r.data.decode('utf8')     

You’ll need to do something about that when downloading the PDF results, or else your API client will try to convert it to text.

My solution was to add a decode parameter to the RESTClientObject.request method, setting the default value to True, so now the decode condition looks like this:

  153         # In the python 3, the response.data is bytes.                                                           
  154         # we need to decode it to string.                                                                        
  155         if sys.version_info > (3,) and decode:                                                                   
  156             r.data = r.data.decode('utf8')        

I made similar updates to the wrapped calls to the REST client in api_client.py.

You need to make sure that you bypass deserialization of the data in the __call_api method in api_client.py:

  146         # deserialize response data                                                                              
  147         if decode:                                                                                               
  148             if response_type:                                                                                    
  149                 deserialized_data = self.deserialize(response_data, response_type)                               
  150             else:                                                                                                
  151                 deserialized_data = None                                                                         
  152         else:                                                                                                    
  153             deserialized_data = response_data.data

Finally in apis/render_task_api.py, you can update the api call like this:

  609         response = self.api_client.call_api(resource_path, 'GET',                                                
  610                                             path_params,                                                         
  611                                             query_params,                                                        
  612                                             header_params,                                                       
  613                                             body=body_params,                                                    
  614                                             post_params=form_params,                                             
  615                                             files=local_var_files,                                               
  616                                             response_type='str',                                                 
  617                                             auth_settings=auth_settings,                                         
  618                                             decode=False,                                                        
  619                                             callback=params.get('callback'))     

Here’s a gist that includes the affected files: https://gist.github.com/mpatek/316cccf6ec15721c8f60343e3b5deed7

Note: if width and height parameters there are wrong, the PDF may render super funky

Are we going to support Expand Tables as part of API , That is a great option to have if we are generating large number of PDF’s.

guneev09
Participant I

If we fetch PNG from look, how will look gets refreshed automatically without opening it ?

I want to generate PNG every 2 hours 

Hi Guys,

There is any way to pass parameters to be used by the report data source, to return dynamic results, depending on that parameters?

I have a report configured with a datasource that receives parameters, that will be used as parameters in a DB SP call. 

In the Looker API documentation, for the download pdf, I did not see nothing related with this. 

Can anyone help me? 

Many thanks