Issue 112 - Support for the new image api endpoint#113
Issue 112 - Support for the new image api endpoint#113jz-huang merged 5 commits intotableau:vnextfrom
Conversation
…the use of the new endpoint. added unit tests
|
Added a new ImageRequestOptions class because the "resolution" parameter doesn't really fit in with the current parameters in RequestOptions. Filter, sort, paging are all for endpoints that return a list of resources, doesn't really make sense to to add "resolution" to the group. And resolution is only parameter that's available for the image endpoint. With the new ImageRequestOptions, it would be easier to add more parameters for this endpoint in the future (i.e. filters within the viz). |
|
Do we really need ABC and metaclass stuff? I think just a simple inherit is probably enough, it's a single method and not a complex class. It can also all probably rest in the same file -- splitting everything out into multiple files and enforcing the subclassing via metaclasses is very "java" The rest of the code looks pretty solid, I'll do a detailed review tomorrow! |
t8y8
left a comment
There was a problem hiding this comment.
One major objection, and then mostly small nits and questions
samples/get_view_image.py
Outdated
| def main(): | ||
|
|
||
| parser = argparse.ArgumentParser(description='Query View Image From Server') | ||
| parser.add_argument('--server', '-s', required=True, help='server address') |
There was a problem hiding this comment.
Please add site as well (I know not all samples have site yet, but you can avoid the rework later ;) )
There was a problem hiding this comment.
@t8y8 i think you are saying "don't assume that everything is in the default site." yes?
There was a problem hiding this comment.
Right, I noticed when messing around with 'livetests' that we assume the default site, we should take a site-id/content_url parameter for all of the samples (especially for Online folks)
There was a problem hiding this comment.
sounds good, i'll add content_url as a parameter.
There was a problem hiding this comment.
Thanks!
(Don't worry about the other samples for now, I might do that in a quick PR against development; and we don't want merge conflicts :) )
samples/get_view_image.py
Outdated
|
|
||
| # Step 2: Get all the projects on server, then look for the default one. | ||
| all_projects, pagination_item = server.projects.get() | ||
| default_project = next((project for project in all_projects if project.is_default()), None) |
There was a problem hiding this comment.
You can just use use TSC.Pager here.
(And this will fail if they have more than 100 projects and the default isn't in the first set of 100 returned)
samples/get_view_image.py
Outdated
| server.views.populate_image(view_item, image_req_option) | ||
|
|
||
| with open(args.filepath, "wb") as image_file: | ||
| image_file.write(view_item.image) |
There was a problem hiding this comment.
How large can this file get?
There was a problem hiding this comment.
The files are usually a few hundred KB, probably gets to a couple of MB's at most.
There was a problem hiding this comment.
Cool, we shouldn't have to worry about it then.
| import abc | ||
|
|
||
|
|
||
| class RequestOptionsBase(object): |
There was a problem hiding this comment.
I think the abc and metaclass magic is unnecessary and doesn't really buy us anything but complexity to new contributors. It's kinda black arts level stuff :)
There was a problem hiding this comment.
Ok, I wanted to take advantage of the builtin abstract class support. But it's kind of an overkill for this simple use case and like you said, probably makes the code more difficult to understand as well. will switch back to simple inheritance =)
There was a problem hiding this comment.
Thank you!
Yeah, in this case it's not warranted (It's usually only done for large classes like a protocol in asyncio or twisted or something)
SIDE NOTE: I'm not a fan of the idea of having subclasses for all of our different url options/parameters, I feel like it'll just grow into a long list of "Which class do I need again", but in absence of a better idea I'll be quiet for now :)
There was a problem hiding this comment.
I'm not a big fan of this either, but I didn't like adding "resolution" to RequestOptions. We don't have a way of dealing with this in the current design. I think the intention might be that once we add "fields" to RequestOptions that would cover all of the URL parameters. This endpoint is kind of an edge case. @LGraber probably have an opinion or two on this.
samples/get_view_image.py
Outdated
| @@ -0,0 +1,67 @@ | |||
| #### | |||
| # This script demonstrates how to use the Tableau Server Client | |||
| # to query a high resolution image of a view from Tableau Server. | |||
There was a problem hiding this comment.
We're not really 'querying' an image are we? More like 'fetch' or 'download'?
There was a problem hiding this comment.
Good catch, download is more appropriate, especially since we are saving the image to a specified filepath
samples/get_view_image.py
Outdated
| parser = argparse.ArgumentParser(description='Query View Image From Server') | ||
| parser.add_argument('--server', '-s', required=True, help='server address') | ||
| parser.add_argument('--username', '-u', required=True, help='username to sign into server') | ||
| parser.add_argument('--view-name', '-v', required=True, help='name of view') |
There was a problem hiding this comment.
can we expand the help text just a bit please? something like "name of the view we want an image of"
samples/get_view_image.py
Outdated
| # Step 1: Sign in to server. | ||
| tableau_auth = TSC.TableauAuth(args.username, password) | ||
| server = TSC.Server(args.server) | ||
| server.version = 2.5 |
There was a problem hiding this comment.
There was a problem hiding this comment.
This will just fail if run against an old Server (actually, it will fail on sign in, not even on the new call).
My PR for detecting the version is outstanding, #100 -- waiting for approval to check it in.
But even then it wouldn't know which calls each version can and cannot make.
@RussTheAerialist had an idea to decorate the calls in the API with what version they use so we could throw a nice "NotSupported" exception or something, that work isn't yet started that I'm aware of (and is sorta dependent on my PR to get the version from Server)
samples/get_view_image.py
Outdated
| default_project = next((project for project in all_projects if project.is_default()), None) | ||
|
|
||
| # Step 3: If default project is found, download the image for the specified view name | ||
| if default_project is not None: |
There was a problem hiding this comment.
should we add optional ability to specify the project in the args or is that over-complicating the sample?
There was a problem hiding this comment.
Good question.
All sites will have a default project, but exposing the ability to choose another might be nice (I think all the samples would want that though)
There was a problem hiding this comment.
actually the views endpoint returns all the views on the site, so we don't need to specify the project. Copy and pasting problems... I'll remove the project related logic
samples/get_view_image.py
Outdated
| req_option = TSC.RequestOptions() | ||
| req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, | ||
| TSC.RequestOptions.Operator.Equals, args.view_name)) | ||
| all_views, pagination_item = server.views.get(req_option) |
There was a problem hiding this comment.
@t8y8 use pager here too? is our guidance to always use pager or only use it when you expect to have a lot of items (in this case we'd expect filter to reduce item count)?
There was a problem hiding this comment.
Ha, good point.
But since he's not searching for one in particular, I don't think it matters much -- he just wants to get a view
samples/get_view_image.py
Outdated
| image_req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High) | ||
| server.views.populate_image(view_item, image_req_option) | ||
|
|
||
| with open(args.filepath, "wb") as image_file: |
There was a problem hiding this comment.
what does "wb" do here? confusing to me b/c I assume it means workbook but this is an image.
There was a problem hiding this comment.
"write binary" it's the standard in python code for opening a file in binary mode for writing
|
|
||
| class ImageRequestOptions(RequestOptionsBase): | ||
| class Resolution: | ||
| High = 'high' |
There was a problem hiding this comment.
is there only one option?
There was a problem hiding this comment.
yes, it defaults to a standard resolution (~800x800), high doubles that.
There was a problem hiding this comment.
ok. can you please add a comment here that says if you don't specify 'high' that the underlying REST API will default to standard?
| with open(POPULATE_PREVIEW_IMAGE, 'rb') as f: | ||
| response = f.read() | ||
| with requests_mock.mock() as m: | ||
| m.get(self.baseurl + '/views/d79634e1-6063-4ec9-95ff-50acbf609ff5/image', content=response) |
There was a problem hiding this comment.
what server will this execute against? is this a valid view ID?
There was a problem hiding this comment.
It's a mock, he's registering that URL so that when the API calls it it'll hit this fake endpoint and return the image
…d sample to download_view_image.py. Comments clean up
t8y8
left a comment
There was a problem hiding this comment.
🚀 I'll leave it up to you if you want to clean up my final nit
| def __init__(self, imageresolution=None): | ||
| self.imageresolution = imageresolution | ||
|
|
||
| def image_resolution(self, imageresolution): |
There was a problem hiding this comment.
I called :)
This is unnecessary (and technically slower)
| view_item = all_views[0] | ||
|
|
||
| # Step 3: Query the image endpoint and save the image to the specified location | ||
| image_req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High) |
There was a problem hiding this comment.
I agree with Tyler. This is starting to feel heavy to code. The base RequestOptions felt more necessary because fields was more than a simple hash. As I watch this code being written in our samples, though, it is feeling heavier and heavier. It feels like the simple hash syntax might be more lightweight and we can still validate it. I am okay with you checking this in since it is in vnext but I think we should discuss this as I want users to be able to write minimal code that is easy to read and it is starting to feel like perhaps our use of enums and strongly typed objects is making the python harder and less ... pythonic. I am open to discussion.
There was a problem hiding this comment.
I meant to say filter, not fields. Fields, also, was going to be interesting.
|
I'm merging this in for now so Ben can WAM, let's keep the discussion going in #114. |
added support for the new image api endpoint. Added unit tests and a sample. Added the new sample to docs.
Unit tests and pep8 run clean. Manually tested sample.