From d107c4827cd9ea59cdb9f618952eea35aef98197 Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Wed, 17 Feb 2021 15:38:14 -0800 Subject: [PATCH 01/12] Add Mypy to CI runs (#802) Add mypy runs to CI, but skip misc errors, so we only see the important stuff. For now these are non blocking. --- .github/workflows/run-tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e12d61383..a0917c7b6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -23,6 +23,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -e .[test] + pip install mypy - name: Lint with pycodestyle run: | @@ -31,3 +32,7 @@ jobs: - name: Test with pytest run: | pytest test + - name: Run Mypy but allow failures + run: | + mypy --show-error-codes --disable-error-code misc tableauserverclient + continue-on-error: true From 42f710ebe4cb2b90d80724a66de35a980b10b1a2 Mon Sep 17 00:00:00 2001 From: Brian Cantoni Date: Fri, 5 Mar 2021 13:37:46 -0800 Subject: [PATCH 02/12] Change build badge from Travis to GitHub Actions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2c30704a..366a4565b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Tableau Server Client (Python) -[![Tableau Supported](https://img.shields.io/badge/Support%20Level-Tableau%20Supported-53bd92.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) [![Build Status](https://travis-ci.org/tableau/server-client-python.svg?branch=master)](https://travis-ci.org/tableau/server-client-python) +![Tableau Supported](https://img.shields.io/badge/Support%20Level-Tableau%20Supported-53bd92.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) ![Build Status](https://github.com/tableau/server-client-python/actions/workflows/run-tests.yml/badge.svg) Use the Tableau Server Client (TSC) library to increase your productivity as you interact with the Tableau Server REST API. With the TSC library you can do almost everything that you can do with the REST API, including: From acbaa8ccb3b6ba883723dd52842f2a4c3fbc78b5 Mon Sep 17 00:00:00 2001 From: Brian Cantoni Date: Fri, 5 Mar 2021 13:41:18 -0800 Subject: [PATCH 03/12] Fix badge links --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 366a4565b..1aed88d61 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Tableau Server Client (Python) -![Tableau Supported](https://img.shields.io/badge/Support%20Level-Tableau%20Supported-53bd92.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) ![Build Status](https://github.com/tableau/server-client-python/actions/workflows/run-tests.yml/badge.svg) +[![Tableau Supported](https://img.shields.io/badge/Support%20Level-Tableau%20Supported-53bd92.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) [![Build Status](https://github.com/tableau/server-client-python/actions/workflows/run-tests.yml/badge.svg)](https://github.com/tableau/server-client-python/actions) Use the Tableau Server Client (TSC) library to increase your productivity as you interact with the Tableau Server REST API. With the TSC library you can do almost everything that you can do with the REST API, including: From 39560e55cab644fe8867db9cc0fc2bc03f884322 Mon Sep 17 00:00:00 2001 From: t8y8 Date: Wed, 21 Apr 2021 13:29:30 -0700 Subject: [PATCH 04/12] reformat the world, once and for all --- tableauserverclient/__init__.py | 59 +- tableauserverclient/_version.py | 132 +-- tableauserverclient/filesys_helpers.py | 11 +- tableauserverclient/models/column_item.py | 10 +- .../models/connection_credentials.py | 10 +- tableauserverclient/models/connection_item.py | 43 +- .../models/data_acceleration_report_item.py | 55 +- tableauserverclient/models/data_alert_item.py | 89 +- tableauserverclient/models/database_item.py | 90 +- tableauserverclient/models/datasource_item.py | 202 +++-- tableauserverclient/models/favorites_item.py | 40 +- tableauserverclient/models/fileupload_item.py | 6 +- tableauserverclient/models/flow_item.py | 60 +- tableauserverclient/models/group_item.py | 24 +- tableauserverclient/models/interval_item.py | 4 +- tableauserverclient/models/job_item.py | 68 +- tableauserverclient/models/pagination_item.py | 8 +- .../models/permissions_item.py | 79 +- .../models/personal_access_token_auth.py | 11 +- tableauserverclient/models/project_item.py | 24 +- .../models/property_decorators.py | 29 +- tableauserverclient/models/reference_item.py | 1 - tableauserverclient/models/schedule_item.py | 159 ++-- .../models/server_info_item.py | 6 +- tableauserverclient/models/site_item.py | 603 ++++++++++---- .../models/subscription_item.py | 44 +- tableauserverclient/models/table_item.py | 40 +- tableauserverclient/models/tableau_auth.py | 20 +- tableauserverclient/models/tag_item.py | 4 +- tableauserverclient/models/target.py | 2 +- tableauserverclient/models/task_item.py | 48 +- tableauserverclient/models/user_item.py | 89 +- tableauserverclient/models/view_item.py | 36 +- tableauserverclient/models/webhook_item.py | 23 +- tableauserverclient/models/workbook_item.py | 198 +++-- tableauserverclient/namespace.py | 12 +- tableauserverclient/server/__init__.py | 51 +- .../server/endpoint/auth_endpoint.py | 37 +- .../data_acceleration_report_endpoint.py | 8 +- .../server/endpoint/data_alert_endpoint.py | 22 +- .../server/endpoint/databases_endpoint.py | 38 +- .../server/endpoint/datasources_endpoint.py | 118 +-- .../endpoint/default_permissions_endpoint.py | 38 +- .../server/endpoint/endpoint.py | 71 +- .../server/endpoint/exceptions.py | 9 +- .../server/endpoint/favorites_endpoint.py | 38 +- .../server/endpoint/fileuploads_endpoint.py | 12 +- .../server/endpoint/flows_endpoint.py | 75 +- .../server/endpoint/groups_endpoint.py | 29 +- .../server/endpoint/jobs_endpoint.py | 17 +- .../server/endpoint/metadata_endpoint.py | 64 +- .../server/endpoint/permissions_endpoint.py | 34 +- .../server/endpoint/projects_endpoint.py | 45 +- .../server/endpoint/resource_tagger.py | 4 +- .../server/endpoint/schedules_endpoint.py | 10 +- .../server/endpoint/server_info_endpoint.py | 2 +- .../server/endpoint/sites_endpoint.py | 22 +- .../server/endpoint/subscriptions_endpoint.py | 21 +- .../server/endpoint/tables_endpoint.py | 34 +- .../server/endpoint/tasks_endpoint.py | 39 +- .../server/endpoint/users_endpoint.py | 16 +- .../server/endpoint/views_endpoint.py | 18 +- .../server/endpoint/webhooks_endpoint.py | 12 +- .../server/endpoint/workbooks_endpoint.py | 144 ++-- tableauserverclient/server/filter.py | 4 +- tableauserverclient/server/pager.py | 4 +- tableauserverclient/server/query.py | 3 +- tableauserverclient/server/request_factory.py | 786 +++++++++--------- tableauserverclient/server/request_options.py | 100 +-- tableauserverclient/server/server.py | 54 +- tableauserverclient/server/sort.py | 2 +- 71 files changed, 2530 insertions(+), 1790 deletions(-) diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index b438d8a2e..2eadcdfa1 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -1,13 +1,54 @@ from .namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE -from .models import ConnectionCredentials, ConnectionItem, DataAlertItem, DatasourceItem,\ - GroupItem, JobItem, BackgroundJobItem, PaginationItem, ProjectItem, ScheduleItem,\ - SiteItem, TableauAuth, PersonalAccessTokenAuth, UserItem, ViewItem, WorkbookItem, UnpopulatedPropertyError,\ - HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval, IntervalItem, TaskItem,\ - SubscriptionItem, Target, PermissionsRule, Permission, DatabaseItem, TableItem, ColumnItem, FlowItem, \ - WebhookItem, PersonalAccessTokenAuth -from .server import RequestOptions, CSVRequestOptions, ImageRequestOptions, PDFRequestOptions, Filter, Sort, \ - Server, ServerResponseError, MissingRequiredFieldError, NotSignedInError, Pager +from .models import ( + ConnectionCredentials, + ConnectionItem, + DataAlertItem, + DatasourceItem, + GroupItem, + JobItem, + BackgroundJobItem, + PaginationItem, + ProjectItem, + ScheduleItem, + SiteItem, + TableauAuth, + PersonalAccessTokenAuth, + UserItem, + ViewItem, + WorkbookItem, + UnpopulatedPropertyError, + HourlyInterval, + DailyInterval, + WeeklyInterval, + MonthlyInterval, + IntervalItem, + TaskItem, + SubscriptionItem, + Target, + PermissionsRule, + Permission, + DatabaseItem, + TableItem, + ColumnItem, + FlowItem, + WebhookItem, + PersonalAccessTokenAuth, +) +from .server import ( + RequestOptions, + CSVRequestOptions, + ImageRequestOptions, + PDFRequestOptions, + Filter, + Sort, + Server, + ServerResponseError, + MissingRequiredFieldError, + NotSignedInError, + Pager, +) from ._version import get_versions -__version__ = get_versions()['version'] + +__version__ = get_versions()["version"] __VERSION__ = __version__ del get_versions diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 9f576606a..c8afb10d4 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -58,17 +57,18 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate -def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, - env=None): +def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None @@ -76,10 +76,9 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + p = subprocess.Popen( + [c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None) + ) break except EnvironmentError: e = sys.exc_info()[1] @@ -116,16 +115,19 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} + return { + "version": dirname[len(parentdir_prefix) :], + "full-revisionid": None, + "dirty": False, + "error": None, + "date": None, + } else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @@ -181,7 +183,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) + tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -190,7 +192,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". - tags = set([r for r in refs if re.search(r'\d', r)]) + tags = set([r for r in refs if re.search(r"\d", r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: @@ -198,19 +200,26 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] + r = ref[len(tag_prefix) :] if verbose: print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} + return { + "version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": None, + "date": date, + } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} + return { + "version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": "no suitable tags", + "date": None, + } @register_vcs_handler("git", "pieces_from_vcs") @@ -225,8 +234,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] - out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=True) + out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) @@ -234,10 +242,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out, rc = run_command( + GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root + ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -260,17 +267,16 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] + git_describe = git_describe[: git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX - mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) + mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag @@ -279,10 +285,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] + pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) @@ -293,13 +298,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], - cwd=root)[0].strip() + date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces @@ -330,8 +333,7 @@ def render_pep440(pieces): rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -445,11 +447,13 @@ def render_git_describe_long(pieces): def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} + return { + "version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None, + } if not style or style == "default": style = "pep440" # the default @@ -469,9 +473,13 @@ def render(pieces, style): else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} + return { + "version": rendered, + "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], + "error": None, + "date": pieces.get("date"), + } def get_versions(): @@ -485,8 +493,7 @@ def get_versions(): verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass @@ -495,13 +502,16 @@ def get_versions(): # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. - for i in cfg.versionfile_source.split('/'): + for i in cfg.versionfile_source.split("/"): root = os.path.dirname(root) except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None, + } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -515,6 +525,10 @@ def get_versions(): except NotThisMethod: pass - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", + "date": None, + } diff --git a/tableauserverclient/filesys_helpers.py b/tableauserverclient/filesys_helpers.py index 11051fdf4..1663ee9e8 100644 --- a/tableauserverclient/filesys_helpers.py +++ b/tableauserverclient/filesys_helpers.py @@ -1,5 +1,6 @@ import os -ALLOWED_SPECIAL = (' ', '.', '_', '-') + +ALLOWED_SPECIAL = (" ", ".", "_", "-") def to_filename(string_to_sanitize): @@ -37,10 +38,10 @@ def get_file_type(file): # This reference lists magic file signatures: https://www.garykessler.net/library/file_sigs.html MAGIC_BYTES = { - 'zip': bytes.fromhex("504b0304"), - 'tde': bytes.fromhex("20020162"), - 'xml': bytes.fromhex("3c3f786d6c20"), - 'hyper': bytes.fromhex("487970657208000001000000") + "zip": bytes.fromhex("504b0304"), + "tde": bytes.fromhex("20020162"), + "xml": bytes.fromhex("3c3f786d6c20"), + "hyper": bytes.fromhex("487970657208000001000000"), } # Peek first bytes of a file diff --git a/tableauserverclient/models/column_item.py b/tableauserverclient/models/column_item.py index 9bf198220..a95d005ca 100644 --- a/tableauserverclient/models/column_item.py +++ b/tableauserverclient/models/column_item.py @@ -48,7 +48,7 @@ def _set_values(self, id, name, description, remote_type): def from_response(cls, resp, ns): all_column_items = list() parsed_response = ET.fromstring(resp) - all_column_xml = parsed_response.findall('.//t:column', namespaces=ns) + all_column_xml = parsed_response.findall(".//t:column", namespaces=ns) for column_xml in all_column_xml: (id, name, description, remote_type) = cls._parse_element(column_xml, ns) @@ -60,9 +60,9 @@ def from_response(cls, resp, ns): @staticmethod def _parse_element(column_xml, ns): - id = column_xml.get('id', None) - name = column_xml.get('name', None) - description = column_xml.get('description', None) - remote_type = column_xml.get('remoteType', None) + id = column_xml.get("id", None) + name = column_xml.get("name", None) + description = column_xml.get("description", None) + remote_type = column_xml.get("remoteType", None) return id, name, description, remote_type diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index c883a515a..db65de0ad 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -35,12 +35,12 @@ def oauth(self, value): @classmethod def from_xml_element(cls, parsed_response, ns): - connection_creds_xml = parsed_response.find('.//t:connectionCredentials', namespaces=ns) + connection_creds_xml = parsed_response.find(".//t:connectionCredentials", namespaces=ns) - name = connection_creds_xml.get('name', None) - password = connection_creds_xml.get('password', None) - embed = connection_creds_xml.get('embed', None) - oAuth = connection_creds_xml.get('oAuth', None) + name = connection_creds_xml.get("name", None) + password = connection_creds_xml.get("password", None) + embed = connection_creds_xml.get("embed", None) + oAuth = connection_creds_xml.get("oAuth", None) connection_creds = cls(name, password, embed, oAuth) return connection_creds diff --git a/tableauserverclient/models/connection_item.py b/tableauserverclient/models/connection_item.py index 8f923fecb..018c093c7 100644 --- a/tableauserverclient/models/connection_item.py +++ b/tableauserverclient/models/connection_item.py @@ -32,34 +32,33 @@ def connection_type(self): return self._connection_type def __repr__(self): - return ( - "".format(**self.__dict__) + return "".format( + **self.__dict__ ) @classmethod def from_response(cls, resp, ns): all_connection_items = list() parsed_response = ET.fromstring(resp) - all_connection_xml = parsed_response.findall('.//t:connection', namespaces=ns) + all_connection_xml = parsed_response.findall(".//t:connection", namespaces=ns) for connection_xml in all_connection_xml: connection_item = cls() - connection_item._id = connection_xml.get('id', None) - connection_item._connection_type = connection_xml.get('type', None) - connection_item.embed_password = string_to_bool(connection_xml.get('embedPassword', '')) - connection_item.server_address = connection_xml.get('serverAddress', None) - connection_item.server_port = connection_xml.get('serverPort', None) - connection_item.username = connection_xml.get('userName', None) - datasource_elem = connection_xml.find('.//t:datasource', namespaces=ns) + connection_item._id = connection_xml.get("id", None) + connection_item._connection_type = connection_xml.get("type", None) + connection_item.embed_password = string_to_bool(connection_xml.get("embedPassword", "")) + connection_item.server_address = connection_xml.get("serverAddress", None) + connection_item.server_port = connection_xml.get("serverPort", None) + connection_item.username = connection_xml.get("userName", None) + datasource_elem = connection_xml.find(".//t:datasource", namespaces=ns) if datasource_elem is not None: - connection_item._datasource_id = datasource_elem.get('id', None) - connection_item._datasource_name = datasource_elem.get('name', None) + connection_item._datasource_id = datasource_elem.get("id", None) + connection_item._datasource_name = datasource_elem.get("name", None) all_connection_items.append(connection_item) return all_connection_items @classmethod def from_xml_element(cls, parsed_response, ns): - ''' + """ @@ -68,27 +67,25 @@ def from_xml_element(cls, parsed_response, ns): - ''' + """ all_connection_items = list() - all_connection_xml = parsed_response.findall('.//t:connection', namespaces=ns) + all_connection_xml = parsed_response.findall(".//t:connection", namespaces=ns) for connection_xml in all_connection_xml: connection_item = cls() - connection_item.server_address = connection_xml.get('serverAddress', None) - connection_item.server_port = connection_xml.get('serverPort', None) + connection_item.server_address = connection_xml.get("serverAddress", None) + connection_item.server_port = connection_xml.get("serverPort", None) - connection_credentials = connection_xml.find( - './/t:connectionCredentials', namespaces=ns) + connection_credentials = connection_xml.find(".//t:connectionCredentials", namespaces=ns) if connection_credentials is not None: - connection_item.connection_credentials = ConnectionCredentials.from_xml_element( - connection_credentials) + connection_item.connection_credentials = ConnectionCredentials.from_xml_element(connection_credentials) return all_connection_items # Used to convert string represented boolean to a boolean type def string_to_bool(s): - return s.lower() == 'true' + return s.lower() == "true" diff --git a/tableauserverclient/models/data_acceleration_report_item.py b/tableauserverclient/models/data_acceleration_report_item.py index 2b443a3d1..eab6c24cd 100644 --- a/tableauserverclient/models/data_acceleration_report_item.py +++ b/tableauserverclient/models/data_acceleration_report_item.py @@ -3,9 +3,15 @@ class DataAccelerationReportItem(object): class ComparisonRecord(object): - def __init__(self, site, sheet_uri, unaccelerated_session_count, - avg_non_accelerated_plt, accelerated_session_count, - avg_accelerated_plt): + def __init__( + self, + site, + sheet_uri, + unaccelerated_session_count, + avg_non_accelerated_plt, + accelerated_session_count, + avg_accelerated_plt, + ): self._site = site self._sheet_uri = sheet_uri self._unaccelerated_session_count = unaccelerated_session_count @@ -46,26 +52,43 @@ def comparison_records(self): @staticmethod def _parse_element(comparison_record_xml, ns): - site = comparison_record_xml.get('site', None) - sheet_uri = comparison_record_xml.get('sheetURI', None) - unaccelerated_session_count = comparison_record_xml.get('unacceleratedSessionCount', None) - avg_non_accelerated_plt = comparison_record_xml.get('averageNonAcceleratedPLT', None) - accelerated_session_count = comparison_record_xml.get('acceleratedSessionCount', None) - avg_accelerated_plt = comparison_record_xml.get('averageAcceleratedPLT', None) - return site, sheet_uri, unaccelerated_session_count, avg_non_accelerated_plt, \ - accelerated_session_count, avg_accelerated_plt + site = comparison_record_xml.get("site", None) + sheet_uri = comparison_record_xml.get("sheetURI", None) + unaccelerated_session_count = comparison_record_xml.get("unacceleratedSessionCount", None) + avg_non_accelerated_plt = comparison_record_xml.get("averageNonAcceleratedPLT", None) + accelerated_session_count = comparison_record_xml.get("acceleratedSessionCount", None) + avg_accelerated_plt = comparison_record_xml.get("averageAcceleratedPLT", None) + return ( + site, + sheet_uri, + unaccelerated_session_count, + avg_non_accelerated_plt, + accelerated_session_count, + avg_accelerated_plt, + ) @classmethod def from_response(cls, resp, ns): comparison_records = list() parsed_response = ET.fromstring(resp) - all_comparison_records_xml = parsed_response.findall('.//t:comparisonRecord', namespaces=ns) + all_comparison_records_xml = parsed_response.findall(".//t:comparisonRecord", namespaces=ns) for comparison_record_xml in all_comparison_records_xml: - (site, sheet_uri, unaccelerated_session_count, avg_non_accelerated_plt, - accelerated_session_count, avg_accelerated_plt) = cls._parse_element(comparison_record_xml, ns) + ( + site, + sheet_uri, + unaccelerated_session_count, + avg_non_accelerated_plt, + accelerated_session_count, + avg_accelerated_plt, + ) = cls._parse_element(comparison_record_xml, ns) comparison_record = DataAccelerationReportItem.ComparisonRecord( - site, sheet_uri, unaccelerated_session_count, avg_non_accelerated_plt, - accelerated_session_count, avg_accelerated_plt) + site, + sheet_uri, + unaccelerated_session_count, + avg_non_accelerated_plt, + accelerated_session_count, + avg_accelerated_plt, + ) comparison_records.append(comparison_record) return cls(comparison_records) diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index 559050b4b..c924b6ab2 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -7,11 +7,11 @@ class DataAlertItem(object): class Frequency: - Once = 'Once' - Frequently = 'Frequently' - Hourly = 'Hourly' - Daily = 'Daily' - Weekly = 'Weekly' + Once = "Once" + Frequently = "Frequently" + Hourly = "Hourly" + Daily = "Daily" + Weekly = "Weekly" def __init__(self): self._id = None @@ -33,7 +33,9 @@ def __init__(self): def __repr__(self): return "".format(**self.__dict__) + public={public}>".format( + **self.__dict__ + ) @property def id(self): @@ -114,10 +116,25 @@ def project_id(self): def project_name(self): return self._project_name - def _set_values(self, id, subject, creatorId, createdAt, updatedAt, - frequency, public, recipients, owner_id, owner_name, - view_id, view_name, workbook_id, workbook_name, project_id, - project_name): + def _set_values( + self, + id, + subject, + creatorId, + createdAt, + updatedAt, + frequency, + public, + recipients, + owner_id, + owner_name, + view_id, + view_name, + workbook_id, + workbook_name, + project_id, + project_name, + ): if id is not None: self._id = id if subject: @@ -155,7 +172,7 @@ def _set_values(self, id, subject, creatorId, createdAt, updatedAt, def from_response(cls, resp, ns): all_alert_items = list() parsed_response = ET.fromstring(resp) - all_alert_xml = parsed_response.findall('.//t:dataAlert', namespaces=ns) + all_alert_xml = parsed_response.findall(".//t:dataAlert", namespaces=ns) for alert_xml in all_alert_xml: kwargs = cls._parse_element(alert_xml, ns) @@ -168,30 +185,30 @@ def from_response(cls, resp, ns): @staticmethod def _parse_element(alert_xml, ns): kwargs = dict() - kwargs['id'] = alert_xml.get('id', None) - kwargs['subject'] = alert_xml.get('subject', None) - kwargs['creatorId'] = alert_xml.get('creatorId', None) - kwargs['createdAt'] = alert_xml.get('createdAt', None) - kwargs['updatedAt'] = alert_xml.get('updatedAt', None) - kwargs['frequency'] = alert_xml.get('frequency', None) - kwargs['public'] = alert_xml.get('public', None) - - owner = alert_xml.findall('.//t:owner', namespaces=ns)[0] - kwargs['owner_id'] = owner.get('id', None) - kwargs['owner_name'] = owner.get('name', None) - - view_response = alert_xml.findall('.//t:view', namespaces=ns)[0] - kwargs['view_id'] = view_response.get('id', None) - kwargs['view_name'] = view_response.get('name', None) - - workbook_response = view_response.findall('.//t:workbook', namespaces=ns)[0] - kwargs['workbook_id'] = workbook_response.get('id', None) - kwargs['workbook_name'] = workbook_response.get('name', None) - project_response = view_response.findall('.//t:project', namespaces=ns)[0] - kwargs['project_id'] = project_response.get('id', None) - kwargs['project_name'] = project_response.get('name', None) - - recipients = alert_xml.findall('.//t:recipient', namespaces=ns) - kwargs['recipients'] = [recipient.get('id', None) for recipient in recipients] + kwargs["id"] = alert_xml.get("id", None) + kwargs["subject"] = alert_xml.get("subject", None) + kwargs["creatorId"] = alert_xml.get("creatorId", None) + kwargs["createdAt"] = alert_xml.get("createdAt", None) + kwargs["updatedAt"] = alert_xml.get("updatedAt", None) + kwargs["frequency"] = alert_xml.get("frequency", None) + kwargs["public"] = alert_xml.get("public", None) + + owner = alert_xml.findall(".//t:owner", namespaces=ns)[0] + kwargs["owner_id"] = owner.get("id", None) + kwargs["owner_name"] = owner.get("name", None) + + view_response = alert_xml.findall(".//t:view", namespaces=ns)[0] + kwargs["view_id"] = view_response.get("id", None) + kwargs["view_name"] = view_response.get("name", None) + + workbook_response = view_response.findall(".//t:workbook", namespaces=ns)[0] + kwargs["workbook_id"] = workbook_response.get("id", None) + kwargs["workbook_name"] = workbook_response.get("name", None) + project_response = view_response.findall(".//t:project", namespaces=ns)[0] + kwargs["project_id"] = project_response.get("id", None) + kwargs["project_name"] = project_response.get("name", None) + + recipients = alert_xml.findall(".//t:recipient", namespaces=ns) + kwargs["recipients"] = [recipient.get("id", None) for recipient in recipients] return kwargs diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index 5a7e74737..a319606e4 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -6,8 +6,8 @@ class DatabaseItem(object): class ContentPermissions: - LockedToProject = 'LockedToDatabase' - ManagedByOwner = 'ManagedByOwner' + LockedToProject = "LockedToDatabase" + ManagedByOwner = "ManagedByOwner" def __init__(self, name, description=None, content_permissions=None): self._id = None @@ -163,64 +163,64 @@ def tables(self): def _set_values(self, database_values): # ID & Settable - if 'id' in database_values: - self._id = database_values['id'] + if "id" in database_values: + self._id = database_values["id"] - if 'contact' in database_values: - self._contact_id = database_values['contact']['id'] + if "contact" in database_values: + self._contact_id = database_values["contact"]["id"] - if 'name' in database_values: - self._name = database_values['name'] + if "name" in database_values: + self._name = database_values["name"] - if 'description' in database_values: - self._description = database_values['description'] + if "description" in database_values: + self._description = database_values["description"] - if 'isCertified' in database_values: - self._certified = string_to_bool(database_values['isCertified']) + if "isCertified" in database_values: + self._certified = string_to_bool(database_values["isCertified"]) - if 'certificationNote' in database_values: - self._certification_note = database_values['certificationNote'] + if "certificationNote" in database_values: + self._certification_note = database_values["certificationNote"] # Not settable, alphabetical - if 'connectionType' in database_values: - self._connection_type = database_values['connectionType'] + if "connectionType" in database_values: + self._connection_type = database_values["connectionType"] - if 'connectorUrl' in database_values: - self._connector_url = database_values['connectorUrl'] + if "connectorUrl" in database_values: + self._connector_url = database_values["connectorUrl"] - if 'contentPermissions' in database_values: - self._content_permissions = database_values['contentPermissions'] + if "contentPermissions" in database_values: + self._content_permissions = database_values["contentPermissions"] - if 'isEmbedded' in database_values: - self._embedded = string_to_bool(database_values['isEmbedded']) + if "isEmbedded" in database_values: + self._embedded = string_to_bool(database_values["isEmbedded"]) - if 'fileExtension' in database_values: - self._file_extension = database_values['fileExtension'] + if "fileExtension" in database_values: + self._file_extension = database_values["fileExtension"] - if 'fileId' in database_values: - self._file_id = database_values['fileId'] + if "fileId" in database_values: + self._file_id = database_values["fileId"] - if 'filePath' in database_values: - self._file_path = database_values['filePath'] + if "filePath" in database_values: + self._file_path = database_values["filePath"] - if 'hostName' in database_values: - self._host_name = database_values['hostName'] + if "hostName" in database_values: + self._host_name = database_values["hostName"] - if 'mimeType' in database_values: - self._mime_type = database_values['mimeType'] + if "mimeType" in database_values: + self._mime_type = database_values["mimeType"] - if 'port' in database_values: - self._port = int(database_values['port']) + if "port" in database_values: + self._port = int(database_values["port"]) - if 'provider' in database_values: - self._provider = database_values['provider'] + if "provider" in database_values: + self._provider = database_values["provider"] - if 'requestUrl' in database_values: - self._request_url = database_values['requestUrl'] + if "requestUrl" in database_values: + self._request_url = database_values["requestUrl"] - if 'type' in database_values: - self._metadata_type = database_values['type'] + if "type" in database_values: + self._metadata_type = database_values["type"] def _set_permissions(self, permissions): self._permissions = permissions @@ -235,11 +235,11 @@ def _set_default_permissions(self, permissions, content_type): def from_response(cls, resp, ns): all_database_items = list() parsed_response = ET.fromstring(resp) - all_database_xml = parsed_response.findall('.//t:database', namespaces=ns) + all_database_xml = parsed_response.findall(".//t:database", namespaces=ns) for database_xml in all_database_xml: parsed_database = cls._parse_element(database_xml, ns) - database_item = cls(parsed_database['name']) + database_item = cls(parsed_database["name"]) database_item._set_values(parsed_database) all_database_items.append(database_item) return all_database_items @@ -247,12 +247,12 @@ def from_response(cls, resp, ns): @staticmethod def _parse_element(database_xml, ns): database_values = database_xml.attrib.copy() - contact = database_xml.find('.//t:contact', namespaces=ns) + contact = database_xml.find(".//t:contact", namespaces=ns) if contact is not None: - database_values['contact'] = contact.attrib.copy() + database_values["contact"] = contact.attrib.copy() return database_values # Used to convert string represented boolean to a boolean type def string_to_bool(s): - return s.lower() == 'true' + return s.lower() == "true" diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index a50d5a412..219df39c2 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -8,9 +8,9 @@ class DatasourceItem(object): class AskDataEnablement: - Enabled = 'Enabled' - Disabled = 'Disabled' - SiteDefault = 'SiteDefault' + Enabled = "Enabled" + Disabled = "Disabled" + SiteDefault = "SiteDefault" def __init__(self, project_id, name=None): self._ask_data_enablement = None @@ -48,7 +48,7 @@ def ask_data_enablement(self, value): @property def connections(self): if self._connections is None: - error = 'Datasource item must be populated with connections first.' + error = "Datasource item must be populated with connections first." raise UnpopulatedPropertyError(error) return self._connections() @@ -144,19 +144,71 @@ def _set_permissions(self, permissions): def _parse_common_elements(self, datasource_xml, ns): if not isinstance(datasource_xml, ET.Element): - datasource_xml = ET.fromstring(datasource_xml).find('.//t:datasource', namespaces=ns) + datasource_xml = ET.fromstring(datasource_xml).find(".//t:datasource", namespaces=ns) if datasource_xml is not None: - (ask_data_enablement, certified, certification_note, _, _, _, _, encrypt_extracts, has_extracts, - _, _, owner_id, project_id, project_name, _, updated_at, use_remote_query_agent, - webpage_url) = self._parse_element(datasource_xml, ns) - self._set_values(ask_data_enablement, certified, certification_note, None, None, None, None, - encrypt_extracts, has_extracts, None, None, owner_id, project_id, project_name, None, - updated_at, use_remote_query_agent, webpage_url) + ( + ask_data_enablement, + certified, + certification_note, + _, + _, + _, + _, + encrypt_extracts, + has_extracts, + _, + _, + owner_id, + project_id, + project_name, + _, + updated_at, + use_remote_query_agent, + webpage_url, + ) = self._parse_element(datasource_xml, ns) + self._set_values( + ask_data_enablement, + certified, + certification_note, + None, + None, + None, + None, + encrypt_extracts, + has_extracts, + None, + None, + owner_id, + project_id, + project_name, + None, + updated_at, + use_remote_query_agent, + webpage_url, + ) return self - def _set_values(self, ask_data_enablement, certified, certification_note, content_url, created_at, datasource_type, - description, encrypt_extracts, has_extracts, id_, name, owner_id, project_id, project_name, tags, - updated_at, use_remote_query_agent, webpage_url): + def _set_values( + self, + ask_data_enablement, + certified, + certification_note, + content_url, + created_at, + datasource_type, + description, + encrypt_extracts, + has_extracts, + id_, + name, + owner_id, + project_id, + project_name, + tags, + updated_at, + use_remote_query_agent, + webpage_url, + ): if ask_data_enablement is not None: self._ask_data_enablement = ask_data_enablement if certification_note: @@ -171,9 +223,9 @@ def _set_values(self, ask_data_enablement, certified, certification_note, conten if description: self.description = description if encrypt_extracts is not None: - self.encrypt_extracts = str(encrypt_extracts).lower() == 'true' + self.encrypt_extracts = str(encrypt_extracts).lower() == "true" if has_extracts is not None: - self._has_extracts = str(has_extracts).lower() == 'true' + self._has_extracts = str(has_extracts).lower() == "true" if id_ is not None: self._id = id_ if name: @@ -190,7 +242,7 @@ def _set_values(self, ask_data_enablement, certified, certification_note, conten if updated_at: self._updated_at = updated_at if use_remote_query_agent is not None: - self._use_remote_query_agent = str(use_remote_query_agent).lower() == 'true' + self._use_remote_query_agent = str(use_remote_query_agent).lower() == "true" if webpage_url: self._webpage_url = webpage_url @@ -198,58 +250,108 @@ def _set_values(self, ask_data_enablement, certified, certification_note, conten def from_response(cls, resp, ns): all_datasource_items = list() parsed_response = ET.fromstring(resp) - all_datasource_xml = parsed_response.findall('.//t:datasource', namespaces=ns) + all_datasource_xml = parsed_response.findall(".//t:datasource", namespaces=ns) for datasource_xml in all_datasource_xml: - (ask_data_enablement, certified, certification_note, content_url, created_at, datasource_type, - description, encrypt_extracts, has_extracts, id_, name, owner_id, project_id, project_name, tags, - updated_at, use_remote_query_agent, webpage_url) = cls._parse_element(datasource_xml, ns) + ( + ask_data_enablement, + certified, + certification_note, + content_url, + created_at, + datasource_type, + description, + encrypt_extracts, + has_extracts, + id_, + name, + owner_id, + project_id, + project_name, + tags, + updated_at, + use_remote_query_agent, + webpage_url, + ) = cls._parse_element(datasource_xml, ns) datasource_item = cls(project_id) - datasource_item._set_values(ask_data_enablement, certified, certification_note, content_url, - created_at, datasource_type, description, encrypt_extracts, - has_extracts, id_, name, owner_id, None, project_name, tags, updated_at, - use_remote_query_agent, webpage_url) + datasource_item._set_values( + ask_data_enablement, + certified, + certification_note, + content_url, + created_at, + datasource_type, + description, + encrypt_extracts, + has_extracts, + id_, + name, + owner_id, + None, + project_name, + tags, + updated_at, + use_remote_query_agent, + webpage_url, + ) all_datasource_items.append(datasource_item) return all_datasource_items @staticmethod def _parse_element(datasource_xml, ns): - certification_note = datasource_xml.get('certificationNote', None) - certified = str(datasource_xml.get('isCertified', None)).lower() == 'true' - content_url = datasource_xml.get('contentUrl', None) - created_at = parse_datetime(datasource_xml.get('createdAt', None)) - datasource_type = datasource_xml.get('type', None) - description = datasource_xml.get('description', None) - encrypt_extracts = datasource_xml.get('encryptExtracts', None) - has_extracts = datasource_xml.get('hasExtracts', None) - id_ = datasource_xml.get('id', None) - name = datasource_xml.get('name', None) - updated_at = parse_datetime(datasource_xml.get('updatedAt', None)) - use_remote_query_agent = datasource_xml.get('useRemoteQueryAgent', None) - webpage_url = datasource_xml.get('webpageUrl', None) + certification_note = datasource_xml.get("certificationNote", None) + certified = str(datasource_xml.get("isCertified", None)).lower() == "true" + content_url = datasource_xml.get("contentUrl", None) + created_at = parse_datetime(datasource_xml.get("createdAt", None)) + datasource_type = datasource_xml.get("type", None) + description = datasource_xml.get("description", None) + encrypt_extracts = datasource_xml.get("encryptExtracts", None) + has_extracts = datasource_xml.get("hasExtracts", None) + id_ = datasource_xml.get("id", None) + name = datasource_xml.get("name", None) + updated_at = parse_datetime(datasource_xml.get("updatedAt", None)) + use_remote_query_agent = datasource_xml.get("useRemoteQueryAgent", None) + webpage_url = datasource_xml.get("webpageUrl", None) tags = None - tags_elem = datasource_xml.find('.//t:tags', namespaces=ns) + tags_elem = datasource_xml.find(".//t:tags", namespaces=ns) if tags_elem is not None: tags = TagItem.from_xml_element(tags_elem, ns) project_id = None project_name = None - project_elem = datasource_xml.find('.//t:project', namespaces=ns) + project_elem = datasource_xml.find(".//t:project", namespaces=ns) if project_elem is not None: - project_id = project_elem.get('id', None) - project_name = project_elem.get('name', None) + project_id = project_elem.get("id", None) + project_name = project_elem.get("name", None) owner_id = None - owner_elem = datasource_xml.find('.//t:owner', namespaces=ns) + owner_elem = datasource_xml.find(".//t:owner", namespaces=ns) if owner_elem is not None: - owner_id = owner_elem.get('id', None) + owner_id = owner_elem.get("id", None) ask_data_enablement = None - ask_data_elem = datasource_xml.find('.//t:askData', namespaces=ns) + ask_data_elem = datasource_xml.find(".//t:askData", namespaces=ns) if ask_data_elem is not None: - ask_data_enablement = ask_data_elem.get('enablement', None) - - return (ask_data_enablement, certified, certification_note, content_url, created_at, - datasource_type, description, encrypt_extracts, has_extracts, id_, name, owner_id, - project_id, project_name, tags, updated_at, use_remote_query_agent, webpage_url) + ask_data_enablement = ask_data_elem.get("enablement", None) + + return ( + ask_data_enablement, + certified, + certification_note, + content_url, + created_at, + datasource_type, + description, + encrypt_extracts, + has_extracts, + id_, + name, + owner_id, + project_id, + project_name, + tags, + updated_at, + use_remote_query_agent, + webpage_url, + ) diff --git a/tableauserverclient/models/favorites_item.py b/tableauserverclient/models/favorites_item.py index 7d2408f93..3d6feff5d 100644 --- a/tableauserverclient/models/favorites_item.py +++ b/tableauserverclient/models/favorites_item.py @@ -5,45 +5,45 @@ from .project_item import ProjectItem from .datasource_item import DatasourceItem -logger = logging.getLogger('tableau.models.favorites_item') +logger = logging.getLogger("tableau.models.favorites_item") class FavoriteItem: class Type: - Workbook = 'workbook' - Datasource = 'datasource' - View = 'view' - Project = 'project' + Workbook = "workbook" + Datasource = "datasource" + View = "view" + Project = "project" @classmethod def from_response(cls, xml, namespace): favorites = { - 'datasources': [], - 'projects': [], - 'views': [], - 'workbooks': [], + "datasources": [], + "projects": [], + "views": [], + "workbooks": [], } parsed_response = ET.fromstring(xml) - for workbook in parsed_response.findall('.//t:favorite/t:workbook', namespace): - fav_workbook = WorkbookItem('') + for workbook in parsed_response.findall(".//t:favorite/t:workbook", namespace): + fav_workbook = WorkbookItem("") fav_workbook._set_values(*fav_workbook._parse_element(workbook, namespace)) if fav_workbook: - favorites['workbooks'].append(fav_workbook) - for view in parsed_response.findall('.//t:favorite[t:view]', namespace): + favorites["workbooks"].append(fav_workbook) + for view in parsed_response.findall(".//t:favorite[t:view]", namespace): fav_views = ViewItem.from_xml_element(view, namespace) if fav_views: for fav_view in fav_views: - favorites['views'].append(fav_view) - for datasource in parsed_response.findall('.//t:favorite/t:datasource', namespace): - fav_datasource = DatasourceItem('') + favorites["views"].append(fav_view) + for datasource in parsed_response.findall(".//t:favorite/t:datasource", namespace): + fav_datasource = DatasourceItem("") fav_datasource._set_values(*fav_datasource._parse_element(datasource, namespace)) if fav_datasource: - favorites['datasources'].append(fav_datasource) - for project in parsed_response.findall('.//t:favorite/t:project', namespace): - fav_project = ProjectItem('p') + favorites["datasources"].append(fav_datasource) + for project in parsed_response.findall(".//t:favorite/t:project", namespace): + fav_project = ProjectItem("p") fav_project._set_values(*fav_project._parse_element(project)) if fav_project: - favorites['projects'].append(fav_project) + favorites["projects"].append(fav_project) return favorites diff --git a/tableauserverclient/models/fileupload_item.py b/tableauserverclient/models/fileupload_item.py index bd5a3a85f..a697a5aaf 100644 --- a/tableauserverclient/models/fileupload_item.py +++ b/tableauserverclient/models/fileupload_item.py @@ -17,8 +17,8 @@ def file_size(self): @classmethod def from_response(cls, resp, ns): parsed_response = ET.fromstring(resp) - fileupload_elem = parsed_response.find('.//t:fileUpload', namespaces=ns) + fileupload_elem = parsed_response.find(".//t:fileUpload", namespaces=ns) fileupload_item = cls() - fileupload_item._upload_session_id = fileupload_elem.get('uploadSessionId', None) - fileupload_item._file_size = fileupload_elem.get('fileSize', None) + fileupload_item._upload_session_id = fileupload_elem.get("uploadSessionId", None) + fileupload_item._file_size = fileupload_elem.get("fileSize", None) return fileupload_item diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index c978d8175..99e857369 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -26,7 +26,7 @@ def __init__(self, project_id, name=None): @property def connections(self): if self._connections is None: - error = 'Flow item must be populated with connections first.' + error = "Flow item must be populated with connections first." raise UnpopulatedPropertyError(error) return self._connections() @@ -86,15 +86,15 @@ def _set_permissions(self, permissions): def _parse_common_elements(self, flow_xml, ns): if not isinstance(flow_xml, ET.Element): - flow_xml = ET.fromstring(flow_xml).find('.//t:flow', namespaces=ns) + flow_xml = ET.fromstring(flow_xml).find(".//t:flow", namespaces=ns) if flow_xml is not None: (_, _, _, _, _, updated_at, _, project_id, project_name, owner_id) = self._parse_element(flow_xml, ns) - self._set_values(None, None, None, None, None, updated_at, None, project_id, - project_name, owner_id) + self._set_values(None, None, None, None, None, updated_at, None, project_id, project_name, owner_id) return self - def _set_values(self, id, name, description, webpage_url, created_at, - updated_at, tags, project_id, project_name, owner_id): + def _set_values( + self, id, name, description, webpage_url, created_at, updated_at, tags, project_id, project_name, owner_id + ): if id is not None: self._id = id if name: @@ -121,42 +121,52 @@ def _set_values(self, id, name, description, webpage_url, created_at, def from_response(cls, resp, ns): all_flow_items = list() parsed_response = ET.fromstring(resp) - all_flow_xml = parsed_response.findall('.//t:flow', namespaces=ns) + all_flow_xml = parsed_response.findall(".//t:flow", namespaces=ns) for flow_xml in all_flow_xml: - (id_, name, description, webpage_url, created_at, updated_at, - tags, project_id, project_name, owner_id) = cls._parse_element(flow_xml, ns) + ( + id_, + name, + description, + webpage_url, + created_at, + updated_at, + tags, + project_id, + project_name, + owner_id, + ) = cls._parse_element(flow_xml, ns) flow_item = cls(project_id) - flow_item._set_values(id_, name, description, webpage_url, created_at, updated_at, - tags, None, project_name, owner_id) + flow_item._set_values( + id_, name, description, webpage_url, created_at, updated_at, tags, None, project_name, owner_id + ) all_flow_items.append(flow_item) return all_flow_items @staticmethod def _parse_element(flow_xml, ns): - id_ = flow_xml.get('id', None) - name = flow_xml.get('name', None) - description = flow_xml.get('description', None) - webpage_url = flow_xml.get('webpageUrl', None) - created_at = parse_datetime(flow_xml.get('createdAt', None)) - updated_at = parse_datetime(flow_xml.get('updatedAt', None)) + id_ = flow_xml.get("id", None) + name = flow_xml.get("name", None) + description = flow_xml.get("description", None) + webpage_url = flow_xml.get("webpageUrl", None) + created_at = parse_datetime(flow_xml.get("createdAt", None)) + updated_at = parse_datetime(flow_xml.get("updatedAt", None)) tags = None - tags_elem = flow_xml.find('.//t:tags', namespaces=ns) + tags_elem = flow_xml.find(".//t:tags", namespaces=ns) if tags_elem is not None: tags = TagItem.from_xml_element(tags_elem, ns) project_id = None project_name = None - project_elem = flow_xml.find('.//t:project', namespaces=ns) + project_elem = flow_xml.find(".//t:project", namespaces=ns) if project_elem is not None: - project_id = project_elem.get('id', None) - project_name = project_elem.get('name', None) + project_id = project_elem.get("id", None) + project_name = project_elem.get("name", None) owner_id = None - owner_elem = flow_xml.find('.//t:owner', namespaces=ns) + owner_elem = flow_xml.find(".//t:owner", namespaces=ns) if owner_elem is not None: - owner_id = owner_elem.get('id', None) + owner_id = owner_elem.get("id", None) - return (id_, name, description, webpage_url, created_at, updated_at, tags, project_id, - project_name, owner_id) + return (id_, name, description, webpage_url, created_at, updated_at, tags, project_id, project_name, owner_id) diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index af9465dfb..fdc06604b 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -7,11 +7,11 @@ class GroupItem(object): - tag_name = 'group' + tag_name = "group" class LicenseMode: - onLogin = 'onLogin' - onSync = 'onSync' + onLogin = "onLogin" + onSync = "onSync" def __init__(self, name=None, domain_name=None): self._id = None @@ -78,23 +78,23 @@ def _set_users(self, users): def from_response(cls, resp, ns): all_group_items = list() parsed_response = ET.fromstring(resp) - all_group_xml = parsed_response.findall('.//t:group', namespaces=ns) + all_group_xml = parsed_response.findall(".//t:group", namespaces=ns) for group_xml in all_group_xml: - name = group_xml.get('name', None) + name = group_xml.get("name", None) group_item = cls(name) - group_item._id = group_xml.get('id', None) + group_item._id = group_xml.get("id", None) # Domain name is returned in a domain element for some calls - domain_elem = group_xml.find('.//t:domain', namespaces=ns) + domain_elem = group_xml.find(".//t:domain", namespaces=ns) if domain_elem is not None: - group_item.domain_name = domain_elem.get('name', None) + group_item.domain_name = domain_elem.get("name", None) # Import element is returned for both local and AD groups (2020.3+) - import_elem = group_xml.find('.//t:import', namespaces=ns) + import_elem = group_xml.find(".//t:import", namespaces=ns) if import_elem is not None: - group_item.domain_name = import_elem.get('domainName', None) - group_item.license_mode = import_elem.get('grantLicenseMode', None) - group_item.minimum_site_role = import_elem.get('siteRole', None) + group_item.domain_name = import_elem.get("domainName", None) + group_item.license_mode = import_elem.get("grantLicenseMode", None) + group_item.minimum_site_role = import_elem.get("siteRole", None) all_group_items.append(group_item) return all_group_items diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index cbc148e88..320e01ef2 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -62,7 +62,7 @@ def interval(self): @interval.setter def interval(self, interval): - VALID_INTERVALS = {.25, .5, 1, 2, 4, 6, 8, 12} + VALID_INTERVALS = {0.25, 0.5, 1, 2, 4, 6, 8, 12} if float(interval) not in VALID_INTERVALS: error = "Invalid interval {} not in {}".format(interval, str(VALID_INTERVALS)) raise ValueError(error) @@ -73,7 +73,7 @@ def _interval_type_pairs(self): # We use fractional hours for the two minute-based intervals. # Need to convert to minutes from hours here - if self.interval in {.25, .5}: + if self.interval in {0.25, 0.5}: calculated_interval = int(self.interval * 60) interval_type = IntervalItem.Occurrence.Minutes else: diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index 985907ba3..7b7ea4921 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -3,8 +3,18 @@ class JobItem(object): - def __init__(self, id_, job_type, progress, created_at, started_at=None, - completed_at=None, finish_code=0, notes=None, mode=None): + def __init__( + self, + id_, + job_type, + progress, + created_at, + started_at=None, + completed_at=None, + finish_code=0, + notes=None, + mode=None, + ): self._id = id_ self._type = job_type self._progress = progress @@ -57,14 +67,15 @@ def mode(self, value): self._mode = value def __repr__(self): - return "".format(**self.__dict__) + return ( + "".format(**self.__dict__) + ) @classmethod def from_response(cls, xml, ns): parsed_response = ET.fromstring(xml) - all_tasks_xml = parsed_response.findall( - './/t:job', namespaces=ns) + all_tasks_xml = parsed_response.findall(".//t:job", namespaces=ns) all_tasks = [JobItem._parse_element(x, ns) for x in all_tasks_xml] @@ -72,16 +83,15 @@ def from_response(cls, xml, ns): @classmethod def _parse_element(cls, element, ns): - id_ = element.get('id', None) - type_ = element.get('type', None) - progress = element.get('progress', None) - created_at = parse_datetime(element.get('createdAt', None)) - started_at = parse_datetime(element.get('startedAt', None)) - completed_at = parse_datetime(element.get('completedAt', None)) - finish_code = element.get('finishCode', -1) - notes = [note.text for note in - element.findall('.//t:notes', namespaces=ns)] or None - mode = element.get('mode', None) + id_ = element.get("id", None) + type_ = element.get("type", None) + progress = element.get("progress", None) + created_at = parse_datetime(element.get("createdAt", None)) + started_at = parse_datetime(element.get("startedAt", None)) + completed_at = parse_datetime(element.get("completedAt", None)) + finish_code = element.get("finishCode", -1) + notes = [note.text for note in element.findall(".//t:notes", namespaces=ns)] or None + mode = element.get("mode", None) return cls(id_, type_, progress, created_at, started_at, completed_at, finish_code, notes, mode) @@ -93,8 +103,9 @@ class Status: Failed = "Failed" Cancelled = "Cancelled" - def __init__(self, id_, created_at, priority, job_type, status, title=None, subtitle=None, started_at=None, - ended_at=None): + def __init__( + self, id_, created_at, priority, job_type, status, title=None, subtitle=None, started_at=None, ended_at=None + ): self._id = id_ self._type = job_type self._status = status @@ -150,20 +161,19 @@ def priority(self): @classmethod def from_response(cls, xml, ns): parsed_response = ET.fromstring(xml) - all_tasks_xml = parsed_response.findall( - './/t:backgroundJob', namespaces=ns) + all_tasks_xml = parsed_response.findall(".//t:backgroundJob", namespaces=ns) return [cls._parse_element(x, ns) for x in all_tasks_xml] @classmethod def _parse_element(cls, element, ns): - id_ = element.get('id', None) - type_ = element.get('jobType', None) - status = element.get('status', None) - created_at = parse_datetime(element.get('createdAt', None)) - started_at = parse_datetime(element.get('startedAt', None)) - ended_at = parse_datetime(element.get('endedAt', None)) - priority = element.get('priority', None) - title = element.get('title', None) - subtitle = element.get('subtitle', None) + id_ = element.get("id", None) + type_ = element.get("jobType", None) + status = element.get("status", None) + created_at = parse_datetime(element.get("createdAt", None)) + started_at = parse_datetime(element.get("startedAt", None)) + ended_at = parse_datetime(element.get("endedAt", None)) + priority = element.get("priority", None) + title = element.get("title", None) + subtitle = element.get("subtitle", None) return cls(id_, created_at, priority, type_, status, title, subtitle, started_at, ended_at) diff --git a/tableauserverclient/models/pagination_item.py b/tableauserverclient/models/pagination_item.py index a1f5409e3..df9ca26e6 100644 --- a/tableauserverclient/models/pagination_item.py +++ b/tableauserverclient/models/pagination_item.py @@ -22,12 +22,12 @@ def total_available(self): @classmethod def from_response(cls, resp, ns): parsed_response = ET.fromstring(resp) - pagination_xml = parsed_response.find('t:pagination', namespaces=ns) + pagination_xml = parsed_response.find("t:pagination", namespaces=ns) pagination_item = cls() if pagination_xml is not None: - pagination_item._page_number = int(pagination_xml.get('pageNumber', '-1')) - pagination_item._page_size = int(pagination_xml.get('pageSize', '-1')) - pagination_item._total_available = int(pagination_xml.get('totalAvailable', '-1')) + pagination_item._page_number = int(pagination_xml.get("pageNumber", "-1")) + pagination_item._page_size = int(pagination_xml.get("pageSize", "-1")) + pagination_item._total_available = int(pagination_xml.get("totalAvailable", "-1")) return pagination_item @classmethod diff --git a/tableauserverclient/models/permissions_item.py b/tableauserverclient/models/permissions_item.py index 216315587..113e8525e 100644 --- a/tableauserverclient/models/permissions_item.py +++ b/tableauserverclient/models/permissions_item.py @@ -5,45 +5,43 @@ from .user_item import UserItem from .group_item import GroupItem -logger = logging.getLogger('tableau.models.permissions_item') +logger = logging.getLogger("tableau.models.permissions_item") class Permission: - class Mode: - Allow = 'Allow' - Deny = 'Deny' + Allow = "Allow" + Deny = "Deny" class Capability: - AddComment = 'AddComment' - ChangeHierarchy = 'ChangeHierarchy' - ChangePermissions = 'ChangePermissions' - Connect = 'Connect' - Delete = 'Delete' - Execute = 'Execute' - ExportData = 'ExportData' - ExportImage = 'ExportImage' - ExportXml = 'ExportXml' - Filter = 'Filter' - ProjectLeader = 'ProjectLeader' - Read = 'Read' - ShareView = 'ShareView' - ViewComments = 'ViewComments' - ViewUnderlyingData = 'ViewUnderlyingData' - WebAuthoring = 'WebAuthoring' - Write = 'Write' + AddComment = "AddComment" + ChangeHierarchy = "ChangeHierarchy" + ChangePermissions = "ChangePermissions" + Connect = "Connect" + Delete = "Delete" + Execute = "Execute" + ExportData = "ExportData" + ExportImage = "ExportImage" + ExportXml = "ExportXml" + Filter = "Filter" + ProjectLeader = "ProjectLeader" + Read = "Read" + ShareView = "ShareView" + ViewComments = "ViewComments" + ViewUnderlyingData = "ViewUnderlyingData" + WebAuthoring = "WebAuthoring" + Write = "Write" class Resource: - Workbook = 'workbook' - Datasource = 'datasource' - Flow = 'flow' - Table = 'table' - Database = 'database' - View = 'view' + Workbook = "workbook" + Datasource = "datasource" + Flow = "flow" + Table = "table" + Database = "database" + View = "view" class PermissionsRule(object): - def __init__(self, grantee, capabilities): self.grantee = grantee self.capabilities = capabilities @@ -53,23 +51,20 @@ def from_response(cls, resp, ns=None): parsed_response = ET.fromstring(resp) rules = [] - permissions_rules_list_xml = parsed_response.findall('.//t:granteeCapabilities', - namespaces=ns) + permissions_rules_list_xml = parsed_response.findall(".//t:granteeCapabilities", namespaces=ns) for grantee_capability_xml in permissions_rules_list_xml: capability_dict = {} grantee = PermissionsRule._parse_grantee_element(grantee_capability_xml, ns) - for capability_xml in grantee_capability_xml.findall( - './/t:capabilities/t:capability', namespaces=ns): - name = capability_xml.get('name') - mode = capability_xml.get('mode') + for capability_xml in grantee_capability_xml.findall(".//t:capabilities/t:capability", namespaces=ns): + name = capability_xml.get("name") + mode = capability_xml.get("mode") capability_dict[name] = mode - rule = PermissionsRule(grantee, - capability_dict) + rule = PermissionsRule(grantee, capability_dict) rules.append(rule) return rules @@ -79,17 +74,17 @@ def _parse_grantee_element(grantee_capability_xml, ns): """Use Xpath magic and some string splitting to get the right object type from the xml""" # Get the first element in the tree with an 'id' attribute - grantee_element = grantee_capability_xml.findall('.//*[@id]', namespaces=ns).pop() - grantee_id = grantee_element.get('id', None) - grantee_type = grantee_element.tag.split('}').pop() + grantee_element = grantee_capability_xml.findall(".//*[@id]", namespaces=ns).pop() + grantee_id = grantee_element.get("id", None) + grantee_type = grantee_element.tag.split("}").pop() if grantee_id is None: - logger.error('Cannot find grantee type in response') + logger.error("Cannot find grantee type in response") raise UnknownGranteeTypeError() - if grantee_type == 'user': + if grantee_type == "user": grantee = UserItem.as_reference(grantee_id) - elif grantee_type == 'group': + elif grantee_type == "group": grantee = GroupItem.as_reference(grantee_id) else: raise UnknownGranteeTypeError("No support for grantee type of {}".format(grantee_type)) diff --git a/tableauserverclient/models/personal_access_token_auth.py b/tableauserverclient/models/personal_access_token_auth.py index 13a2391b8..c80a020e8 100644 --- a/tableauserverclient/models/personal_access_token_auth.py +++ b/tableauserverclient/models/personal_access_token_auth.py @@ -1,5 +1,5 @@ class PersonalAccessTokenAuth(object): - def __init__(self, token_name, personal_access_token, site_id=''): + def __init__(self, token_name, personal_access_token, site_id=""): self.token_name = token_name self.personal_access_token = personal_access_token self.site_id = site_id @@ -8,12 +8,7 @@ def __init__(self, token_name, personal_access_token, site_id=''): @property def credentials(self): - return { - 'personalAccessTokenName': self.token_name, - 'personalAccessTokenSecret': self.personal_access_token - } + return {"personalAccessTokenName": self.token_name, "personalAccessTokenSecret": self.personal_access_token} def __repr__(self): - return "".format( - self.token_name, self.personal_access_token - ) + return "".format(self.token_name, self.personal_access_token) diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index 4cfbcb4e9..e8434a0ad 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -8,8 +8,8 @@ class ProjectItem(object): class ContentPermissions: - LockedToProject = 'LockedToProject' - ManagedByOwner = 'ManagedByOwner' + LockedToProject = "LockedToProject" + ManagedByOwner = "ManagedByOwner" def __init__(self, name, description=None, content_permissions=None, parent_id=None): self._content_permissions = None @@ -80,14 +80,14 @@ def owner_id(self): @owner_id.setter def owner_id(self, value): - raise NotImplementedError('REST API does not currently support updating project owner.') + raise NotImplementedError("REST API does not currently support updating project owner.") def is_default(self): - return self.name.lower() == 'default' + return self.name.lower() == "default" def _parse_common_tags(self, project_xml, ns): if not isinstance(project_xml, ET.Element): - project_xml = ET.fromstring(project_xml).find('.//t:project', namespaces=ns) + project_xml = ET.fromstring(project_xml).find(".//t:project", namespaces=ns) if project_xml is not None: (_, name, description, content_permissions, parent_id) = self._parse_element(project_xml) @@ -118,7 +118,7 @@ def _set_default_permissions(self, permissions, content_type): def from_response(cls, resp, ns): all_project_items = list() parsed_response = ET.fromstring(resp) - all_project_xml = parsed_response.findall('.//t:project', namespaces=ns) + all_project_xml = parsed_response.findall(".//t:project", namespaces=ns) for project_xml in all_project_xml: (id, name, description, content_permissions, parent_id, owner_id) = cls._parse_element(project_xml) @@ -129,13 +129,13 @@ def from_response(cls, resp, ns): @staticmethod def _parse_element(project_xml): - id = project_xml.get('id', None) - name = project_xml.get('name', None) - description = project_xml.get('description', None) - content_permissions = project_xml.get('contentPermissions', None) - parent_id = project_xml.get('parentProjectId', None) + id = project_xml.get("id", None) + name = project_xml.get("name", None) + description = project_xml.get("description", None) + content_permissions = project_xml.get("contentPermissions", None) + parent_id = project_xml.get("parentProjectId", None) owner_id = None for owner in project_xml: - owner_id = owner.get('id', None) + owner_id = owner.get("id", None) return id, name, description, content_permissions, parent_id, owner_id diff --git a/tableauserverclient/models/property_decorators.py b/tableauserverclient/models/property_decorators.py index f1625d112..153786d4c 100644 --- a/tableauserverclient/models/property_decorators.py +++ b/tableauserverclient/models/property_decorators.py @@ -2,6 +2,7 @@ import re from functools import wraps from ..datetime_helpers import parse_datetime + try: basestring except NameError: @@ -70,13 +71,13 @@ def wrapper(self, value): def property_is_int(range, allowed=None): - '''Takes a range of ints and a list of exemptions to check against + """Takes a range of ints and a list of exemptions to check against when setting a property on a model. The range is a tuple of (min, max) and the allowed list (empty by default) allows values outside that range. This is useful for when we use sentinel values. Example: Revisions allow a range of 2-10000, but use -1 as a sentinel for 'unlimited'. - ''' + """ if allowed is None: allowed = () # Empty tuple for fast no-op testing. @@ -98,7 +99,9 @@ def wrapper(self, value): raise ValueError(error) return func(self, value) + return wrapper + return property_type_decorator @@ -112,12 +115,14 @@ def validate_regex_decorator(self, value): if not compiled_re.match(value): raise ValueError(error) return func(self, value) + return validate_regex_decorator + return wrapper def property_is_datetime(func): - """ Takes the following datetime format and turns it into a datetime object: + """Takes the following datetime format and turns it into a datetime object: 2016-08-18T18:25:36Z @@ -130,11 +135,13 @@ def wrapper(self, value): if isinstance(value, datetime.datetime): return func(self, value) if not isinstance(value, basestring): - raise ValueError("Cannot convert {} into a datetime, cannot update {}".format(value.__class__.__name__, - func.__name__)) + raise ValueError( + "Cannot convert {} into a datetime, cannot update {}".format(value.__class__.__name__, func.__name__) + ) dt = parse_datetime(value) return func(self, dt) + return wrapper @@ -142,15 +149,15 @@ def property_is_data_acceleration_config(func): @wraps(func) def wrapper(self, value): if not isinstance(value, dict): - raise ValueError("{} is not type 'dict', cannot update {})".format(value.__class__.__name__, - func.__name__)) - if len(value) != 4 or not all(attr in value.keys() for attr in ('acceleration_enabled', - 'accelerate_now', - 'last_updated_at', - 'acceleration_status')): + raise ValueError("{} is not type 'dict', cannot update {})".format(value.__class__.__name__, func.__name__)) + if len(value) != 4 or not all( + attr in value.keys() + for attr in ("acceleration_enabled", "accelerate_now", "last_updated_at", "acceleration_status") + ): error = "{} should have 2 keys ".format(func.__name__) error += "'acceleration_enabled' and 'accelerate_now'" error += "instead you have {}".format(value.keys()) raise ValueError(error) return func(self, value) + return wrapper diff --git a/tableauserverclient/models/reference_item.py b/tableauserverclient/models/reference_item.py index 2cf0f0119..48d2ab56a 100644 --- a/tableauserverclient/models/reference_item.py +++ b/tableauserverclient/models/reference_item.py @@ -1,5 +1,4 @@ class ResourceReference(object): - def __init__(self, id_, tag_name): self.id = id_ self.tag_name = tag_name diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index c93ffe922..b54c20ae9 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -35,7 +35,7 @@ def __init__(self, name, priority, schedule_type, execution_order, interval_item self.schedule_type = schedule_type def __repr__(self): - return "".format(**self.__dict__) + return ''.format(**self.__dict__) @property def created_at(self): @@ -109,27 +109,53 @@ def warnings(self): def _parse_common_tags(self, schedule_xml, ns): if not isinstance(schedule_xml, ET.Element): - schedule_xml = ET.fromstring(schedule_xml).find('.//t:schedule', namespaces=ns) + schedule_xml = ET.fromstring(schedule_xml).find(".//t:schedule", namespaces=ns) if schedule_xml is not None: - (_, name, _, _, updated_at, _, next_run_at, end_schedule_at, execution_order, - priority, interval_item) = self._parse_element(schedule_xml, ns) - - self._set_values(id_=None, - name=name, - state=None, - created_at=None, - updated_at=updated_at, - schedule_type=None, - next_run_at=next_run_at, - end_schedule_at=end_schedule_at, - execution_order=execution_order, - priority=priority, - interval_item=interval_item) + ( + _, + name, + _, + _, + updated_at, + _, + next_run_at, + end_schedule_at, + execution_order, + priority, + interval_item, + ) = self._parse_element(schedule_xml, ns) + + self._set_values( + id_=None, + name=name, + state=None, + created_at=None, + updated_at=updated_at, + schedule_type=None, + next_run_at=next_run_at, + end_schedule_at=end_schedule_at, + execution_order=execution_order, + priority=priority, + interval_item=interval_item, + ) return self - def _set_values(self, id_, name, state, created_at, updated_at, schedule_type, - next_run_at, end_schedule_at, execution_order, priority, interval_item, warnings=None): + def _set_values( + self, + id_, + name, + state, + created_at, + updated_at, + schedule_type, + next_run_at, + end_schedule_at, + execution_order, + priority, + interval_item, + warnings=None, + ): if id_ is not None: self._id = id_ if name: @@ -165,25 +191,38 @@ def from_element(cls, parsed_response, ns): warnings = cls._read_warnings(parsed_response, ns) all_schedule_items = [] - all_schedule_xml = parsed_response.findall('.//t:schedule', namespaces=ns) + all_schedule_xml = parsed_response.findall(".//t:schedule", namespaces=ns) for schedule_xml in all_schedule_xml: - (id_, name, state, created_at, updated_at, schedule_type, next_run_at, - end_schedule_at, execution_order, priority, interval_item) = cls._parse_element(schedule_xml, ns) + ( + id_, + name, + state, + created_at, + updated_at, + schedule_type, + next_run_at, + end_schedule_at, + execution_order, + priority, + interval_item, + ) = cls._parse_element(schedule_xml, ns) schedule_item = cls(name, priority, schedule_type, execution_order, interval_item) - schedule_item._set_values(id_=id_, - name=None, - state=state, - created_at=created_at, - updated_at=updated_at, - schedule_type=None, - next_run_at=next_run_at, - end_schedule_at=end_schedule_at, - execution_order=None, - priority=None, - interval_item=None, - warnings=warnings) + schedule_item._set_values( + id_=id_, + name=None, + state=state, + created_at=created_at, + updated_at=updated_at, + schedule_type=None, + next_run_at=next_run_at, + end_schedule_at=end_schedule_at, + execution_order=None, + priority=None, + interval_item=None, + warnings=warnings, + ) all_schedule_items.append(schedule_item) return all_schedule_items @@ -223,44 +262,58 @@ def _parse_interval_item(parsed_response, frequency, ns): @staticmethod def _parse_element(schedule_xml, ns): - id = schedule_xml.get('id', None) - name = schedule_xml.get('name', None) - state = schedule_xml.get('state', None) - created_at = parse_datetime(schedule_xml.get('createdAt', None)) - updated_at = parse_datetime(schedule_xml.get('updatedAt', None)) - schedule_type = schedule_xml.get('type', None) - frequency = schedule_xml.get('frequency', None) - next_run_at = parse_datetime(schedule_xml.get('nextRunAt', None)) - end_schedule_at = parse_datetime(schedule_xml.get('endScheduleAt', None)) - execution_order = schedule_xml.get('executionOrder', None) - - priority = schedule_xml.get('priority', None) + id = schedule_xml.get("id", None) + name = schedule_xml.get("name", None) + state = schedule_xml.get("state", None) + created_at = parse_datetime(schedule_xml.get("createdAt", None)) + updated_at = parse_datetime(schedule_xml.get("updatedAt", None)) + schedule_type = schedule_xml.get("type", None) + frequency = schedule_xml.get("frequency", None) + next_run_at = parse_datetime(schedule_xml.get("nextRunAt", None)) + end_schedule_at = parse_datetime(schedule_xml.get("endScheduleAt", None)) + execution_order = schedule_xml.get("executionOrder", None) + + priority = schedule_xml.get("priority", None) if priority: priority = int(priority) interval_item = None - frequency_detail_elem = schedule_xml.find('.//t:frequencyDetails', namespaces=ns) + frequency_detail_elem = schedule_xml.find(".//t:frequencyDetails", namespaces=ns) if frequency_detail_elem is not None: interval_item = ScheduleItem._parse_interval_item(frequency_detail_elem, frequency, ns) - return id, name, state, created_at, updated_at, schedule_type, \ - next_run_at, end_schedule_at, execution_order, priority, interval_item + return ( + id, + name, + state, + created_at, + updated_at, + schedule_type, + next_run_at, + end_schedule_at, + execution_order, + priority, + interval_item, + ) @staticmethod def parse_add_to_schedule_response(response, ns): parsed_response = ET.fromstring(response.content) warnings = ScheduleItem._read_warnings(parsed_response, ns) - all_task_xml = parsed_response.findall('.//t:task', namespaces=ns) + all_task_xml = parsed_response.findall(".//t:task", namespaces=ns) - error = "Status {}: {}".format(response.status_code, response.reason) \ - if response.status_code < 200 or response.status_code >= 300 else None + error = ( + "Status {}: {}".format(response.status_code, response.reason) + if response.status_code < 200 or response.status_code >= 300 + else None + ) task_created = len(all_task_xml) > 0 return error, warnings, task_created @staticmethod def _read_warnings(parsed_response, ns): - all_warning_xml = parsed_response.findall('.//t:warning', namespaces=ns) + all_warning_xml = parsed_response.findall(".//t:warning", namespaces=ns) warnings = list() if len(all_warning_xml) > 0 else None for warning_xml in all_warning_xml: - warnings.append(warning_xml.get('message', None)) + warnings.append(warning_xml.get("message", None)) return warnings diff --git a/tableauserverclient/models/server_info_item.py b/tableauserverclient/models/server_info_item.py index 0fcdb1e1e..1f6604662 100644 --- a/tableauserverclient/models/server_info_item.py +++ b/tableauserverclient/models/server_info_item.py @@ -22,10 +22,10 @@ def rest_api_version(self): @classmethod def from_response(cls, resp, ns): parsed_response = ET.fromstring(resp) - product_version_tag = parsed_response.find('.//t:productVersion', namespaces=ns) - rest_api_version_tag = parsed_response.find('.//t:restApiVersion', namespaces=ns) + product_version_tag = parsed_response.find(".//t:productVersion", namespaces=ns) + rest_api_version_tag = parsed_response.find(".//t:restApiVersion", namespaces=ns) - build_number = product_version_tag.get('build', None) + build_number = product_version_tag.get("build", None) product_version = product_version_tag.text rest_api_version = rest_api_version_tag.text diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index f562289ce..ac20e6a89 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -1,6 +1,12 @@ import xml.etree.ElementTree as ET -from .property_decorators import (property_is_enum, property_is_boolean, property_matches, - property_not_empty, property_not_nullable, property_is_int) +from .property_decorators import ( + property_is_enum, + property_is_boolean, + property_matches, + property_not_empty, + property_not_nullable, + property_is_int, +) VALID_CONTENT_URL_RE = r"^[a-zA-Z0-9_\-]*$" @@ -8,28 +14,62 @@ class SiteItem(object): class AdminMode: - ContentAndUsers = 'ContentAndUsers' - ContentOnly = 'ContentOnly' + ContentAndUsers = "ContentAndUsers" + ContentOnly = "ContentOnly" class State: - Active = 'Active' - Suspended = 'Suspended' - - def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_quota=None, - disable_subscriptions=False, subscribe_others_enabled=True, revision_history_enabled=False, - revision_limit=None, data_acceleration_mode=None, flows_enabled=True, cataloging_enabled=True, - editing_flows_enabled=True, scheduling_flows_enabled=True, allow_subscription_attachments=True, - guest_access_enabled=False, cache_warmup_enabled=True, commenting_enabled=True, - extract_encryption_mode=None, request_access_enabled=False, run_now_enabled=True, - tier_explorer_capacity=None, tier_creator_capacity=None, tier_viewer_capacity=None, - data_alerts_enabled=True, commenting_mentions_enabled=True, catalog_obfuscation_enabled=False, - flow_auto_save_enabled=True, web_extraction_enabled=True, metrics_content_type_enabled=True, - notify_site_admins_on_throttle=False, authoring_enabled=True, custom_subscription_email_enabled=False, - custom_subscription_email=False, custom_subscription_footer_enabled=False, - custom_subscription_footer=False, ask_data_mode='EnabledByDefault', named_sharing_enabled=True, - mobile_biometrics_enabled=False, sheet_image_enabled=True, derived_permissions_enabled=False, - user_visibility_mode='FULL', use_default_time_zone=True, time_zone=None, - auto_suspend_refresh_enabled=True, auto_suspend_refresh_inactivity_window=30): + Active = "Active" + Suspended = "Suspended" + + def __init__( + self, + name, + content_url, + admin_mode=None, + user_quota=None, + storage_quota=None, + disable_subscriptions=False, + subscribe_others_enabled=True, + revision_history_enabled=False, + revision_limit=None, + data_acceleration_mode=None, + flows_enabled=True, + cataloging_enabled=True, + editing_flows_enabled=True, + scheduling_flows_enabled=True, + allow_subscription_attachments=True, + guest_access_enabled=False, + cache_warmup_enabled=True, + commenting_enabled=True, + extract_encryption_mode=None, + request_access_enabled=False, + run_now_enabled=True, + tier_explorer_capacity=None, + tier_creator_capacity=None, + tier_viewer_capacity=None, + data_alerts_enabled=True, + commenting_mentions_enabled=True, + catalog_obfuscation_enabled=False, + flow_auto_save_enabled=True, + web_extraction_enabled=True, + metrics_content_type_enabled=True, + notify_site_admins_on_throttle=False, + authoring_enabled=True, + custom_subscription_email_enabled=False, + custom_subscription_email=False, + custom_subscription_footer_enabled=False, + custom_subscription_footer=False, + ask_data_mode="EnabledByDefault", + named_sharing_enabled=True, + mobile_biometrics_enabled=False, + sheet_image_enabled=True, + derived_permissions_enabled=False, + user_visibility_mode="FULL", + use_default_time_zone=True, + time_zone=None, + auto_suspend_refresh_enabled=True, + auto_suspend_refresh_inactivity_window=30, + ): self._admin_mode = None self._id = None self._num_users = None @@ -198,7 +238,7 @@ def flows_enabled(self, value): self._flows_enabled = value def is_default(self): - return self.name.lower() == 'default' + return self.name.lower() == "default" @property def editing_flows_enabled(self): @@ -496,51 +536,171 @@ def auto_suspend_refresh_enabled(self, value): def _parse_common_tags(self, site_xml, ns): if not isinstance(site_xml, ET.Element): - site_xml = ET.fromstring(site_xml).find('.//t:site', namespaces=ns) + site_xml = ET.fromstring(site_xml).find(".//t:site", namespaces=ns) if site_xml is not None: - (_, name, content_url, _, admin_mode, state, - subscribe_others_enabled, disable_subscriptions, revision_history_enabled, - user_quota, storage_quota, revision_limit, num_users, storage, - data_acceleration_mode, flows_enabled, cataloging_enabled, editing_flows_enabled, - scheduling_flows_enabled, allow_subscription_attachments, guest_access_enabled, - cache_warmup_enabled, commenting_enabled, extract_encryption_mode, request_access_enabled, - run_now_enabled, tier_explorer_capacity, tier_creator_capacity, tier_viewer_capacity, data_alerts_enabled, - commenting_mentions_enabled, catalog_obfuscation_enabled, flow_auto_save_enabled, web_extraction_enabled, - metrics_content_type_enabled, notify_site_admins_on_throttle, authoring_enabled, - custom_subscription_email_enabled, custom_subscription_email, custom_subscription_footer_enabled, - custom_subscription_footer, ask_data_mode, named_sharing_enabled, mobile_biometrics_enabled, - sheet_image_enabled, derived_permissions_enabled, user_visibility_mode, use_default_time_zone, time_zone, - auto_suspend_refresh_enabled, auto_suspend_refresh_inactivity_window) = self._parse_element(site_xml, ns) - - self._set_values(None, name, content_url, None, admin_mode, state, subscribe_others_enabled, - disable_subscriptions, revision_history_enabled, user_quota, storage_quota, - revision_limit, num_users, storage, data_acceleration_mode, flows_enabled, - cataloging_enabled, editing_flows_enabled, scheduling_flows_enabled, - allow_subscription_attachments, guest_access_enabled, cache_warmup_enabled, - commenting_enabled, extract_encryption_mode, request_access_enabled, run_now_enabled, - tier_explorer_capacity, tier_creator_capacity, tier_viewer_capacity, data_alerts_enabled, - commenting_mentions_enabled, catalog_obfuscation_enabled, flow_auto_save_enabled, - web_extraction_enabled, metrics_content_type_enabled, notify_site_admins_on_throttle, - authoring_enabled, custom_subscription_email_enabled, custom_subscription_email, - custom_subscription_footer_enabled, custom_subscription_footer, ask_data_mode, - named_sharing_enabled, mobile_biometrics_enabled, sheet_image_enabled, - derived_permissions_enabled, user_visibility_mode, use_default_time_zone, time_zone, - auto_suspend_refresh_enabled, auto_suspend_refresh_inactivity_window) + ( + _, + name, + content_url, + _, + admin_mode, + state, + subscribe_others_enabled, + disable_subscriptions, + revision_history_enabled, + user_quota, + storage_quota, + revision_limit, + num_users, + storage, + data_acceleration_mode, + flows_enabled, + cataloging_enabled, + editing_flows_enabled, + scheduling_flows_enabled, + allow_subscription_attachments, + guest_access_enabled, + cache_warmup_enabled, + commenting_enabled, + extract_encryption_mode, + request_access_enabled, + run_now_enabled, + tier_explorer_capacity, + tier_creator_capacity, + tier_viewer_capacity, + data_alerts_enabled, + commenting_mentions_enabled, + catalog_obfuscation_enabled, + flow_auto_save_enabled, + web_extraction_enabled, + metrics_content_type_enabled, + notify_site_admins_on_throttle, + authoring_enabled, + custom_subscription_email_enabled, + custom_subscription_email, + custom_subscription_footer_enabled, + custom_subscription_footer, + ask_data_mode, + named_sharing_enabled, + mobile_biometrics_enabled, + sheet_image_enabled, + derived_permissions_enabled, + user_visibility_mode, + use_default_time_zone, + time_zone, + auto_suspend_refresh_enabled, + auto_suspend_refresh_inactivity_window, + ) = self._parse_element(site_xml, ns) + + self._set_values( + None, + name, + content_url, + None, + admin_mode, + state, + subscribe_others_enabled, + disable_subscriptions, + revision_history_enabled, + user_quota, + storage_quota, + revision_limit, + num_users, + storage, + data_acceleration_mode, + flows_enabled, + cataloging_enabled, + editing_flows_enabled, + scheduling_flows_enabled, + allow_subscription_attachments, + guest_access_enabled, + cache_warmup_enabled, + commenting_enabled, + extract_encryption_mode, + request_access_enabled, + run_now_enabled, + tier_explorer_capacity, + tier_creator_capacity, + tier_viewer_capacity, + data_alerts_enabled, + commenting_mentions_enabled, + catalog_obfuscation_enabled, + flow_auto_save_enabled, + web_extraction_enabled, + metrics_content_type_enabled, + notify_site_admins_on_throttle, + authoring_enabled, + custom_subscription_email_enabled, + custom_subscription_email, + custom_subscription_footer_enabled, + custom_subscription_footer, + ask_data_mode, + named_sharing_enabled, + mobile_biometrics_enabled, + sheet_image_enabled, + derived_permissions_enabled, + user_visibility_mode, + use_default_time_zone, + time_zone, + auto_suspend_refresh_enabled, + auto_suspend_refresh_inactivity_window, + ) return self - def _set_values(self, id, name, content_url, status_reason, admin_mode, state, - subscribe_others_enabled, disable_subscriptions, revision_history_enabled, - user_quota, storage_quota, revision_limit, num_users, storage, data_acceleration_mode, - flows_enabled, cataloging_enabled, editing_flows_enabled, scheduling_flows_enabled, - allow_subscription_attachments, guest_access_enabled, cache_warmup_enabled, commenting_enabled, - extract_encryption_mode, request_access_enabled, run_now_enabled, tier_explorer_capacity, - tier_creator_capacity, tier_viewer_capacity, data_alerts_enabled, commenting_mentions_enabled, - catalog_obfuscation_enabled, flow_auto_save_enabled, web_extraction_enabled, - metrics_content_type_enabled, notify_site_admins_on_throttle, authoring_enabled, - custom_subscription_email_enabled, custom_subscription_email, custom_subscription_footer_enabled, - custom_subscription_footer, ask_data_mode, named_sharing_enabled, mobile_biometrics_enabled, - sheet_image_enabled, derived_permissions_enabled, user_visibility_mode, use_default_time_zone, - time_zone, auto_suspend_refresh_enabled, auto_suspend_refresh_inactivity_window): + def _set_values( + self, + id, + name, + content_url, + status_reason, + admin_mode, + state, + subscribe_others_enabled, + disable_subscriptions, + revision_history_enabled, + user_quota, + storage_quota, + revision_limit, + num_users, + storage, + data_acceleration_mode, + flows_enabled, + cataloging_enabled, + editing_flows_enabled, + scheduling_flows_enabled, + allow_subscription_attachments, + guest_access_enabled, + cache_warmup_enabled, + commenting_enabled, + extract_encryption_mode, + request_access_enabled, + run_now_enabled, + tier_explorer_capacity, + tier_creator_capacity, + tier_viewer_capacity, + data_alerts_enabled, + commenting_mentions_enabled, + catalog_obfuscation_enabled, + flow_auto_save_enabled, + web_extraction_enabled, + metrics_content_type_enabled, + notify_site_admins_on_throttle, + authoring_enabled, + custom_subscription_email_enabled, + custom_subscription_email, + custom_subscription_footer_enabled, + custom_subscription_footer, + ask_data_mode, + named_sharing_enabled, + mobile_biometrics_enabled, + sheet_image_enabled, + derived_permissions_enabled, + user_visibility_mode, + use_default_time_zone, + time_zone, + auto_suspend_refresh_enabled, + auto_suspend_refresh_inactivity_window, + ): if id is not None: self._id = id if name: @@ -648,133 +808,252 @@ def _set_values(self, id, name, content_url, status_reason, admin_mode, state, def from_response(cls, resp, ns): all_site_items = list() parsed_response = ET.fromstring(resp) - all_site_xml = parsed_response.findall('.//t:site', namespaces=ns) + all_site_xml = parsed_response.findall(".//t:site", namespaces=ns) for site_xml in all_site_xml: - (id, name, content_url, status_reason, admin_mode, state, subscribe_others_enabled, - disable_subscriptions, revision_history_enabled, user_quota, storage_quota, - revision_limit, num_users, storage, data_acceleration_mode, flows_enabled, cataloging_enabled, - editing_flows_enabled, scheduling_flows_enabled, allow_subscription_attachments, guest_access_enabled, - cache_warmup_enabled, commenting_enabled, extract_encryption_mode, request_access_enabled, - run_now_enabled, tier_explorer_capacity, tier_creator_capacity, tier_viewer_capacity, - data_alerts_enabled, commenting_mentions_enabled, catalog_obfuscation_enabled, flow_auto_save_enabled, - web_extraction_enabled, metrics_content_type_enabled, notify_site_admins_on_throttle, - authoring_enabled, custom_subscription_email_enabled, custom_subscription_email, - custom_subscription_footer_enabled, custom_subscription_footer, ask_data_mode, named_sharing_enabled, - mobile_biometrics_enabled, sheet_image_enabled, derived_permissions_enabled, user_visibility_mode, - use_default_time_zone, time_zone, auto_suspend_refresh_enabled, - auto_suspend_refresh_inactivity_window) = cls._parse_element(site_xml, ns) + ( + id, + name, + content_url, + status_reason, + admin_mode, + state, + subscribe_others_enabled, + disable_subscriptions, + revision_history_enabled, + user_quota, + storage_quota, + revision_limit, + num_users, + storage, + data_acceleration_mode, + flows_enabled, + cataloging_enabled, + editing_flows_enabled, + scheduling_flows_enabled, + allow_subscription_attachments, + guest_access_enabled, + cache_warmup_enabled, + commenting_enabled, + extract_encryption_mode, + request_access_enabled, + run_now_enabled, + tier_explorer_capacity, + tier_creator_capacity, + tier_viewer_capacity, + data_alerts_enabled, + commenting_mentions_enabled, + catalog_obfuscation_enabled, + flow_auto_save_enabled, + web_extraction_enabled, + metrics_content_type_enabled, + notify_site_admins_on_throttle, + authoring_enabled, + custom_subscription_email_enabled, + custom_subscription_email, + custom_subscription_footer_enabled, + custom_subscription_footer, + ask_data_mode, + named_sharing_enabled, + mobile_biometrics_enabled, + sheet_image_enabled, + derived_permissions_enabled, + user_visibility_mode, + use_default_time_zone, + time_zone, + auto_suspend_refresh_enabled, + auto_suspend_refresh_inactivity_window, + ) = cls._parse_element(site_xml, ns) site_item = cls(name, content_url) - site_item._set_values(id, name, content_url, status_reason, admin_mode, state, subscribe_others_enabled, - disable_subscriptions, revision_history_enabled, user_quota, storage_quota, - revision_limit, num_users, storage, data_acceleration_mode, flows_enabled, - cataloging_enabled, editing_flows_enabled, scheduling_flows_enabled, - allow_subscription_attachments, guest_access_enabled, cache_warmup_enabled, - commenting_enabled, extract_encryption_mode, request_access_enabled, run_now_enabled, - tier_explorer_capacity, tier_creator_capacity, tier_viewer_capacity, - data_alerts_enabled, commenting_mentions_enabled, catalog_obfuscation_enabled, - flow_auto_save_enabled, web_extraction_enabled, metrics_content_type_enabled, - notify_site_admins_on_throttle, authoring_enabled, custom_subscription_email_enabled, - custom_subscription_email, custom_subscription_footer_enabled, - custom_subscription_footer, ask_data_mode, named_sharing_enabled, - mobile_biometrics_enabled, sheet_image_enabled, derived_permissions_enabled, - user_visibility_mode, use_default_time_zone, time_zone, auto_suspend_refresh_enabled, - auto_suspend_refresh_inactivity_window) + site_item._set_values( + id, + name, + content_url, + status_reason, + admin_mode, + state, + subscribe_others_enabled, + disable_subscriptions, + revision_history_enabled, + user_quota, + storage_quota, + revision_limit, + num_users, + storage, + data_acceleration_mode, + flows_enabled, + cataloging_enabled, + editing_flows_enabled, + scheduling_flows_enabled, + allow_subscription_attachments, + guest_access_enabled, + cache_warmup_enabled, + commenting_enabled, + extract_encryption_mode, + request_access_enabled, + run_now_enabled, + tier_explorer_capacity, + tier_creator_capacity, + tier_viewer_capacity, + data_alerts_enabled, + commenting_mentions_enabled, + catalog_obfuscation_enabled, + flow_auto_save_enabled, + web_extraction_enabled, + metrics_content_type_enabled, + notify_site_admins_on_throttle, + authoring_enabled, + custom_subscription_email_enabled, + custom_subscription_email, + custom_subscription_footer_enabled, + custom_subscription_footer, + ask_data_mode, + named_sharing_enabled, + mobile_biometrics_enabled, + sheet_image_enabled, + derived_permissions_enabled, + user_visibility_mode, + use_default_time_zone, + time_zone, + auto_suspend_refresh_enabled, + auto_suspend_refresh_inactivity_window, + ) all_site_items.append(site_item) return all_site_items @staticmethod def _parse_element(site_xml, ns): - id = site_xml.get('id', None) - name = site_xml.get('name', None) - content_url = site_xml.get('contentUrl', None) - status_reason = site_xml.get('statusReason', None) - admin_mode = site_xml.get('adminMode', None) - state = site_xml.get('state', None) - subscribe_others_enabled = string_to_bool(site_xml.get('subscribeOthersEnabled', '')) - disable_subscriptions = string_to_bool(site_xml.get('disableSubscriptions', '')) - revision_history_enabled = string_to_bool(site_xml.get('revisionHistoryEnabled', '')) - editing_flows_enabled = string_to_bool(site_xml.get('editingFlowsEnabled', '')) - scheduling_flows_enabled = string_to_bool(site_xml.get('schedulingFlowsEnabled', '')) - allow_subscription_attachments = string_to_bool(site_xml.get('allowSubscriptionAttachments', '')) - guest_access_enabled = string_to_bool(site_xml.get('guestAccessEnabled', '')) - cache_warmup_enabled = string_to_bool(site_xml.get('cacheWarmupEnabled', '')) - commenting_enabled = string_to_bool(site_xml.get('commentingEnabled', '')) - extract_encryption_mode = site_xml.get('extractEncryptionMode', None) - request_access_enabled = string_to_bool(site_xml.get('requestAccessEnabled', '')) - run_now_enabled = string_to_bool(site_xml.get('runNowEnabled', '')) - tier_explorer_capacity = site_xml.get('tierExplorerCapacity', None) + id = site_xml.get("id", None) + name = site_xml.get("name", None) + content_url = site_xml.get("contentUrl", None) + status_reason = site_xml.get("statusReason", None) + admin_mode = site_xml.get("adminMode", None) + state = site_xml.get("state", None) + subscribe_others_enabled = string_to_bool(site_xml.get("subscribeOthersEnabled", "")) + disable_subscriptions = string_to_bool(site_xml.get("disableSubscriptions", "")) + revision_history_enabled = string_to_bool(site_xml.get("revisionHistoryEnabled", "")) + editing_flows_enabled = string_to_bool(site_xml.get("editingFlowsEnabled", "")) + scheduling_flows_enabled = string_to_bool(site_xml.get("schedulingFlowsEnabled", "")) + allow_subscription_attachments = string_to_bool(site_xml.get("allowSubscriptionAttachments", "")) + guest_access_enabled = string_to_bool(site_xml.get("guestAccessEnabled", "")) + cache_warmup_enabled = string_to_bool(site_xml.get("cacheWarmupEnabled", "")) + commenting_enabled = string_to_bool(site_xml.get("commentingEnabled", "")) + extract_encryption_mode = site_xml.get("extractEncryptionMode", None) + request_access_enabled = string_to_bool(site_xml.get("requestAccessEnabled", "")) + run_now_enabled = string_to_bool(site_xml.get("runNowEnabled", "")) + tier_explorer_capacity = site_xml.get("tierExplorerCapacity", None) if tier_explorer_capacity: tier_explorer_capacity = int(tier_explorer_capacity) - tier_creator_capacity = site_xml.get('tierCreatorCapacity', None) + tier_creator_capacity = site_xml.get("tierCreatorCapacity", None) if tier_creator_capacity: tier_creator_capacity = int(tier_creator_capacity) - tier_viewer_capacity = site_xml.get('tierViewerCapacity', None) + tier_viewer_capacity = site_xml.get("tierViewerCapacity", None) if tier_viewer_capacity: tier_viewer_capacity = int(tier_viewer_capacity) - data_alerts_enabled = string_to_bool(site_xml.get('dataAlertsEnabled', '')) - commenting_mentions_enabled = string_to_bool(site_xml.get('commentingMentionsEnabled', '')) - catalog_obfuscation_enabled = string_to_bool(site_xml.get('catalogObfuscationEnabled', '')) - flow_auto_save_enabled = string_to_bool(site_xml.get('flowAutoSaveEnabled', '')) - web_extraction_enabled = string_to_bool(site_xml.get('webExtractionEnabled', '')) - metrics_content_type_enabled = string_to_bool(site_xml.get('metricsContentTypeEnabled', '')) - notify_site_admins_on_throttle = string_to_bool(site_xml.get('notifySiteAdminsOnThrottle', '')) - authoring_enabled = string_to_bool(site_xml.get('authoringEnabled', '')) - custom_subscription_email_enabled = string_to_bool(site_xml.get('customSubscriptionEmailEnabled', '')) - custom_subscription_email = site_xml.get('customSubscriptionEmail', None) - custom_subscription_footer_enabled = string_to_bool(site_xml.get('customSubscriptionFooterEnabled', '')) - custom_subscription_footer = site_xml.get('customSubscriptionFooter', None) - ask_data_mode = site_xml.get('askDataMode', None) - named_sharing_enabled = string_to_bool(site_xml.get('namedSharingEnabled', '')) - mobile_biometrics_enabled = string_to_bool(site_xml.get('mobileBiometricsEnabled', '')) - sheet_image_enabled = string_to_bool(site_xml.get('sheetImageEnabled', '')) - derived_permissions_enabled = string_to_bool(site_xml.get('derivedPermissionsEnabled', '')) - user_visibility_mode = site_xml.get('userVisibilityMode', '') - use_default_time_zone = string_to_bool(site_xml.get('useDefaultTimeZone', '')) - time_zone = site_xml.get('timeZone', None) - auto_suspend_refresh_enabled = string_to_bool(site_xml.get('autoSuspendRefreshEnabled', '')) - auto_suspend_refresh_inactivity_window = site_xml.get('autoSuspendRefreshInactivityWindow', None) + data_alerts_enabled = string_to_bool(site_xml.get("dataAlertsEnabled", "")) + commenting_mentions_enabled = string_to_bool(site_xml.get("commentingMentionsEnabled", "")) + catalog_obfuscation_enabled = string_to_bool(site_xml.get("catalogObfuscationEnabled", "")) + flow_auto_save_enabled = string_to_bool(site_xml.get("flowAutoSaveEnabled", "")) + web_extraction_enabled = string_to_bool(site_xml.get("webExtractionEnabled", "")) + metrics_content_type_enabled = string_to_bool(site_xml.get("metricsContentTypeEnabled", "")) + notify_site_admins_on_throttle = string_to_bool(site_xml.get("notifySiteAdminsOnThrottle", "")) + authoring_enabled = string_to_bool(site_xml.get("authoringEnabled", "")) + custom_subscription_email_enabled = string_to_bool(site_xml.get("customSubscriptionEmailEnabled", "")) + custom_subscription_email = site_xml.get("customSubscriptionEmail", None) + custom_subscription_footer_enabled = string_to_bool(site_xml.get("customSubscriptionFooterEnabled", "")) + custom_subscription_footer = site_xml.get("customSubscriptionFooter", None) + ask_data_mode = site_xml.get("askDataMode", None) + named_sharing_enabled = string_to_bool(site_xml.get("namedSharingEnabled", "")) + mobile_biometrics_enabled = string_to_bool(site_xml.get("mobileBiometricsEnabled", "")) + sheet_image_enabled = string_to_bool(site_xml.get("sheetImageEnabled", "")) + derived_permissions_enabled = string_to_bool(site_xml.get("derivedPermissionsEnabled", "")) + user_visibility_mode = site_xml.get("userVisibilityMode", "") + use_default_time_zone = string_to_bool(site_xml.get("useDefaultTimeZone", "")) + time_zone = site_xml.get("timeZone", None) + auto_suspend_refresh_enabled = string_to_bool(site_xml.get("autoSuspendRefreshEnabled", "")) + auto_suspend_refresh_inactivity_window = site_xml.get("autoSuspendRefreshInactivityWindow", None) if auto_suspend_refresh_inactivity_window: auto_suspend_refresh_inactivity_window = int(auto_suspend_refresh_inactivity_window) - user_quota = site_xml.get('userQuota', None) + user_quota = site_xml.get("userQuota", None) if user_quota: user_quota = int(user_quota) - storage_quota = site_xml.get('storageQuota', None) + storage_quota = site_xml.get("storageQuota", None) if storage_quota: storage_quota = int(storage_quota) - revision_limit = site_xml.get('revisionLimit', None) + revision_limit = site_xml.get("revisionLimit", None) if revision_limit: revision_limit = int(revision_limit) num_users = None storage = None - usage_elem = site_xml.find('.//t:usage', namespaces=ns) + usage_elem = site_xml.find(".//t:usage", namespaces=ns) if usage_elem is not None: - num_users = usage_elem.get('numUsers', None) - storage = usage_elem.get('storage', None) - - data_acceleration_mode = site_xml.get('dataAccelerationMode', '') - - flows_enabled = string_to_bool(site_xml.get('flowsEnabled', '')) - cataloging_enabled = string_to_bool(site_xml.get('catalogingEnabled', '')) - - return id, name, content_url, status_reason, admin_mode, state, subscribe_others_enabled,\ - disable_subscriptions, revision_history_enabled, user_quota, storage_quota,\ - revision_limit, num_users, storage, data_acceleration_mode, flows_enabled, cataloging_enabled,\ - editing_flows_enabled, scheduling_flows_enabled, allow_subscription_attachments, guest_access_enabled,\ - cache_warmup_enabled, commenting_enabled, extract_encryption_mode, request_access_enabled, run_now_enabled,\ - tier_explorer_capacity, tier_creator_capacity, tier_viewer_capacity, data_alerts_enabled,\ - commenting_mentions_enabled, catalog_obfuscation_enabled, flow_auto_save_enabled, web_extraction_enabled,\ - metrics_content_type_enabled, notify_site_admins_on_throttle, authoring_enabled,\ - custom_subscription_email_enabled, custom_subscription_email, custom_subscription_footer_enabled,\ - custom_subscription_footer, ask_data_mode, named_sharing_enabled, mobile_biometrics_enabled,\ - sheet_image_enabled, derived_permissions_enabled, user_visibility_mode, use_default_time_zone, time_zone,\ - auto_suspend_refresh_enabled, auto_suspend_refresh_inactivity_window + num_users = usage_elem.get("numUsers", None) + storage = usage_elem.get("storage", None) + + data_acceleration_mode = site_xml.get("dataAccelerationMode", "") + + flows_enabled = string_to_bool(site_xml.get("flowsEnabled", "")) + cataloging_enabled = string_to_bool(site_xml.get("catalogingEnabled", "")) + + return ( + id, + name, + content_url, + status_reason, + admin_mode, + state, + subscribe_others_enabled, + disable_subscriptions, + revision_history_enabled, + user_quota, + storage_quota, + revision_limit, + num_users, + storage, + data_acceleration_mode, + flows_enabled, + cataloging_enabled, + editing_flows_enabled, + scheduling_flows_enabled, + allow_subscription_attachments, + guest_access_enabled, + cache_warmup_enabled, + commenting_enabled, + extract_encryption_mode, + request_access_enabled, + run_now_enabled, + tier_explorer_capacity, + tier_creator_capacity, + tier_viewer_capacity, + data_alerts_enabled, + commenting_mentions_enabled, + catalog_obfuscation_enabled, + flow_auto_save_enabled, + web_extraction_enabled, + metrics_content_type_enabled, + notify_site_admins_on_throttle, + authoring_enabled, + custom_subscription_email_enabled, + custom_subscription_email, + custom_subscription_footer_enabled, + custom_subscription_footer, + ask_data_mode, + named_sharing_enabled, + mobile_biometrics_enabled, + sheet_image_enabled, + derived_permissions_enabled, + user_visibility_mode, + use_default_time_zone, + time_zone, + auto_suspend_refresh_enabled, + auto_suspend_refresh_inactivity_window, + ) # Used to convert string represented boolean to a boolean type def string_to_bool(s): - return s.lower() == 'true' + return s.lower() == "true" diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index cdcc468a1..c5ac10168 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -4,7 +4,6 @@ class SubscriptionItem(object): - def __init__(self, subject, schedule_id, user_id, target): self._id = None self.attach_image = True @@ -22,10 +21,14 @@ def __init__(self, subject, schedule_id, user_id, target): def __repr__(self): if self.id is not None: return "".format(**self.__dict__) + return ( + "".format(**self.__dict__) + ) @classmethod def from_response(cls, xml, ns, task_type=Type.ExtractRefresh): parsed_response = ET.fromstring(xml) - all_tasks_xml = parsed_response.findall( - './/t:task/t:{}'.format(task_type), namespaces=ns) + all_tasks_xml = parsed_response.findall(".//t:task/t:{}".format(task_type), namespaces=ns) all_tasks = (TaskItem._parse_element(x, ns) for x in all_tasks_xml) @@ -43,9 +52,9 @@ def _parse_element(cls, element, ns): schedule_item = None target = None last_run_at = None - workbook_element = element.find('.//t:workbook', namespaces=ns) - datasource_element = element.find('.//t:datasource', namespaces=ns) - last_run_at_element = element.find('.//t:lastRunAt', namespaces=ns) + workbook_element = element.find(".//t:workbook", namespaces=ns) + datasource_element = element.find(".//t:datasource", namespaces=ns) + last_run_at_element = element.find(".//t:lastRunAt", namespaces=ns) schedule_item_list = ScheduleItem.from_element(element, ns) if len(schedule_item_list) >= 1: @@ -54,22 +63,23 @@ def _parse_element(cls, element, ns): # according to the Tableau Server REST API documentation, # there should be only one of workbook or datasource if workbook_element is not None: - workbook_id = workbook_element.get('id', None) + workbook_id = workbook_element.get("id", None) target = Target(workbook_id, "workbook") if datasource_element is not None: - datasource_id = datasource_element.get('id', None) + datasource_id = datasource_element.get("id", None) target = Target(datasource_id, "datasource") if last_run_at_element is not None: last_run_at = parse_datetime(last_run_at_element.text) # Server response has different names for task types - task_type = cls._translate_task_type(element.get('type', None)) + task_type = cls._translate_task_type(element.get("type", None)) - priority = int(element.get('priority', -1)) - consecutive_failed_count = int(element.get('consecutiveFailedCount', 0)) - id_ = element.get('id', None) - return cls(id_, task_type, priority, consecutive_failed_count, schedule_item.id, - schedule_item, last_run_at, target) + priority = int(element.get("priority", -1)) + consecutive_failed_count = int(element.get("consecutiveFailedCount", 0)) + id_ = element.get("id", None) + return cls( + id_, task_type, priority, consecutive_failed_count, schedule_item.id, schedule_item, last_run_at, target + ) @staticmethod def _translate_task_type(task_type): diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index b5a05b0d1..b9796cbae 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -7,33 +7,33 @@ class UserItem(object): - tag_name = 'user' + tag_name = "user" class Roles: - Interactor = 'Interactor' - Publisher = 'Publisher' - ServerAdministrator = 'ServerAdministrator' - SiteAdministrator = 'SiteAdministrator' - Unlicensed = 'Unlicensed' - UnlicensedWithPublish = 'UnlicensedWithPublish' - Viewer = 'Viewer' - ViewerWithPublish = 'ViewerWithPublish' - Guest = 'Guest' - - Creator = 'Creator' - Explorer = 'Explorer' - ExplorerCanPublish = 'ExplorerCanPublish' - ReadOnly = 'ReadOnly' - SiteAdministratorCreator = 'SiteAdministratorCreator' - SiteAdministratorExplorer = 'SiteAdministratorExplorer' + Interactor = "Interactor" + Publisher = "Publisher" + ServerAdministrator = "ServerAdministrator" + SiteAdministrator = "SiteAdministrator" + Unlicensed = "Unlicensed" + UnlicensedWithPublish = "UnlicensedWithPublish" + Viewer = "Viewer" + ViewerWithPublish = "ViewerWithPublish" + Guest = "Guest" + + Creator = "Creator" + Explorer = "Explorer" + ExplorerCanPublish = "ExplorerCanPublish" + ReadOnly = "ReadOnly" + SiteAdministratorCreator = "SiteAdministratorCreator" + SiteAdministratorExplorer = "SiteAdministratorExplorer" # Online only - SupportUser = 'SupportUser' + SupportUser = "SupportUser" class Auth: - OpenID = 'OpenID' - SAML = 'SAML' - ServerDefault = 'ServerDefault' + OpenID = "OpenID" + SAML = "SAML" + ServerDefault = "ServerDefault" def __init__(self, name=None, site_role=None, auth_setting=None): self._auth_setting = None @@ -126,14 +126,15 @@ def _set_groups(self, groups): def _parse_common_tags(self, user_xml, ns): if not isinstance(user_xml, ET.Element): - user_xml = ET.fromstring(user_xml).find('.//t:user', namespaces=ns) + user_xml = ET.fromstring(user_xml).find(".//t:user", namespaces=ns) if user_xml is not None: (_, _, site_role, _, _, fullname, email, auth_setting, _) = self._parse_element(user_xml, ns) self._set_values(None, None, site_role, None, None, fullname, email, auth_setting, None) return self - def _set_values(self, id, name, site_role, last_login, - external_auth_user_id, fullname, email, auth_setting, domain_name): + def _set_values( + self, id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name + ): if id is not None: self._id = id if name: @@ -157,13 +158,23 @@ def _set_values(self, id, name, site_role, last_login, def from_response(cls, resp, ns): all_user_items = [] parsed_response = ET.fromstring(resp) - all_user_xml = parsed_response.findall('.//t:user', namespaces=ns) + all_user_xml = parsed_response.findall(".//t:user", namespaces=ns) for user_xml in all_user_xml: - (id, name, site_role, last_login, external_auth_user_id, - fullname, email, auth_setting, domain_name) = cls._parse_element(user_xml, ns) + ( + id, + name, + site_role, + last_login, + external_auth_user_id, + fullname, + email, + auth_setting, + domain_name, + ) = cls._parse_element(user_xml, ns) user_item = cls(name, site_role) - user_item._set_values(id, name, site_role, last_login, external_auth_user_id, - fullname, email, auth_setting, domain_name) + user_item._set_values( + id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name + ) all_user_items.append(user_item) return all_user_items @@ -173,19 +184,19 @@ def as_reference(id_): @staticmethod def _parse_element(user_xml, ns): - id = user_xml.get('id', None) - name = user_xml.get('name', None) - site_role = user_xml.get('siteRole', None) - last_login = parse_datetime(user_xml.get('lastLogin', None)) - external_auth_user_id = user_xml.get('externalAuthUserId', None) - fullname = user_xml.get('fullName', None) - email = user_xml.get('email', None) - auth_setting = user_xml.get('authSetting', None) + id = user_xml.get("id", None) + name = user_xml.get("name", None) + site_role = user_xml.get("siteRole", None) + last_login = parse_datetime(user_xml.get("lastLogin", None)) + external_auth_user_id = user_xml.get("externalAuthUserId", None) + fullname = user_xml.get("fullName", None) + email = user_xml.get("email", None) + auth_setting = user_xml.get("authSetting", None) domain_name = None - domain_elem = user_xml.find('.//t:domain', namespaces=ns) + domain_elem = user_xml.find(".//t:domain", namespaces=ns) if domain_elem is not None: - domain_name = domain_elem.get('name', None) + domain_name = domain_elem.get("name", None) return id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name diff --git a/tableauserverclient/models/view_item.py b/tableauserverclient/models/view_item.py index f9b7a2940..f18acfc33 100644 --- a/tableauserverclient/models/view_item.py +++ b/tableauserverclient/models/view_item.py @@ -119,42 +119,42 @@ def _set_permissions(self, permissions): self._permissions = permissions @classmethod - def from_response(cls, resp, ns, workbook_id=''): + def from_response(cls, resp, ns, workbook_id=""): return cls.from_xml_element(ET.fromstring(resp), ns, workbook_id) @classmethod - def from_xml_element(cls, parsed_response, ns, workbook_id=''): + def from_xml_element(cls, parsed_response, ns, workbook_id=""): all_view_items = list() - all_view_xml = parsed_response.findall('.//t:view', namespaces=ns) + all_view_xml = parsed_response.findall(".//t:view", namespaces=ns) for view_xml in all_view_xml: view_item = cls() - usage_elem = view_xml.find('.//t:usage', namespaces=ns) - workbook_elem = view_xml.find('.//t:workbook', namespaces=ns) - owner_elem = view_xml.find('.//t:owner', namespaces=ns) - project_elem = view_xml.find('.//t:project', namespaces=ns) - tags_elem = view_xml.find('.//t:tags', namespaces=ns) - view_item._created_at = parse_datetime(view_xml.get('createdAt', None)) - view_item._updated_at = parse_datetime(view_xml.get('updatedAt', None)) - view_item._id = view_xml.get('id', None) - view_item._name = view_xml.get('name', None) - view_item._content_url = view_xml.get('contentUrl', None) - view_item._sheet_type = view_xml.get('sheetType', None) + usage_elem = view_xml.find(".//t:usage", namespaces=ns) + workbook_elem = view_xml.find(".//t:workbook", namespaces=ns) + owner_elem = view_xml.find(".//t:owner", namespaces=ns) + project_elem = view_xml.find(".//t:project", namespaces=ns) + tags_elem = view_xml.find(".//t:tags", namespaces=ns) + view_item._created_at = parse_datetime(view_xml.get("createdAt", None)) + view_item._updated_at = parse_datetime(view_xml.get("updatedAt", None)) + view_item._id = view_xml.get("id", None) + view_item._name = view_xml.get("name", None) + view_item._content_url = view_xml.get("contentUrl", None) + view_item._sheet_type = view_xml.get("sheetType", None) if usage_elem is not None: - total_view = usage_elem.get('totalViewCount', None) + total_view = usage_elem.get("totalViewCount", None) if total_view: view_item._total_views = int(total_view) if owner_elem is not None: - view_item._owner_id = owner_elem.get('id', None) + view_item._owner_id = owner_elem.get("id", None) if project_elem is not None: - view_item._project_id = project_elem.get('id', None) + view_item._project_id = project_elem.get("id", None) if workbook_id: view_item._workbook_id = workbook_id elif workbook_elem is not None: - view_item._workbook_id = workbook_elem.get('id', None) + view_item._workbook_id = workbook_elem.get("id", None) if tags_elem is not None: tags = TagItem.from_xml_element(tags_elem, ns) diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py index 57bcfeaa4..5fc5c5749 100644 --- a/tableauserverclient/models/webhook_item.py +++ b/tableauserverclient/models/webhook_item.py @@ -3,13 +3,13 @@ import re -NAMESPACE_RE = re.compile(r'^{.*}') +NAMESPACE_RE = re.compile(r"^{.*}") def _parse_event(events): event = events[0] # Strip out the namespace from the tag name - return NAMESPACE_RE.sub('', event.tag) + return NAMESPACE_RE.sub("", event.tag) class WebhookItem(object): @@ -50,7 +50,7 @@ def event(self, value): def from_response(cls, resp, ns): all_webhooks_items = list() parsed_response = ET.fromstring(resp) - all_webhooks_xml = parsed_response.findall('.//t:webhook', namespaces=ns) + all_webhooks_xml = parsed_response.findall(".//t:webhook", namespaces=ns) for webhook_xml in all_webhooks_xml: values = cls._parse_element(webhook_xml, ns) @@ -61,25 +61,24 @@ def from_response(cls, resp, ns): @staticmethod def _parse_element(webhook_xml, ns): - id = webhook_xml.get('id', None) - name = webhook_xml.get('name', None) + id = webhook_xml.get("id", None) + name = webhook_xml.get("name", None) url = None - url_tag = webhook_xml.find('.//t:webhook-destination-http', namespaces=ns) + url_tag = webhook_xml.find(".//t:webhook-destination-http", namespaces=ns) if url_tag is not None: - url = url_tag.get('url', None) + url = url_tag.get("url", None) - event = webhook_xml.findall('.//t:webhook-source/*', namespaces=ns) + event = webhook_xml.findall(".//t:webhook-source/*", namespaces=ns) if event is not None and len(event) > 0: event = _parse_event(event) owner_id = None - owner_tag = webhook_xml.find('.//t:owner', namespaces=ns) + owner_tag = webhook_xml.find(".//t:owner", namespaces=ns) if owner_tag is not None: - owner_id = owner_tag.get('id', None) + owner_id = owner_tag.get("id", None) return id, name, url, event, owner_id def __repr__(self): - return "".format( - self.id, self.name, self.url, self.event) + return "".format(self.id, self.name, self.url, self.event) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 3a3ddcdf9..20597364e 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -28,10 +28,12 @@ def __init__(self, project_id, name=None, show_tabs=False): self.project_id = project_id self.show_tabs = show_tabs self.tags = set() - self.data_acceleration_config = {'acceleration_enabled': None, - 'accelerate_now': None, - 'last_updated_at': None, - 'acceleration_status': None} + self.data_acceleration_config = { + "acceleration_enabled": None, + "accelerate_now": None, + "last_updated_at": None, + "acceleration_status": None, + } self._permissions = None @property @@ -155,21 +157,64 @@ def _set_preview_image(self, preview_image): def _parse_common_tags(self, workbook_xml, ns): if not isinstance(workbook_xml, ET.Element): - workbook_xml = ET.fromstring(workbook_xml).find('.//t:workbook', namespaces=ns) + workbook_xml = ET.fromstring(workbook_xml).find(".//t:workbook", namespaces=ns) if workbook_xml is not None: - (_, _, _, _, _, description, updated_at, _, show_tabs, - project_id, project_name, owner_id, _, _, - data_acceleration_config) = self._parse_element(workbook_xml, ns) - - self._set_values(None, None, None, None, None, description, updated_at, - None, show_tabs, project_id, project_name, owner_id, None, None, - data_acceleration_config) + ( + _, + _, + _, + _, + _, + description, + updated_at, + _, + show_tabs, + project_id, + project_name, + owner_id, + _, + _, + data_acceleration_config, + ) = self._parse_element(workbook_xml, ns) + + self._set_values( + None, + None, + None, + None, + None, + description, + updated_at, + None, + show_tabs, + project_id, + project_name, + owner_id, + None, + None, + data_acceleration_config, + ) return self - def _set_values(self, id, name, content_url, webpage_url, created_at, description, updated_at, - size, show_tabs, project_id, project_name, owner_id, tags, views, - data_acceleration_config): + def _set_values( + self, + id, + name, + content_url, + webpage_url, + created_at, + description, + updated_at, + size, + show_tabs, + project_id, + project_name, + owner_id, + tags, + views, + data_acceleration_config, + ): if id is not None: self._id = id if name: @@ -206,92 +251,139 @@ def _set_values(self, id, name, content_url, webpage_url, created_at, descriptio def from_response(cls, resp, ns): all_workbook_items = list() parsed_response = ET.fromstring(resp) - all_workbook_xml = parsed_response.findall('.//t:workbook', namespaces=ns) + all_workbook_xml = parsed_response.findall(".//t:workbook", namespaces=ns) for workbook_xml in all_workbook_xml: - (id, name, content_url, webpage_url, created_at, description, updated_at, size, show_tabs, - project_id, project_name, owner_id, tags, views, - data_acceleration_config) = cls._parse_element(workbook_xml, ns) + ( + id, + name, + content_url, + webpage_url, + created_at, + description, + updated_at, + size, + show_tabs, + project_id, + project_name, + owner_id, + tags, + views, + data_acceleration_config, + ) = cls._parse_element(workbook_xml, ns) workbook_item = cls(project_id) - workbook_item._set_values(id, name, content_url, webpage_url, created_at, description, updated_at, - size, show_tabs, None, project_name, owner_id, tags, views, - data_acceleration_config) + workbook_item._set_values( + id, + name, + content_url, + webpage_url, + created_at, + description, + updated_at, + size, + show_tabs, + None, + project_name, + owner_id, + tags, + views, + data_acceleration_config, + ) all_workbook_items.append(workbook_item) return all_workbook_items @staticmethod def _parse_element(workbook_xml, ns): - id = workbook_xml.get('id', None) - name = workbook_xml.get('name', None) - content_url = workbook_xml.get('contentUrl', None) - webpage_url = workbook_xml.get('webpageUrl', None) - created_at = parse_datetime(workbook_xml.get('createdAt', None)) - description = workbook_xml.get('description', None) - updated_at = parse_datetime(workbook_xml.get('updatedAt', None)) - - size = workbook_xml.get('size', None) + id = workbook_xml.get("id", None) + name = workbook_xml.get("name", None) + content_url = workbook_xml.get("contentUrl", None) + webpage_url = workbook_xml.get("webpageUrl", None) + created_at = parse_datetime(workbook_xml.get("createdAt", None)) + description = workbook_xml.get("description", None) + updated_at = parse_datetime(workbook_xml.get("updatedAt", None)) + + size = workbook_xml.get("size", None) if size: size = int(size) - show_tabs = string_to_bool(workbook_xml.get('showTabs', '')) + show_tabs = string_to_bool(workbook_xml.get("showTabs", "")) project_id = None project_name = None - project_tag = workbook_xml.find('.//t:project', namespaces=ns) + project_tag = workbook_xml.find(".//t:project", namespaces=ns) if project_tag is not None: - project_id = project_tag.get('id', None) - project_name = project_tag.get('name', None) + project_id = project_tag.get("id", None) + project_name = project_tag.get("name", None) owner_id = None - owner_tag = workbook_xml.find('.//t:owner', namespaces=ns) + owner_tag = workbook_xml.find(".//t:owner", namespaces=ns) if owner_tag is not None: - owner_id = owner_tag.get('id', None) + owner_id = owner_tag.get("id", None) tags = None - tags_elem = workbook_xml.find('.//t:tags', namespaces=ns) + tags_elem = workbook_xml.find(".//t:tags", namespaces=ns) if tags_elem is not None: all_tags = TagItem.from_xml_element(tags_elem, ns) tags = all_tags views = None - views_elem = workbook_xml.find('.//t:views', namespaces=ns) + views_elem = workbook_xml.find(".//t:views", namespaces=ns) if views_elem is not None: views = ViewItem.from_xml_element(views_elem, ns) - data_acceleration_config = {'acceleration_enabled': None, 'accelerate_now': None, - 'last_updated_at': None, 'acceleration_status': None} - data_acceleration_elem = workbook_xml.find('.//t:dataAccelerationConfig', namespaces=ns) + data_acceleration_config = { + "acceleration_enabled": None, + "accelerate_now": None, + "last_updated_at": None, + "acceleration_status": None, + } + data_acceleration_elem = workbook_xml.find(".//t:dataAccelerationConfig", namespaces=ns) if data_acceleration_elem is not None: data_acceleration_config = parse_data_acceleration_config(data_acceleration_elem) - return id, name, content_url, webpage_url, created_at, description, updated_at, size, show_tabs, \ - project_id, project_name, owner_id, tags, views, data_acceleration_config + return ( + id, + name, + content_url, + webpage_url, + created_at, + description, + updated_at, + size, + show_tabs, + project_id, + project_name, + owner_id, + tags, + views, + data_acceleration_config, + ) def parse_data_acceleration_config(data_acceleration_elem): data_acceleration_config = dict() - acceleration_enabled = data_acceleration_elem.get('accelerationEnabled', None) + acceleration_enabled = data_acceleration_elem.get("accelerationEnabled", None) if acceleration_enabled is not None: acceleration_enabled = string_to_bool(acceleration_enabled) - accelerate_now = data_acceleration_elem.get('accelerateNow', None) + accelerate_now = data_acceleration_elem.get("accelerateNow", None) if accelerate_now is not None: accelerate_now = string_to_bool(accelerate_now) - last_updated_at = data_acceleration_elem.get('lastUpdatedAt', None) + last_updated_at = data_acceleration_elem.get("lastUpdatedAt", None) if last_updated_at is not None: last_updated_at = parse_datetime(last_updated_at) - acceleration_status = data_acceleration_elem.get('accelerationStatus', None) + acceleration_status = data_acceleration_elem.get("accelerationStatus", None) - data_acceleration_config['acceleration_enabled'] = acceleration_enabled - data_acceleration_config['accelerate_now'] = accelerate_now - data_acceleration_config['last_updated_at'] = last_updated_at - data_acceleration_config['acceleration_status'] = acceleration_status + data_acceleration_config["acceleration_enabled"] = acceleration_enabled + data_acceleration_config["accelerate_now"] = accelerate_now + data_acceleration_config["last_updated_at"] = last_updated_at + data_acceleration_config["acceleration_status"] = acceleration_status return data_acceleration_config # Used to convert string represented boolean to a boolean type def string_to_bool(s): - return s.lower() == 'true' + return s.lower() == "true" diff --git a/tableauserverclient/namespace.py b/tableauserverclient/namespace.py index 43717dce2..986a02fb3 100644 --- a/tableauserverclient/namespace.py +++ b/tableauserverclient/namespace.py @@ -1,9 +1,9 @@ from xml.etree import ElementTree as ET import re -OLD_NAMESPACE = 'http://tableausoftware.com/api' -NEW_NAMESPACE = 'http://tableau.com/api' -NAMESPACE_RE = re.compile(r'\{(.*?)\}') +OLD_NAMESPACE = "http://tableausoftware.com/api" +NEW_NAMESPACE = "http://tableau.com/api" +NAMESPACE_RE = re.compile(r"\{(.*?)\}") class UnknownNamespaceError(Exception): @@ -12,7 +12,7 @@ class UnknownNamespaceError(Exception): class Namespace(object): def __init__(self): - self._namespace = {'t': NEW_NAMESPACE} + self._namespace = {"t": NEW_NAMESPACE} self._detected = False def __call__(self): @@ -22,7 +22,7 @@ def detect(self, xml): if self._detected: return - if not xml.startswith(b'= FILESIZE_LIMIT: - logger.info('Publishing {0} to server with chunking method (datasource over 64MB)'.format(filename)) + logger.info("Publishing {0} to server with chunking method (datasource over 64MB)".format(filename)) upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) - xml_request, content_type = RequestFactory.Datasource.publish_req_chunked(datasource_item, - connection_credentials, - connections) + xml_request, content_type = RequestFactory.Datasource.publish_req_chunked( + datasource_item, connection_credentials, connections + ) else: - logger.info('Publishing {0} to server'.format(filename)) + logger.info("Publishing {0} to server".format(filename)) try: - with open(file, 'rb') as f: + with open(file, "rb") as f: file_contents = f.read() except TypeError: file_contents = file.read() - xml_request, content_type = RequestFactory.Datasource.publish_req(datasource_item, - filename, - file_contents, - connection_credentials, - connections) + xml_request, content_type = RequestFactory.Datasource.publish_req( + datasource_item, filename, file_contents, connection_credentials, connections + ) # Send the publishing request to server try: @@ -255,33 +254,36 @@ def publish(self, datasource_item, file, mode, connection_credentials=None, conn if as_job: new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (JOB_ID: {1}'.format(filename, new_job.id)) + logger.info("Published {0} (JOB_ID: {1}".format(filename, new_job.id)) return new_job else: new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (ID: {1})'.format(filename, new_datasource.id)) + logger.info("Published {0} (ID: {1})".format(filename, new_datasource.id)) return new_datasource server_response = self.post_request(url, xml_request, content_type) new_datasource = DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (ID: {1})'.format(filename, new_datasource.id)) + logger.info("Published {0} (ID: {1})".format(filename, new_datasource.id)) return new_datasource - @api(version='2.0') + @api(version="2.0") def populate_permissions(self, item): self._permissions.populate(item) - @api(version='2.0') + @api(version="2.0") def update_permission(self, item, permission_item): import warnings - warnings.warn('Server.datasources.update_permission is deprecated, ' - 'please use Server.datasources.update_permissions instead.', - DeprecationWarning) + + warnings.warn( + "Server.datasources.update_permission is deprecated, " + "please use Server.datasources.update_permissions instead.", + DeprecationWarning, + ) self._permissions.update(item, permission_item) - @api(version='2.0') + @api(version="2.0") def update_permissions(self, item, permission_item): self._permissions.update(item, permission_item) - @api(version='2.0') + @api(version="2.0") def delete_permission(self, item, capability_item): self._permissions.delete(item, capability_item) diff --git a/tableauserverclient/server/endpoint/default_permissions_endpoint.py b/tableauserverclient/server/endpoint/default_permissions_endpoint.py index d435a03d6..1cfa41733 100644 --- a/tableauserverclient/server/endpoint/default_permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/default_permissions_endpoint.py @@ -11,7 +11,7 @@ class _DefaultPermissionsEndpoint(Endpoint): - """ Adds default-permission model to another endpoint + """Adds default-permission model to another endpoint Tableau default-permissions model applies only to databases and projects and then takes an object type in the uri to set the defaults. @@ -28,38 +28,37 @@ def __init__(self, parent_srv, owner_baseurl): self.owner_baseurl = owner_baseurl def update_default_permissions(self, resource, permissions, content_type): - url = '{0}/{1}/default-permissions/{2}'.format(self.owner_baseurl(), resource.id, content_type + 's') + url = "{0}/{1}/default-permissions/{2}".format(self.owner_baseurl(), resource.id, content_type + "s") update_req = RequestFactory.Permission.add_req(permissions) response = self.put_request(url, update_req) - permissions = PermissionsRule.from_response(response.content, - self.parent_srv.namespace) - logger.info('Updated permissions for resource {0}'.format(resource.id)) + permissions = PermissionsRule.from_response(response.content, self.parent_srv.namespace) + logger.info("Updated permissions for resource {0}".format(resource.id)) return permissions def delete_default_permission(self, resource, rule, content_type): for capability, mode in rule.capabilities.items(): # Made readability better but line is too long, will make this look better - url = '{baseurl}/{content_id}/default-permissions/' \ - '{content_type}/{grantee_type}/{grantee_id}/{cap}/{mode}' \ - .format( + url = ( + "{baseurl}/{content_id}/default-permissions/" + "{content_type}/{grantee_type}/{grantee_id}/{cap}/{mode}".format( baseurl=self.owner_baseurl(), content_id=resource.id, - content_type=content_type + 's', - grantee_type=rule.grantee.tag_name + 's', + content_type=content_type + "s", + grantee_type=rule.grantee.tag_name + "s", grantee_id=rule.grantee.id, cap=capability, - mode=mode) + mode=mode, + ) + ) - logger.debug('Removing {0} permission for capabilty {1}'.format( - mode, capability)) + logger.debug("Removing {0} permission for capabilty {1}".format(mode, capability)) self.delete_request(url) - logger.info('Deleted permission for {0} {1} item {2}'.format( - rule.grantee.tag_name, - rule.grantee.id, - resource.id)) + logger.info( + "Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id) + ) def populate_default_permissions(self, item, content_type): if not item.id: @@ -70,12 +69,11 @@ def permission_fetcher(): return self._get_default_permissions(item, content_type) item._set_default_permissions(permission_fetcher, content_type) - logger.info('Populated {0} permissions for item (ID: {1})'.format(item.id, content_type)) + logger.info("Populated {0} permissions for item (ID: {1})".format(item.id, content_type)) def _get_default_permissions(self, item, content_type, req_options=None): url = "{0}/{1}/default-permissions/{2}".format(self.owner_baseurl(), item.id, content_type + "s") server_response = self.get_request(url, req_options) - permissions = PermissionsRule.from_response(server_response.content, - self.parent_srv.namespace) + permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace) return permissions diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index dc504242a..92a20b21a 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -9,7 +9,7 @@ except ImportError: from distutils.version import LooseVersion as Version -logger = logging.getLogger('tableau.endpoint') +logger = logging.getLogger("tableau.endpoint") Success_codes = [200, 201, 202, 204] @@ -22,9 +22,9 @@ def __init__(self, parent_srv): def _make_common_headers(auth_token, content_type): headers = {} if auth_token is not None: - headers['x-tableau-auth'] = auth_token + headers["x-tableau-auth"] = auth_token if content_type is not None: - headers['content-type'] = content_type + headers["content-type"] = content_type return headers @@ -33,24 +33,23 @@ def _safe_to_log(server_response): """Checks if the server_response content is not xml (eg binary image or zip) and replaces it with a constant """ - ALLOWED_CONTENT_TYPES = ('application/xml', 'application/xml;charset=utf-8') - if server_response.headers.get('Content-Type', None) not in ALLOWED_CONTENT_TYPES: - return '[Truncated File Contents]' + ALLOWED_CONTENT_TYPES = ("application/xml", "application/xml;charset=utf-8") + if server_response.headers.get("Content-Type", None) not in ALLOWED_CONTENT_TYPES: + return "[Truncated File Contents]" else: return server_response.content - def _make_request(self, method, url, content=None, auth_token=None, - content_type=None, parameters=None): + def _make_request(self, method, url, content=None, auth_token=None, content_type=None, parameters=None): parameters = parameters or {} parameters.update(self.parent_srv.http_options) - parameters['headers'] = Endpoint._make_common_headers(auth_token, content_type) + parameters["headers"] = Endpoint._make_common_headers(auth_token, content_type) if content is not None: - parameters['data'] = content + parameters["data"] = content - logger.debug(u'request {}, url: {}'.format(method.__name__, url)) + logger.debug(u"request {}, url: {}".format(method.__name__, url)) if content: - logger.debug(u'request content: {}'.format(content[:1000])) + logger.debug(u"request content: {}".format(content[:1000])) server_response = method(url, **parameters) self.parent_srv._namespace.detect(server_response.content) @@ -59,8 +58,11 @@ def _make_request(self, method, url, content=None, auth_token=None, # This check is to determine if the response is a text response (xml or otherwise) # so that we do not attempt to log bytes and other binary data. if len(server_response.content) > 0 and server_response.encoding: - logger.debug(u'Server response from {0}:\n\t{1}'.format( - url, server_response.content.decode(server_response.encoding))) + logger.debug( + u"Server response from {0}:\n\t{1}".format( + url, server_response.content.decode(server_response.encoding) + ) + ) return server_response def _check_status(self, server_response): @@ -92,25 +94,31 @@ def get_request(self, url, request_object=None, parameters=None): except EndpointUnavailableError: url = request_object.apply_query_params(url) - return self._make_request(self.parent_srv.session.get, url, - auth_token=self.parent_srv.auth_token, - parameters=parameters) + return self._make_request( + self.parent_srv.session.get, url, auth_token=self.parent_srv.auth_token, parameters=parameters + ) def delete_request(self, url): # We don't return anything for a delete self._make_request(self.parent_srv.session.delete, url, auth_token=self.parent_srv.auth_token) - def put_request(self, url, xml_request=None, content_type='text/xml'): - return self._make_request(self.parent_srv.session.put, url, - content=xml_request, - auth_token=self.parent_srv.auth_token, - content_type=content_type) - - def post_request(self, url, xml_request, content_type='text/xml'): - return self._make_request(self.parent_srv.session.post, url, - content=xml_request, - auth_token=self.parent_srv.auth_token, - content_type=content_type) + def put_request(self, url, xml_request=None, content_type="text/xml"): + return self._make_request( + self.parent_srv.session.put, + url, + content=xml_request, + auth_token=self.parent_srv.auth_token, + content_type=content_type, + ) + + def post_request(self, url, xml_request, content_type="text/xml"): + return self._make_request( + self.parent_srv.session.post, + url, + content=xml_request, + auth_token=self.parent_srv.auth_token, + content_type=content_type, + ) def api(version): @@ -131,12 +139,15 @@ def api(version): >>> def get(self, req_options=None): >>> ... """ + def _decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): self.parent_srv.assert_at_least_version(version) return func(self, *args, **kwargs) + return wrapper + return _decorator @@ -162,10 +173,12 @@ def parameter_added_in(**params): >>> def download(self, workbook_id, filepath=None, extract_only=False): >>> ... """ + def _decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): import warnings + server_ver = Version(self.parent_srv.version or "0.0") params_to_check = set(params) & set(kwargs) for p in params_to_check: @@ -174,7 +187,9 @@ def wrapper(self, *args, **kwargs): error = "{!r} not available in {}, it will be ignored. Added in {}".format(p, server_ver, min_ver) warnings.warn(error) return func(self, *args, **kwargs) + return wrapper + return _decorator diff --git a/tableauserverclient/server/endpoint/exceptions.py b/tableauserverclient/server/endpoint/exceptions.py index 3c9226f0f..9a9a81d77 100644 --- a/tableauserverclient/server/endpoint/exceptions.py +++ b/tableauserverclient/server/endpoint/exceptions.py @@ -15,9 +15,11 @@ def __str__(self): def from_response(cls, resp, ns): # Check elements exist before .text parsed_response = ET.fromstring(resp) - error_response = cls(parsed_response.find('t:error', namespaces=ns).get('code', ''), - parsed_response.find('.//t:summary', namespaces=ns).text, - parsed_response.find('.//t:detail', namespaces=ns).text) + error_response = cls( + parsed_response.find("t:error", namespaces=ns).get("code", ""), + parsed_response.find(".//t:summary", namespaces=ns).text, + parsed_response.find(".//t:detail", namespaces=ns).text, + ) return error_response @@ -60,4 +62,5 @@ def __init__(self, error_payload): def __str__(self): from pprint import pformat + return pformat(self.error) diff --git a/tableauserverclient/server/endpoint/favorites_endpoint.py b/tableauserverclient/server/endpoint/favorites_endpoint.py index b1a90ba00..459d852e6 100644 --- a/tableauserverclient/server/endpoint/favorites_endpoint.py +++ b/tableauserverclient/server/endpoint/favorites_endpoint.py @@ -7,7 +7,7 @@ import logging import copy -logger = logging.getLogger('tableau.endpoint.favorites') +logger = logging.getLogger("tableau.endpoint.favorites") class Favorites(Endpoint): @@ -18,60 +18,60 @@ def baseurl(self): # Gets all favorites @api(version="2.5") def get(self, user_item, req_options=None): - logger.info('Querying all favorites for user {0}'.format(user_item.name)) - url = '{0}/{1}'.format(self.baseurl, user_item.id) + logger.info("Querying all favorites for user {0}".format(user_item.name)) + url = "{0}/{1}".format(self.baseurl, user_item.id) server_response = self.get_request(url, req_options) user_item._favorites = FavoriteItem.from_response(server_response.content, self.parent_srv.namespace) @api(version="2.0") def add_favorite_workbook(self, user_item, workbook_item): - url = '{0}/{1}'.format(self.baseurl, user_item.id) + url = "{0}/{1}".format(self.baseurl, user_item.id) add_req = RequestFactory.Favorite.add_workbook_req(workbook_item.id, workbook_item.name) server_response = self.put_request(url, add_req) - logger.info('Favorited {0} for user (ID: {1})'.format(workbook_item.name, user_item.id)) + logger.info("Favorited {0} for user (ID: {1})".format(workbook_item.name, user_item.id)) @api(version="2.0") def add_favorite_view(self, user_item, view_item): - url = '{0}/{1}'.format(self.baseurl, user_item.id) + url = "{0}/{1}".format(self.baseurl, user_item.id) add_req = RequestFactory.Favorite.add_view_req(view_item.id, view_item.name) server_response = self.put_request(url, add_req) - logger.info('Favorited {0} for user (ID: {1})'.format(view_item.name, user_item.id)) + logger.info("Favorited {0} for user (ID: {1})".format(view_item.name, user_item.id)) @api(version="2.3") def add_favorite_datasource(self, user_item, datasource_item): - url = '{0}/{1}'.format(self.baseurl, user_item.id) + url = "{0}/{1}".format(self.baseurl, user_item.id) add_req = RequestFactory.Favorite.add_datasource_req(datasource_item.id, datasource_item.name) server_response = self.put_request(url, add_req) - logger.info('Favorited {0} for user (ID: {1})'.format(datasource_item.name, user_item.id)) + logger.info("Favorited {0} for user (ID: {1})".format(datasource_item.name, user_item.id)) @api(version="3.1") def add_favorite_project(self, user_item, project_item): - url = '{0}/{1}'.format(self.baseurl, user_item.id) + url = "{0}/{1}".format(self.baseurl, user_item.id) add_req = RequestFactory.Favorite.add_project_req(project_item.id, project_item.name) server_response = self.put_request(url, add_req) - logger.info('Favorited {0} for user (ID: {1})'.format(project_item.name, user_item.id)) + logger.info("Favorited {0} for user (ID: {1})".format(project_item.name, user_item.id)) @api(version="2.0") def delete_favorite_workbook(self, user_item, workbook_item): - url = '{0}/{1}/workbooks/{2}'.format(self.baseurl, user_item.id, workbook_item.id) - logger.info('Removing favorite {0} for user (ID: {1})'.format(workbook_item.id, user_item.id)) + url = "{0}/{1}/workbooks/{2}".format(self.baseurl, user_item.id, workbook_item.id) + logger.info("Removing favorite {0} for user (ID: {1})".format(workbook_item.id, user_item.id)) self.delete_request(url) @api(version="2.0") def delete_favorite_view(self, user_item, view_item): - url = '{0}/{1}/views/{2}'.format(self.baseurl, user_item.id, view_item.id) - logger.info('Removing favorite {0} for user (ID: {1})'.format(view_item.id, user_item.id)) + url = "{0}/{1}/views/{2}".format(self.baseurl, user_item.id, view_item.id) + logger.info("Removing favorite {0} for user (ID: {1})".format(view_item.id, user_item.id)) self.delete_request(url) @api(version="2.3") def delete_favorite_datasource(self, user_item, datasource_item): - url = '{0}/{1}/datasources/{2}'.format(self.baseurl, user_item.id, datasource_item.id) - logger.info('Removing favorite {0} for user (ID: {1})'.format(datasource_item.id, user_item.id)) + url = "{0}/{1}/datasources/{2}".format(self.baseurl, user_item.id, datasource_item.id) + logger.info("Removing favorite {0} for user (ID: {1})".format(datasource_item.id, user_item.id)) self.delete_request(url) @api(version="3.1") def delete_favorite_project(self, user_item, project_item): - url = '{0}/{1}/projects/{2}'.format(self.baseurl, user_item.id, project_item.id) - logger.info('Removing favorite {0} for user (ID: {1})'.format(project_item.id, user_item.id)) + url = "{0}/{1}/projects/{2}".format(self.baseurl, user_item.id, project_item.id) + logger.info("Removing favorite {0} for user (ID: {1})".format(project_item.id, user_item.id)) self.delete_request(url) diff --git a/tableauserverclient/server/endpoint/fileuploads_endpoint.py b/tableauserverclient/server/endpoint/fileuploads_endpoint.py index c89a595d4..05a3ce17c 100644 --- a/tableauserverclient/server/endpoint/fileuploads_endpoint.py +++ b/tableauserverclient/server/endpoint/fileuploads_endpoint.py @@ -8,13 +8,13 @@ # For when a datasource is over 64MB, break it into 5MB(standard chunk size) chunks CHUNK_SIZE = 1024 * 1024 * 5 # 5MB -logger = logging.getLogger('tableau.endpoint.fileuploads') +logger = logging.getLogger("tableau.endpoint.fileuploads") class Fileuploads(Endpoint): def __init__(self, parent_srv): super(Fileuploads, self).__init__(parent_srv) - self.upload_id = '' + self.upload_id = "" @property def baseurl(self): @@ -23,10 +23,10 @@ def baseurl(self): @api(version="2.0") def initiate(self): url = self.baseurl - server_response = self.post_request(url, '') + server_response = self.post_request(url, "") fileupload_item = FileuploadItem.from_response(server_response.content, self.parent_srv.namespace) self.upload_id = fileupload_item.upload_session_id - logger.info('Initiated file upload session (ID: {0})'.format(self.upload_id)) + logger.info("Initiated file upload session (ID: {0})".format(self.upload_id)) return self.upload_id @api(version="2.0") @@ -36,13 +36,13 @@ def append(self, xml_request, content_type): raise MissingRequiredFieldError(error) url = "{0}/{1}".format(self.baseurl, self.upload_id) server_response = self.put_request(url, xml_request, content_type) - logger.info('Uploading a chunk to session (ID: {0})'.format(self.upload_id)) + logger.info("Uploading a chunk to session (ID: {0})".format(self.upload_id)) return FileuploadItem.from_response(server_response.content, self.parent_srv.namespace) def read_chunks(self, file): file_opened = False try: - file_content = open(file, 'rb') + file_content = open(file, "rb") file_opened = True except TypeError: file_content = file diff --git a/tableauserverclient/server/endpoint/flows_endpoint.py b/tableauserverclient/server/endpoint/flows_endpoint.py index dfe16f904..41cbe19cd 100644 --- a/tableauserverclient/server/endpoint/flows_endpoint.py +++ b/tableauserverclient/server/endpoint/flows_endpoint.py @@ -14,11 +14,11 @@ from contextlib import closing # The maximum size of a file that can be published in a single request is 64MB -FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB +FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB -ALLOWED_FILE_EXTENSIONS = ['tfl', 'tflx'] +ALLOWED_FILE_EXTENSIONS = ["tfl", "tflx"] -logger = logging.getLogger('tableau.endpoint.flows') +logger = logging.getLogger("tableau.endpoint.flows") class Flows(Endpoint): @@ -34,7 +34,7 @@ def baseurl(self): # Get all flows @api(version="3.3") def get(self, req_options=None): - logger.info('Querying all flows on site') + logger.info("Querying all flows on site") url = self.baseurl server_response = self.get_request(url, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) @@ -47,7 +47,7 @@ def get_by_id(self, flow_id): if not flow_id: error = "Flow ID undefined." raise ValueError(error) - logger.info('Querying single flow (ID: {0})'.format(flow_id)) + logger.info("Querying single flow (ID: {0})".format(flow_id)) url = "{0}/{1}".format(self.baseurl, flow_id) server_response = self.get_request(url) return FlowItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -56,17 +56,17 @@ def get_by_id(self, flow_id): @api(version="3.3") def populate_connections(self, flow_item): if not flow_item.id: - error = 'Flow item missing ID. Flow must be retrieved from server first.' + error = "Flow item missing ID. Flow must be retrieved from server first." raise MissingRequiredFieldError(error) def connections_fetcher(): return self._get_flow_connections(flow_item) flow_item._set_connections(connections_fetcher) - logger.info('Populated connections for flow (ID: {0})'.format(flow_item.id)) + logger.info("Populated connections for flow (ID: {0})".format(flow_item.id)) def _get_flow_connections(self, flow_item, req_options=None): - url = '{0}/{1}/connections'.format(self.baseurl, flow_item.id) + url = "{0}/{1}/connections".format(self.baseurl, flow_item.id) server_response = self.get_request(url, req_options) connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace) return connections @@ -79,7 +79,7 @@ def delete(self, flow_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, flow_id) self.delete_request(url) - logger.info('Deleted single flow (ID: {0})'.format(flow_id)) + logger.info("Deleted single flow (ID: {0})".format(flow_id)) # Download 1 flow by id @api(version="3.3") @@ -89,24 +89,24 @@ def download(self, flow_id, filepath=None): raise ValueError(error) url = "{0}/{1}/content".format(self.baseurl, flow_id) - with closing(self.get_request(url, parameters={'stream': True})) as server_response: - _, params = cgi.parse_header(server_response.headers['Content-Disposition']) - filename = to_filename(os.path.basename(params['filename'])) + with closing(self.get_request(url, parameters={"stream": True})) as server_response: + _, params = cgi.parse_header(server_response.headers["Content-Disposition"]) + filename = to_filename(os.path.basename(params["filename"])) download_path = make_download_path(filepath, filename) - with open(download_path, 'wb') as f: + with open(download_path, "wb") as f: for chunk in server_response.iter_content(1024): # 1KB f.write(chunk) - logger.info('Downloaded flow to {0} (ID: {1})'.format(download_path, flow_id)) + logger.info("Downloaded flow to {0} (ID: {1})".format(download_path, flow_id)) return os.path.abspath(download_path) # Update flow @api(version="3.3") def update(self, flow_item): if not flow_item.id: - error = 'Flow item missing ID. Flow must be retrieved from server first.' + error = "Flow item missing ID. Flow must be retrieved from server first." raise MissingRequiredFieldError(error) self._resource_tagger.update_tags(self.baseurl, flow_item) @@ -115,7 +115,7 @@ def update(self, flow_item): url = "{0}/{1}".format(self.baseurl, flow_item.id) update_req = RequestFactory.Flow.update_req(flow_item) server_response = self.put_request(url, update_req) - logger.info('Updated flow item (ID: {0})'.format(flow_item.id)) + logger.info("Updated flow item (ID: {0})".format(flow_item.id)) updated_flow = copy.copy(flow_item) return updated_flow._parse_common_elements(server_response.content, self.parent_srv.namespace) @@ -128,8 +128,7 @@ def update_connection(self, flow_item, connection_item): server_response = self.put_request(url, update_req) connection = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Updated flow item (ID: {0} & connection item {1}'.format(flow_item.id, - connection_item.id)) + logger.info("Updated flow item (ID: {0} & connection item {1}".format(flow_item.id, connection_item.id)) return connection @api(version="3.3") @@ -147,7 +146,7 @@ def publish(self, flow_item, file_path, mode, connections=None): error = "File path does not lead to an existing file." raise IOError(error) if not mode or not hasattr(self.parent_srv.PublishMode, mode): - error = 'Invalid mode defined.' + error = "Invalid mode defined." raise ValueError(error) filename = os.path.basename(file_path) @@ -157,29 +156,25 @@ def publish(self, flow_item, file_path, mode, connections=None): if not flow_item.name: flow_item.name = os.path.splitext(filename)[0] if file_extension not in ALLOWED_FILE_EXTENSIONS: - error = "Only {} files can be published as flows.".format(', '.join(ALLOWED_FILE_EXTENSIONS)) + error = "Only {} files can be published as flows.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) # Construct the url with the defined mode url = "{0}?flowType={1}".format(self.baseurl, file_extension) if mode == self.parent_srv.PublishMode.Overwrite or mode == self.parent_srv.PublishMode.Append: - url += '&{0}=true'.format(mode.lower()) + url += "&{0}=true".format(mode.lower()) # Determine if chunking is required (64MB is the limit for single upload method) if os.path.getsize(file_path) >= FILESIZE_LIMIT: - logger.info('Publishing {0} to server with chunking method (flow over 64MB)'.format(filename)) + logger.info("Publishing {0} to server with chunking method (flow over 64MB)".format(filename)) upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file_path) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) - xml_request, content_type = RequestFactory.Flow.publish_req_chunked(flow_item, - connections) + xml_request, content_type = RequestFactory.Flow.publish_req_chunked(flow_item, connections) else: - logger.info('Publishing {0} to server'.format(filename)) - with open(file_path, 'rb') as f: + logger.info("Publishing {0} to server".format(filename)) + with open(file_path, "rb") as f: file_contents = f.read() - xml_request, content_type = RequestFactory.Flow.publish_req(flow_item, - filename, - file_contents, - connections) + xml_request, content_type = RequestFactory.Flow.publish_req(flow_item, filename, file_contents, connections) # Send the publishing request to server try: @@ -190,30 +185,32 @@ def publish(self, flow_item, file_path, mode, connections=None): raise err else: new_flow = FlowItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (ID: {1})'.format(filename, new_flow.id)) + logger.info("Published {0} (ID: {1})".format(filename, new_flow.id)) return new_flow server_response = self.post_request(url, xml_request, content_type) new_flow = FlowItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (ID: {1})'.format(filename, new_flow.id)) + logger.info("Published {0} (ID: {1})".format(filename, new_flow.id)) return new_flow - @api(version='3.3') + @api(version="3.3") def populate_permissions(self, item): self._permissions.populate(item) - @api(version='3.3') + @api(version="3.3") def update_permission(self, item, permission_item): import warnings - warnings.warn('Server.flows.update_permission is deprecated, ' - 'please use Server.flows.update_permissions instead.', - DeprecationWarning) + + warnings.warn( + "Server.flows.update_permission is deprecated, " "please use Server.flows.update_permissions instead.", + DeprecationWarning, + ) self._permissions.update(item, permission_item) - @api(version='3.3') + @api(version="3.3") def update_permissions(self, item, permission_item): self._permissions.update(item, permission_item) - @api(version='3.3') + @api(version="3.3") def delete_permission(self, item, capability_item): self._permissions.delete(item, capability_item) diff --git a/tableauserverclient/server/endpoint/groups_endpoint.py b/tableauserverclient/server/endpoint/groups_endpoint.py index 6a9b81afd..4a09872cb 100644 --- a/tableauserverclient/server/endpoint/groups_endpoint.py +++ b/tableauserverclient/server/endpoint/groups_endpoint.py @@ -5,7 +5,7 @@ import logging -logger = logging.getLogger('tableau.endpoint.groups') +logger = logging.getLogger("tableau.endpoint.groups") class Groups(Endpoint): @@ -16,7 +16,7 @@ def baseurl(self): # Gets all groups @api(version="2.0") def get(self, req_options=None): - logger.info('Querying all groups on site') + logger.info("Querying all groups on site") url = self.baseurl server_response = self.get_request(url, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) @@ -42,7 +42,7 @@ def _get_users_for_group(self, group_item, req_options=None): server_response = self.get_request(url, req_options) user_item = UserItem.from_response(server_response.content, self.parent_srv.namespace) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) - logger.info('Populated users for group (ID: {0})'.format(group_item.id)) + logger.info("Populated users for group (ID: {0})".format(group_item.id)) return user_item, pagination_item # Deletes 1 group by id @@ -53,30 +53,33 @@ def delete(self, group_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, group_id) self.delete_request(url) - logger.info('Deleted single group (ID: {0})'.format(group_id)) + logger.info("Deleted single group (ID: {0})".format(group_id)) @api(version="2.0") def update(self, group_item, default_site_role=None, as_job=False): # (1/8/2021): Deprecated starting v0.15 if default_site_role is not None: import warnings - warnings.simplefilter('always', DeprecationWarning) - warnings.warn('Groups.update(...default_site_role=""...) is deprecated, ' - 'please set the minimum_site_role field of GroupItem', - DeprecationWarning) + + warnings.simplefilter("always", DeprecationWarning) + warnings.warn( + 'Groups.update(...default_site_role=""...) is deprecated, ' + "please set the minimum_site_role field of GroupItem", + DeprecationWarning, + ) group_item.minimum_site_role = default_site_role if not group_item.id: error = "Group item missing ID." raise MissingRequiredFieldError(error) - if as_job and (group_item.domain_name is None or group_item.domain_name == 'local'): + if as_job and (group_item.domain_name is None or group_item.domain_name == "local"): error = "Local groups cannot be updated asynchronously." raise ValueError(error) url = "{0}/{1}".format(self.baseurl, group_item.id) update_req = RequestFactory.Group.update_req(group_item, None) server_response = self.put_request(url, update_req) - logger.info('Updated group item (ID: {0})'.format(group_item.id)) + logger.info("Updated group item (ID: {0})".format(group_item.id)) if as_job: return JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] else: @@ -97,7 +100,7 @@ def create_AD_group(self, group_item, asJob=False): url = self.baseurl + asJobparameter create_req = RequestFactory.Group.create_ad_req(group_item) server_response = self.post_request(url, create_req) - if (asJob): + if asJob: return JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] else: return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -113,7 +116,7 @@ def remove_user(self, group_item, user_id): raise ValueError(error) url = "{0}/{1}/users/{2}".format(self.baseurl, group_item.id, user_id) self.delete_request(url) - logger.info('Removed user (id: {0}) from group (ID: {1})'.format(user_id, group_item.id)) + logger.info("Removed user (id: {0}) from group (ID: {1})".format(user_id, group_item.id)) # Adds 1 user to 1 group @api(version="2.0") @@ -128,5 +131,5 @@ def add_user(self, group_item, user_id): add_req = RequestFactory.Group.add_user_req(user_id) server_response = self.post_request(url, add_req) user = UserItem.from_response(server_response.content, self.parent_srv.namespace).pop() - logger.info('Added user (id: {0}) to group (ID: {1})'.format(user_id, group_item.id)) + logger.info("Added user (id: {0}) to group (ID: {1})".format(user_id, group_item.id)) return user diff --git a/tableauserverclient/server/endpoint/jobs_endpoint.py b/tableauserverclient/server/endpoint/jobs_endpoint.py index d8bbe39c7..6079ca788 100644 --- a/tableauserverclient/server/endpoint/jobs_endpoint.py +++ b/tableauserverclient/server/endpoint/jobs_endpoint.py @@ -10,7 +10,7 @@ # In case we are in python 3 the string check is different basestring = str -logger = logging.getLogger('tableau.endpoint.jobs') +logger = logging.getLogger("tableau.endpoint.jobs") class Jobs(Endpoint): @@ -18,31 +18,32 @@ class Jobs(Endpoint): def baseurl(self): return "{0}/sites/{1}/jobs".format(self.parent_srv.baseurl, self.parent_srv.site_id) - @api(version='2.6') + @api(version="2.6") def get(self, job_id=None, req_options=None): # Backwards Compatibility fix until we rev the major version if job_id is not None and isinstance(job_id, basestring): import warnings + warnings.warn("Jobs.get(job_id) is deprecated, update code to use Jobs.get_by_id(job_id)") return self.get_by_id(job_id) if isinstance(job_id, RequestOptionsBase): req_options = job_id - self.parent_srv.assert_at_least_version('3.1') + self.parent_srv.assert_at_least_version("3.1") server_response = self.get_request(self.baseurl, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) jobs = BackgroundJobItem.from_response(server_response.content, self.parent_srv.namespace) return jobs, pagination_item - @api(version='3.1') + @api(version="3.1") def cancel(self, job_id): - id_ = getattr(job_id, 'id', job_id) - url = '{0}/{1}'.format(self.baseurl, id_) + id_ = getattr(job_id, "id", job_id) + url = "{0}/{1}".format(self.baseurl, id_) return self.put_request(url) - @api(version='2.6') + @api(version="2.6") def get_by_id(self, job_id): - logger.info('Query for information about job ' + job_id) + logger.info("Query for information about job " + job_id) url = "{0}/{1}".format(self.baseurl, job_id) server_response = self.get_request(url) new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] diff --git a/tableauserverclient/server/endpoint/metadata_endpoint.py b/tableauserverclient/server/endpoint/metadata_endpoint.py index ac111d6ef..368a92a97 100644 --- a/tableauserverclient/server/endpoint/metadata_endpoint.py +++ b/tableauserverclient/server/endpoint/metadata_endpoint.py @@ -4,7 +4,7 @@ import json -logger = logging.getLogger('tableau.endpoint.metadata') +logger = logging.getLogger("tableau.endpoint.metadata") def is_valid_paged_query(parsed_query): @@ -12,9 +12,11 @@ def is_valid_paged_query(parsed_query): Also check that we are asking for the pageInfo object, so we get the endCursor. There is no way to do this relilably without writing a GraphQL parser, so simply check that that the string contains 'hasNextPage' and 'endCursor'""" - return all(k in parsed_query['variables'] for k in ('first', 'afterToken')) and \ - 'hasNextPage' in parsed_query['query'] and \ - 'endCursor' in parsed_query['query'] + return ( + all(k in parsed_query["variables"] for k in ("first", "afterToken")) + and "hasNextPage" in parsed_query["query"] + and "endCursor" in parsed_query["query"] + ) def extract_values(obj, key): @@ -40,8 +42,8 @@ def extract(obj, arr, key): def get_page_info(result): - next_page = extract_values(result, 'hasNextPage').pop() - cursor = extract_values(result, 'endCursor').pop() + next_page = extract_values(result, "hasNextPage").pop() + cursor = extract_values(result, "endCursor").pop() return next_page, cursor @@ -56,20 +58,20 @@ def control_baseurl(self): @api("3.5") def query(self, query, variables=None, abort_on_error=False): - logger.info('Querying Metadata API') + logger.info("Querying Metadata API") url = self.baseurl try: - graphql_query = json.dumps({'query': query, 'variables': variables}) + graphql_query = json.dumps({"query": query, "variables": variables}) except Exception as e: - raise InvalidGraphQLQuery('Must provide a string') + raise InvalidGraphQLQuery("Must provide a string") # Setting content type because post_reuqest defaults to text/xml - server_response = self.post_request(url, graphql_query, content_type='text/json') + server_response = self.post_request(url, graphql_query, content_type="text/json") results = server_response.json() - if abort_on_error and results.get('errors', None): - raise GraphQLError(results['errors']) + if abort_on_error and results.get("errors", None): + raise GraphQLError(results["errors"]) return results @@ -87,32 +89,34 @@ def eventing_status(self): @api("3.5") def paginated_query(self, query, variables=None, abort_on_error=False): - logger.info('Querying Metadata API using a Paged Query') + logger.info("Querying Metadata API using a Paged Query") url = self.baseurl if variables is None: # default paramaters - variables = {'first': 100, 'afterToken': None} - elif (('first' in variables) and ('afterToken' not in variables)): + variables = {"first": 100, "afterToken": None} + elif ("first" in variables) and ("afterToken" not in variables): # they passed a page size but not a token, probably because they're starting at `null` token - variables.update({'afterToken': None}) + variables.update({"afterToken": None}) - graphql_query = json.dumps({'query': query, 'variables': variables}) + graphql_query = json.dumps({"query": query, "variables": variables}) parsed_query = json.loads(graphql_query) if not is_valid_paged_query(parsed_query): - raise InvalidGraphQLQuery('Paged queries must have a `$first` and `$afterToken` variables as well as ' - 'a pageInfo object with `endCursor` and `hasNextPage`') + raise InvalidGraphQLQuery( + "Paged queries must have a `$first` and `$afterToken` variables as well as " + "a pageInfo object with `endCursor` and `hasNextPage`" + ) - results_dict = {'pages': []} - paginated_results = results_dict['pages'] + results_dict = {"pages": []} + paginated_results = results_dict["pages"] # get first page - server_response = self.post_request(url, graphql_query, content_type='text/json') + server_response = self.post_request(url, graphql_query, content_type="text/json") results = server_response.json() - if abort_on_error and results.get('errors', None): - raise GraphQLError(results['errors']) + if abort_on_error and results.get("errors", None): + raise GraphQLError(results["errors"]) paginated_results.append(results) @@ -121,18 +125,18 @@ def paginated_query(self, query, variables=None, abort_on_error=False): while has_another_page: # Update the page - variables.update({'afterToken': cursor}) + variables.update({"afterToken": cursor}) # make the call logger.debug("Calling Token: " + cursor) - graphql_query = json.dumps({'query': query, 'variables': variables}) - server_response = self.post_request(url, graphql_query, content_type='text/json') + graphql_query = json.dumps({"query": query, "variables": variables}) + server_response = self.post_request(url, graphql_query, content_type="text/json") results = server_response.json() # verify response - if abort_on_error and results.get('errors', None): - raise GraphQLError(results['errors']) + if abort_on_error and results.get("errors", None): + raise GraphQLError(results["errors"]) # save results and repeat paginated_results.append(results) has_another_page, cursor = get_page_info(results) - logger.info('Sucessfully got all results for paged query') + logger.info("Sucessfully got all results for paged query") return results_dict diff --git a/tableauserverclient/server/endpoint/permissions_endpoint.py b/tableauserverclient/server/endpoint/permissions_endpoint.py index 585fd0052..0992f5ca9 100644 --- a/tableauserverclient/server/endpoint/permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/permissions_endpoint.py @@ -10,7 +10,7 @@ class _PermissionsEndpoint(Endpoint): - """ Adds permission model to another endpoint + """Adds permission model to another endpoint Tableau permissions model is identical between objects but they are nested under the parent object endpoint (i.e. permissions for workbooks are under @@ -27,12 +27,11 @@ def __init__(self, parent_srv, owner_baseurl): self.owner_baseurl = owner_baseurl def update(self, resource, permissions): - url = '{0}/{1}/permissions'.format(self.owner_baseurl(), resource.id) + url = "{0}/{1}/permissions".format(self.owner_baseurl(), resource.id) update_req = RequestFactory.Permission.add_req(permissions) response = self.put_request(url, update_req) - permissions = PermissionsRule.from_response(response.content, - self.parent_srv.namespace) - logger.info('Updated permissions for resource {0}'.format(resource.id)) + permissions = PermissionsRule.from_response(response.content, self.parent_srv.namespace) + logger.info("Updated permissions for resource {0}".format(resource.id)) return permissions @@ -46,23 +45,17 @@ def delete(self, resource, rules): for rule in rules: for capability, mode in rule.capabilities.items(): " /permissions/groups/group-id/capability-name/capability-mode" - url = '{0}/{1}/permissions/{2}/{3}/{4}/{5}'.format( - self.owner_baseurl(), - resource.id, - rule.grantee.tag_name + 's', - rule.grantee.id, - capability, - mode) + url = "{0}/{1}/permissions/{2}/{3}/{4}/{5}".format( + self.owner_baseurl(), resource.id, rule.grantee.tag_name + "s", rule.grantee.id, capability, mode + ) - logger.debug('Removing {0} permission for capabilty {1}'.format( - mode, capability)) + logger.debug("Removing {0} permission for capabilty {1}".format(mode, capability)) self.delete_request(url) - logger.info('Deleted permission for {0} {1} item {2}'.format( - rule.grantee.tag_name, - rule.grantee.id, - resource.id)) + logger.info( + "Deleted permission for {0} {1} item {2}".format(rule.grantee.tag_name, rule.grantee.id, resource.id) + ) def populate(self, item): if not item.id: @@ -73,12 +66,11 @@ def permission_fetcher(): return self._get_permissions(item) item._set_permissions(permission_fetcher) - logger.info('Populated permissions for item (ID: {0})'.format(item.id)) + logger.info("Populated permissions for item (ID: {0})".format(item.id)) def _get_permissions(self, item, req_options=None): url = "{0}/{1}/permissions".format(self.owner_baseurl(), item.id) server_response = self.get_request(url, req_options) - permissions = PermissionsRule.from_response(server_response.content, - self.parent_srv.namespace) + permissions = PermissionsRule.from_response(server_response.content, self.parent_srv.namespace) return permissions diff --git a/tableauserverclient/server/endpoint/projects_endpoint.py b/tableauserverclient/server/endpoint/projects_endpoint.py index 170425eab..72286e570 100644 --- a/tableauserverclient/server/endpoint/projects_endpoint.py +++ b/tableauserverclient/server/endpoint/projects_endpoint.py @@ -7,7 +7,7 @@ import logging -logger = logging.getLogger('tableau.endpoint.projects') +logger = logging.getLogger("tableau.endpoint.projects") class Projects(Endpoint): @@ -23,7 +23,7 @@ def baseurl(self): @api(version="2.0") def get(self, req_options=None): - logger.info('Querying all projects on site') + logger.info("Querying all projects on site") url = self.baseurl server_response = self.get_request(url, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) @@ -37,7 +37,7 @@ def delete(self, project_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, project_id) self.delete_request(url) - logger.info('Deleted single project (ID: {0})'.format(project_id)) + logger.info("Deleted single project (ID: {0})".format(project_id)) @api(version="2.0") def update(self, project_item): @@ -48,7 +48,7 @@ def update(self, project_item): url = "{0}/{1}".format(self.baseurl, project_item.id) update_req = RequestFactory.Project.update_req(project_item) server_response = self.put_request(url, update_req) - logger.info('Updated project item (ID: {0})'.format(project_item.id)) + logger.info("Updated project item (ID: {0})".format(project_item.id)) updated_project = ProjectItem.from_response(server_response.content, self.parent_srv.namespace)[0] return updated_project @@ -58,61 +58,64 @@ def create(self, project_item): create_req = RequestFactory.Project.create_req(project_item) server_response = self.post_request(url, create_req) new_project = ProjectItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Created new project (ID: {0})'.format(new_project.id)) + logger.info("Created new project (ID: {0})".format(new_project.id)) return new_project - @api(version='2.0') + @api(version="2.0") def populate_permissions(self, item): self._permissions.populate(item) - @api(version='2.0') + @api(version="2.0") def update_permission(self, item, rules): import warnings - warnings.warn('Server.projects.update_permission is deprecated, ' - 'please use Server.projects.update_permissions instead.', - DeprecationWarning) + + warnings.warn( + "Server.projects.update_permission is deprecated, " + "please use Server.projects.update_permissions instead.", + DeprecationWarning, + ) return self._permissions.update(item, rules) - @api(version='2.0') + @api(version="2.0") def update_permissions(self, item, rules): return self._permissions.update(item, rules) - @api(version='2.0') + @api(version="2.0") def delete_permission(self, item, rules): self._permissions.delete(item, rules) - @api(version='2.1') + @api(version="2.1") def populate_workbook_default_permissions(self, item): self._default_permissions.populate_default_permissions(item, Permission.Resource.Workbook) - @api(version='2.1') + @api(version="2.1") def populate_datasource_default_permissions(self, item): self._default_permissions.populate_default_permissions(item, Permission.Resource.Datasource) - @api(version='3.4') + @api(version="3.4") def populate_flow_default_permissions(self, item): self._default_permissions.populate_default_permissions(item, Permission.Resource.Flow) - @api(version='2.1') + @api(version="2.1") def update_workbook_default_permissions(self, item, rules): return self._default_permissions.update_default_permissions(item, rules, Permission.Resource.Workbook) - @api(version='2.1') + @api(version="2.1") def update_datasource_default_permissions(self, item, rules): return self._default_permissions.update_default_permissions(item, rules, Permission.Resource.Datasource) - @api(version='3.4') + @api(version="3.4") def update_flow_default_permissions(self, item, rules): return self._default_permissions.update_default_permissions(item, rules, Permission.Resource.Flow) - @api(version='2.1') + @api(version="2.1") def delete_workbook_default_permissions(self, item, rule): self._default_permissions.delete_default_permission(item, rule, Permission.Resource.Workbook) - @api(version='2.1') + @api(version="2.1") def delete_datasource_default_permissions(self, item, rule): self._default_permissions.delete_default_permission(item, rule, Permission.Resource.Datasource) - @api(version='3.4') + @api(version="3.4") def delete_flow_default_permissions(self, item, rule): self._default_permissions.delete_default_permission(item, rule, Permission.Resource.Flow) diff --git a/tableauserverclient/server/endpoint/resource_tagger.py b/tableauserverclient/server/endpoint/resource_tagger.py index ccee9fa10..a38c66ebe 100644 --- a/tableauserverclient/server/endpoint/resource_tagger.py +++ b/tableauserverclient/server/endpoint/resource_tagger.py @@ -6,7 +6,7 @@ import copy import urllib.parse -logger = logging.getLogger('tableau.endpoint.resource_tagger') +logger = logging.getLogger("tableau.endpoint.resource_tagger") class _ResourceTagger(Endpoint): @@ -47,4 +47,4 @@ def update_tags(self, baseurl, resource_item): if add_set: resource_item.tags = self._add_tags(baseurl, resource_item.id, add_set) resource_item._initial_tags = copy.copy(resource_item.tags) - logger.info('Updated tags to {0}'.format(resource_item.tags)) + logger.info("Updated tags to {0}".format(resource_item.tags)) diff --git a/tableauserverclient/server/endpoint/schedules_endpoint.py b/tableauserverclient/server/endpoint/schedules_endpoint.py index 3fd164b49..3a5e665fa 100644 --- a/tableauserverclient/server/endpoint/schedules_endpoint.py +++ b/tableauserverclient/server/endpoint/schedules_endpoint.py @@ -5,9 +5,9 @@ import copy from collections import namedtuple -logger = logging.getLogger('tableau.endpoint.schedules') +logger = logging.getLogger("tableau.endpoint.schedules") # Oh to have a first class Result concept in Python... -AddResponse = namedtuple('AddResponse', ('result', 'error', 'warnings', 'task_created')) +AddResponse = namedtuple("AddResponse", ("result", "error", "warnings", "task_created")) OK = AddResponse(result=True, error=None, warnings=None, task_created=None) @@ -65,8 +65,7 @@ def create(self, schedule_item): return new_schedule @api(version="2.8") - def add_to_schedule(self, schedule_id, workbook=None, datasource=None, - task_type=TaskItem.Type.ExtractRefresh): + def add_to_schedule(self, schedule_id, workbook=None, datasource=None, task_type=TaskItem.Type.ExtractRefresh): def add_to(resource, type_, req_factory): id_ = resource.id url = "{0}/{1}/{2}s".format(self.siteurl, schedule_id, type_) @@ -74,7 +73,8 @@ def add_to(resource, type_, req_factory): response = self.put_request(url, add_req) error, warnings, task_created = ScheduleItem.parse_add_to_schedule_response( - response, self.parent_srv.namespace) + response, self.parent_srv.namespace + ) if task_created: logger.info("Added {} to {} to schedule {}".format(type_, id_, schedule_id)) diff --git a/tableauserverclient/server/endpoint/server_info_endpoint.py b/tableauserverclient/server/endpoint/server_info_endpoint.py index 0a6b9ec89..98d996b52 100644 --- a/tableauserverclient/server/endpoint/server_info_endpoint.py +++ b/tableauserverclient/server/endpoint/server_info_endpoint.py @@ -3,7 +3,7 @@ from ...models import ServerInfoItem import logging -logger = logging.getLogger('tableau.endpoint.server_info') +logger = logging.getLogger("tableau.endpoint.server_info") class ServerInfo(Endpoint): diff --git a/tableauserverclient/server/endpoint/sites_endpoint.py b/tableauserverclient/server/endpoint/sites_endpoint.py index c57cb3d4f..9446a01a8 100644 --- a/tableauserverclient/server/endpoint/sites_endpoint.py +++ b/tableauserverclient/server/endpoint/sites_endpoint.py @@ -5,7 +5,7 @@ import copy import logging -logger = logging.getLogger('tableau.endpoint.sites') +logger = logging.getLogger("tableau.endpoint.sites") class Sites(Endpoint): @@ -16,7 +16,7 @@ def baseurl(self): # Gets all sites @api(version="2.0") def get(self, req_options=None): - logger.info('Querying all sites on site') + logger.info("Querying all sites on site") url = self.baseurl server_response = self.get_request(url, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) @@ -29,7 +29,7 @@ def get_by_id(self, site_id): if not site_id: error = "Site ID undefined." raise ValueError(error) - logger.info('Querying single site (ID: {0})'.format(site_id)) + logger.info("Querying single site (ID: {0})".format(site_id)) url = "{0}/{1}".format(self.baseurl, site_id) server_response = self.get_request(url) return SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -40,7 +40,7 @@ def get_by_name(self, site_name): if not site_name: error = "Site Name undefined." raise ValueError(error) - logger.info('Querying single site (Name: {0})'.format(site_name)) + logger.info("Querying single site (Name: {0})".format(site_name)) url = "{0}/{1}?key=name".format(self.baseurl, site_name) server_response = self.get_request(url) return SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -51,7 +51,7 @@ def get_by_content_url(self, content_url): if content_url is None: error = "Content URL undefined." raise ValueError(error) - logger.info('Querying single site (Content URL: {0})'.format(content_url)) + logger.info("Querying single site (Content URL: {0})".format(content_url)) url = "{0}/{1}?key=contentUrl".format(self.baseurl, content_url) server_response = self.get_request(url) return SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -64,13 +64,13 @@ def update(self, site_item): raise MissingRequiredFieldError(error) if site_item.admin_mode: if site_item.admin_mode == SiteItem.AdminMode.ContentOnly and site_item.user_quota: - error = 'You cannot set admin_mode to ContentOnly and also set a user quota' + error = "You cannot set admin_mode to ContentOnly and also set a user quota" raise ValueError(error) url = "{0}/{1}".format(self.baseurl, site_item.id) update_req = RequestFactory.Site.update_req(site_item) server_response = self.put_request(url, update_req) - logger.info('Updated site item (ID: {0})'.format(site_item.id)) + logger.info("Updated site item (ID: {0})".format(site_item.id)) update_site = copy.copy(site_item) return update_site._parse_common_tags(server_response.content, self.parent_srv.namespace) @@ -85,23 +85,23 @@ def delete(self, site_id): # If we deleted the site we are logged into # then we are automatically logged out if site_id == self.parent_srv.site_id: - logger.info('Deleting current site and clearing auth tokens') + logger.info("Deleting current site and clearing auth tokens") self.parent_srv._clear_auth() - logger.info('Deleted single site (ID: {0}) and signed out'.format(site_id)) + logger.info("Deleted single site (ID: {0}) and signed out".format(site_id)) # Create new site @api(version="2.0") def create(self, site_item): if site_item.admin_mode: if site_item.admin_mode == SiteItem.AdminMode.ContentOnly and site_item.user_quota: - error = 'You cannot set admin_mode to ContentOnly and also set a user quota' + error = "You cannot set admin_mode to ContentOnly and also set a user quota" raise ValueError(error) url = self.baseurl create_req = RequestFactory.Site.create_req(site_item) server_response = self.post_request(url, create_req) new_site = SiteItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Created new site (ID: {0})'.format(new_site.id)) + logger.info("Created new site (ID: {0})".format(new_site.id)) return new_site @api(version="3.5") diff --git a/tableauserverclient/server/endpoint/subscriptions_endpoint.py b/tableauserverclient/server/endpoint/subscriptions_endpoint.py index 120a2a17b..1a66e8ac5 100644 --- a/tableauserverclient/server/endpoint/subscriptions_endpoint.py +++ b/tableauserverclient/server/endpoint/subscriptions_endpoint.py @@ -4,18 +4,17 @@ import logging -logger = logging.getLogger('tableau.endpoint.subscriptions') +logger = logging.getLogger("tableau.endpoint.subscriptions") class Subscriptions(Endpoint): @property def baseurl(self): - return "{0}/sites/{1}/subscriptions".format(self.parent_srv.baseurl, - self.parent_srv.site_id) + return "{0}/sites/{1}/subscriptions".format(self.parent_srv.baseurl, self.parent_srv.site_id) - @api(version='2.3') + @api(version="2.3") def get(self, req_options=None): - logger.info('Querying all subscriptions for the site') + logger.info("Querying all subscriptions for the site") url = self.baseurl server_response = self.get_request(url, req_options) @@ -23,7 +22,7 @@ def get(self, req_options=None): all_subscriptions = SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace) return all_subscriptions, pagination_item - @api(version='2.3') + @api(version="2.3") def get_by_id(self, subscription_id): if not subscription_id: error = "No Subscription ID provided" @@ -33,7 +32,7 @@ def get_by_id(self, subscription_id): server_response = self.get_request(url) return SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)[0] - @api(version='2.3') + @api(version="2.3") def create(self, subscription_item): if not subscription_item: error = "No Susbcription provided" @@ -44,16 +43,16 @@ def create(self, subscription_item): server_response = self.post_request(url, create_req) return SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)[0] - @api(version='2.3') + @api(version="2.3") def delete(self, subscription_id): if not subscription_id: error = "Subscription ID undefined." raise ValueError(error) url = "{0}/{1}".format(self.baseurl, subscription_id) self.delete_request(url) - logger.info('Deleted subscription (ID: {0})'.format(subscription_id)) + logger.info("Deleted subscription (ID: {0})".format(subscription_id)) - @api(version='2.3') + @api(version="2.3") def update(self, subscription_item): if not subscription_item.id: error = "Subscription item missing ID. Subscription must be retrieved from server first." @@ -61,5 +60,5 @@ def update(self, subscription_item): url = "{0}/{1}".format(self.baseurl, subscription_item.id) update_req = RequestFactory.Subscription.update_req(subscription_item) server_response = self.put_request(url, update_req) - logger.info('Updated subscription item (ID: {0})'.format(subscription_item.id)) + logger.info("Updated subscription item (ID: {0})".format(subscription_item.id)) return SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)[0] diff --git a/tableauserverclient/server/endpoint/tables_endpoint.py b/tableauserverclient/server/endpoint/tables_endpoint.py index 3a5c2f3f4..e35535d19 100644 --- a/tableauserverclient/server/endpoint/tables_endpoint.py +++ b/tableauserverclient/server/endpoint/tables_endpoint.py @@ -7,7 +7,7 @@ import logging -logger = logging.getLogger('tableau.endpoint.tables') +logger = logging.getLogger("tableau.endpoint.tables") class Tables(Endpoint): @@ -22,7 +22,7 @@ def baseurl(self): @api(version="3.5") def get(self, req_options=None): - logger.info('Querying all tables on site') + logger.info("Querying all tables on site") url = self.baseurl server_response = self.get_request(url, req_options) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) @@ -35,7 +35,7 @@ def get_by_id(self, table_id): if not table_id: error = "table ID undefined." raise ValueError(error) - logger.info('Querying single table (ID: {0})'.format(table_id)) + logger.info("Querying single table (ID: {0})".format(table_id)) url = "{0}/{1}".format(self.baseurl, table_id) server_response = self.get_request(url) return TableItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -47,7 +47,7 @@ def delete(self, table_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, table_id) self.delete_request(url) - logger.info('Deleted single table (ID: {0})'.format(table_id)) + logger.info("Deleted single table (ID: {0})".format(table_id)) @api(version="3.5") def update(self, table_item): @@ -58,7 +58,7 @@ def update(self, table_item): url = "{0}/{1}".format(self.baseurl, table_item.id) update_req = RequestFactory.Table.update_req(table_item) server_response = self.put_request(url, update_req) - logger.info('Updated table item (ID: {0})'.format(table_item.id)) + logger.info("Updated table item (ID: {0})".format(table_item.id)) updated_table = TableItem.from_response(server_response.content, self.parent_srv.namespace)[0] return updated_table @@ -73,13 +73,12 @@ def column_fetcher(): return Pager(lambda options: self._get_columns_for_table(table_item, options), req_options) table_item._set_columns(column_fetcher) - logger.info('Populated columns for table (ID: {0}'.format(table_item.id)) + logger.info("Populated columns for table (ID: {0}".format(table_item.id)) def _get_columns_for_table(self, table_item, req_options=None): url = "{0}/{1}/columns".format(self.baseurl, table_item.id) server_response = self.get_request(url, req_options) - columns = ColumnItem.from_response(server_response.content, - self.parent_srv.namespace) + columns = ColumnItem.from_response(server_response.content, self.parent_srv.namespace) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) return columns, pagination_item @@ -90,26 +89,27 @@ def update_column(self, table_item, column_item): server_response = self.put_request(url, update_req) column = ColumnItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Updated table item (ID: {0} & column item {1}'.format(table_item.id, - column_item.id)) + logger.info("Updated table item (ID: {0} & column item {1}".format(table_item.id, column_item.id)) return column - @api(version='3.5') + @api(version="3.5") def populate_permissions(self, item): self._permissions.populate(item) - @api(version='3.5') + @api(version="3.5") def update_permission(self, item, rules): import warnings - warnings.warn('Server.tables.update_permission is deprecated, ' - 'please use Server.tables.update_permissions instead.', - DeprecationWarning) + + warnings.warn( + "Server.tables.update_permission is deprecated, " "please use Server.tables.update_permissions instead.", + DeprecationWarning, + ) return self._permissions.update(item, rules) - @api(version='3.5') + @api(version="3.5") def update_permissions(self, item, rules): return self._permissions.update(item, rules) - @api(version='3.5') + @api(version="3.5") def delete_permission(self, item, rules): return self._permissions.delete(item, rules) diff --git a/tableauserverclient/server/endpoint/tasks_endpoint.py b/tableauserverclient/server/endpoint/tasks_endpoint.py index a3e5e7b34..abc249721 100644 --- a/tableauserverclient/server/endpoint/tasks_endpoint.py +++ b/tableauserverclient/server/endpoint/tasks_endpoint.py @@ -4,61 +4,57 @@ import logging -logger = logging.getLogger('tableau.endpoint.tasks') +logger = logging.getLogger("tableau.endpoint.tasks") class Tasks(Endpoint): @property def baseurl(self): - return "{0}/sites/{1}/tasks".format(self.parent_srv.baseurl, - self.parent_srv.site_id) + return "{0}/sites/{1}/tasks".format(self.parent_srv.baseurl, self.parent_srv.site_id) def __normalize_task_type(self, task_type): """ - The word for extract refresh used in API URL is "extractRefreshes". - It is different than the tag "extractRefresh" used in the request body. + The word for extract refresh used in API URL is "extractRefreshes". + It is different than the tag "extractRefresh" used in the request body. """ if task_type == TaskItem.Type.ExtractRefresh: - return '{}es'.format(task_type) + return "{}es".format(task_type) else: return task_type - @api(version='2.6') + @api(version="2.6") def get(self, req_options=None, task_type=TaskItem.Type.ExtractRefresh): if task_type == TaskItem.Type.DataAcceleration: self.parent_srv.assert_at_least_version("3.8") - logger.info('Querying all {} tasks for the site'.format(task_type)) + logger.info("Querying all {} tasks for the site".format(task_type)) url = "{0}/{1}".format(self.baseurl, self.__normalize_task_type(task_type)) server_response = self.get_request(url, req_options) - pagination_item = PaginationItem.from_response(server_response.content, - self.parent_srv.namespace) - all_tasks = TaskItem.from_response(server_response.content, - self.parent_srv.namespace, - task_type) + pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) + all_tasks = TaskItem.from_response(server_response.content, self.parent_srv.namespace, task_type) return all_tasks, pagination_item - @api(version='2.6') + @api(version="2.6") def get_by_id(self, task_id): if not task_id: error = "No Task ID provided" raise ValueError(error) logger.info("Querying a single task by id ({})".format(task_id)) - url = "{}/{}/{}".format(self.baseurl, - self.__normalize_task_type(TaskItem.Type.ExtractRefresh), task_id) + url = "{}/{}/{}".format(self.baseurl, self.__normalize_task_type(TaskItem.Type.ExtractRefresh), task_id) server_response = self.get_request(url) return TaskItem.from_response(server_response.content, self.parent_srv.namespace)[0] - @api(version='2.6') + @api(version="2.6") def run(self, task_item): if not task_item.id: error = "User item missing ID." raise MissingRequiredFieldError(error) - url = "{0}/{1}/{2}/runNow".format(self.baseurl, - self.__normalize_task_type(TaskItem.Type.ExtractRefresh), task_item.id) + url = "{0}/{1}/{2}/runNow".format( + self.baseurl, self.__normalize_task_type(TaskItem.Type.ExtractRefresh), task_item.id + ) run_req = RequestFactory.Task.run_req(task_item) server_response = self.post_request(url, run_req) return server_response.content @@ -72,7 +68,6 @@ def delete(self, task_id, task_type=TaskItem.Type.ExtractRefresh): if not task_id: error = "No Task ID provided" raise ValueError(error) - url = "{0}/{1}/{2}".format(self.baseurl, - self.__normalize_task_type(task_type), task_id) + url = "{0}/{1}/{2}".format(self.baseurl, self.__normalize_task_type(task_type), task_id) self.delete_request(url) - logger.info('Deleted single task (ID: {0})'.format(task_id)) + logger.info("Deleted single task (ID: {0})".format(task_id)) diff --git a/tableauserverclient/server/endpoint/users_endpoint.py b/tableauserverclient/server/endpoint/users_endpoint.py index 17e12a8b1..3318e6bb3 100644 --- a/tableauserverclient/server/endpoint/users_endpoint.py +++ b/tableauserverclient/server/endpoint/users_endpoint.py @@ -6,7 +6,7 @@ import copy import logging -logger = logging.getLogger('tableau.endpoint.users') +logger = logging.getLogger("tableau.endpoint.users") class Users(QuerysetEndpoint): @@ -17,7 +17,7 @@ def baseurl(self): # Gets all users @api(version="2.0") def get(self, req_options=None): - logger.info('Querying all users on site') + logger.info("Querying all users on site") if req_options is None: req_options = RequestOptions() @@ -35,7 +35,7 @@ def get_by_id(self, user_id): if not user_id: error = "User ID undefined." raise ValueError(error) - logger.info('Querying single user (ID: {0})'.format(user_id)) + logger.info("Querying single user (ID: {0})".format(user_id)) url = "{0}/{1}".format(self.baseurl, user_id) server_response = self.get_request(url) return UserItem.from_response(server_response.content, self.parent_srv.namespace).pop() @@ -50,7 +50,7 @@ def update(self, user_item, password=None): url = "{0}/{1}".format(self.baseurl, user_item.id) update_req = RequestFactory.User.update_req(user_item, password) server_response = self.put_request(url, update_req) - logger.info('Updated user item (ID: {0})'.format(user_item.id)) + logger.info("Updated user item (ID: {0})".format(user_item.id)) updated_item = copy.copy(user_item) return updated_item._parse_common_tags(server_response.content, self.parent_srv.namespace) @@ -62,7 +62,7 @@ def remove(self, user_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, user_id) self.delete_request(url) - logger.info('Removed single user (ID: {0})'.format(user_id)) + logger.info("Removed single user (ID: {0})".format(user_id)) # Add new user to site @api(version="2.0") @@ -71,7 +71,7 @@ def add(self, user_item): add_req = RequestFactory.User.add_req(user_item) server_response = self.post_request(url, add_req) new_user = UserItem.from_response(server_response.content, self.parent_srv.namespace).pop() - logger.info('Added new user (ID: {0})'.format(new_user.id)) + logger.info("Added new user (ID: {0})".format(new_user.id)) return new_user # Get workbooks for user @@ -89,7 +89,7 @@ def wb_pager(): def _get_wbs_for_user(self, user_item, req_options=None): url = "{0}/{1}/workbooks".format(self.baseurl, user_item.id) server_response = self.get_request(url, req_options) - logger.info('Populated workbooks for user (ID: {0})'.format(user_item.id)) + logger.info("Populated workbooks for user (ID: {0})".format(user_item.id)) workbook_item = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) return workbook_item, pagination_item @@ -112,7 +112,7 @@ def groups_for_user_pager(): def _get_groups_for_user(self, user_item, req_options=None): url = "{0}/{1}/groups".format(self.baseurl, user_item.id) server_response = self.get_request(url, req_options) - logger.info('Populated groups for user (ID: {0})'.format(user_item.id)) + logger.info("Populated groups for user (ID: {0})".format(user_item.id)) group_item = GroupItem.from_response(server_response.content, self.parent_srv.namespace) pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) return group_item, pagination_item diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py index 8c848c295..a00e7f145 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -7,7 +7,7 @@ from contextlib import closing import logging -logger = logging.getLogger('tableau.endpoint.views') +logger = logging.getLogger("tableau.endpoint.views") class Views(QuerysetEndpoint): @@ -27,7 +27,7 @@ def baseurl(self): @api(version="2.2") def get(self, req_options=None, usage=False): - logger.info('Querying all views on site') + logger.info("Querying all views on site") url = self.baseurl if usage: url += "?includeUsageStatistics=true" @@ -41,7 +41,7 @@ def get_by_id(self, view_id): if not view_id: error = "View item missing ID." raise MissingRequiredFieldError(error) - logger.info('Querying single view (ID: {0})'.format(view_id)) + logger.info("Querying single view (ID: {0})".format(view_id)) url = "{0}/{1}".format(self.baseurl, view_id) server_response = self.get_request(url) return ViewItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -56,12 +56,10 @@ def image_fetcher(): return self._get_preview_for_view(view_item) view_item._set_preview_image(image_fetcher) - logger.info('Populated preview image for view (ID: {0})'.format(view_item.id)) + logger.info("Populated preview image for view (ID: {0})".format(view_item.id)) def _get_preview_for_view(self, view_item): - url = "{0}/workbooks/{1}/views/{2}/previewImage".format(self.siteurl, - view_item.workbook_id, - view_item.id) + url = "{0}/workbooks/{1}/views/{2}/previewImage".format(self.siteurl, view_item.workbook_id, view_item.id) server_response = self.get_request(url) image = server_response.content return image @@ -121,15 +119,15 @@ def _get_view_csv(self, view_item, req_options): csv = server_response.iter_content(1024) return csv - @api(version='3.2') + @api(version="3.2") def populate_permissions(self, item): self._permissions.populate(item) - @api(version='3.2') + @api(version="3.2") def update_permissions(self, resource, rules): return self._permissions.update(resource, rules) - @api(version='3.2') + @api(version="3.2") def delete_permission(self, item, capability_item): return self._permissions.delete(item, capability_item) diff --git a/tableauserverclient/server/endpoint/webhooks_endpoint.py b/tableauserverclient/server/endpoint/webhooks_endpoint.py index fe108a27d..6f5135ac1 100644 --- a/tableauserverclient/server/endpoint/webhooks_endpoint.py +++ b/tableauserverclient/server/endpoint/webhooks_endpoint.py @@ -4,7 +4,7 @@ import logging -logger = logging.getLogger('tableau.endpoint.webhooks') +logger = logging.getLogger("tableau.endpoint.webhooks") class Webhooks(Endpoint): @@ -17,7 +17,7 @@ def baseurl(self): @api(version="3.6") def get(self, req_options=None): - logger.info('Querying all Webhooks on site') + logger.info("Querying all Webhooks on site") url = self.baseurl server_response = self.get_request(url, req_options) all_webhook_items = WebhookItem.from_response(server_response.content, self.parent_srv.namespace) @@ -29,7 +29,7 @@ def get_by_id(self, webhook_id): if not webhook_id: error = "Webhook ID undefined." raise ValueError(error) - logger.info('Querying single webhook (ID: {0})'.format(webhook_id)) + logger.info("Querying single webhook (ID: {0})".format(webhook_id)) url = "{0}/{1}".format(self.baseurl, webhook_id) server_response = self.get_request(url) return WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -41,7 +41,7 @@ def delete(self, webhook_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, webhook_id) self.delete_request(url) - logger.info('Deleted single webhook (ID: {0})'.format(webhook_id)) + logger.info("Deleted single webhook (ID: {0})".format(webhook_id)) @api(version="3.6") def create(self, webhook_item): @@ -50,7 +50,7 @@ def create(self, webhook_item): server_response = self.post_request(url, create_req) new_webhook = WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Created new webhook (ID: {0})'.format(new_webhook.id)) + logger.info("Created new webhook (ID: {0})".format(new_webhook.id)) return new_webhook @api(version="3.6") @@ -60,5 +60,5 @@ def test(self, webhook_id): raise ValueError(error) url = "{0}/{1}/test".format(self.baseurl, webhook_id) testOutcome = self.get_request(url) - logger.info('Testing webhook (ID: {0} returned {1})'.format(webhook_id, testOutcome)) + logger.info("Testing webhook (ID: {0} returned {1})".format(webhook_id, testOutcome)) return testOutcome diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index e40d9e1dd..aa72979dd 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -14,11 +14,11 @@ from contextlib import closing # The maximum size of a file that can be published in a single request is 64MB -FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB +FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB -ALLOWED_FILE_EXTENSIONS = ['twb', 'twbx'] +ALLOWED_FILE_EXTENSIONS = ["twb", "twbx"] -logger = logging.getLogger('tableau.endpoint.workbooks') +logger = logging.getLogger("tableau.endpoint.workbooks") class Workbooks(QuerysetEndpoint): @@ -34,13 +34,11 @@ def baseurl(self): # Get all workbooks on site @api(version="2.0") def get(self, req_options=None): - logger.info('Querying all workbooks on site') + logger.info("Querying all workbooks on site") url = self.baseurl server_response = self.get_request(url, req_options) - pagination_item = PaginationItem.from_response( - server_response.content, self.parent_srv.namespace) - all_workbook_items = WorkbookItem.from_response( - server_response.content, self.parent_srv.namespace) + pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) + all_workbook_items = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace) return all_workbook_items, pagination_item # Get 1 workbook @@ -49,14 +47,14 @@ def get_by_id(self, workbook_id): if not workbook_id: error = "Workbook ID undefined." raise ValueError(error) - logger.info('Querying single workbook (ID: {0})'.format(workbook_id)) + logger.info("Querying single workbook (ID: {0})".format(workbook_id)) url = "{0}/{1}".format(self.baseurl, workbook_id) server_response = self.get_request(url) return WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0] @api(version="2.8") def refresh(self, workbook_id): - id_ = getattr(workbook_id, 'id', workbook_id) + id_ = getattr(workbook_id, "id", workbook_id) url = "{0}/{1}/refresh".format(self.baseurl, id_) empty_req = RequestFactory.Empty.empty_req() server_response = self.post_request(url, empty_req) @@ -64,9 +62,9 @@ def refresh(self, workbook_id): return new_job # create one or more extracts on 1 workbook, optionally encrypted - @api(version='3.5') + @api(version="3.5") def create_extract(self, workbook_item, encrypt=False, includeAll=True, datasources=None): - id_ = getattr(workbook_item, 'id', workbook_item) + id_ = getattr(workbook_item, "id", workbook_item) url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt) datasource_req = RequestFactory.Workbook.embedded_extract_req(includeAll, datasources) @@ -75,9 +73,9 @@ def create_extract(self, workbook_item, encrypt=False, includeAll=True, datasour return new_job # delete all the extracts on 1 workbook - @api(version='3.5') + @api(version="3.5") def delete_extract(self, workbook_item): - id_ = getattr(workbook_item, 'id', workbook_item) + id_ = getattr(workbook_item, "id", workbook_item) url = "{0}/{1}/deleteExtract".format(self.baseurl, id_) empty_req = RequestFactory.Empty.empty_req() server_response = self.post_request(url, empty_req) @@ -90,7 +88,7 @@ def delete(self, workbook_id): raise ValueError(error) url = "{0}/{1}".format(self.baseurl, workbook_id) self.delete_request(url) - logger.info('Deleted single workbook (ID: {0})'.format(workbook_id)) + logger.info("Deleted single workbook (ID: {0})".format(workbook_id)) # Update workbook @api(version="2.0") @@ -105,14 +103,15 @@ def update(self, workbook_item): url = "{0}/{1}".format(self.baseurl, workbook_item.id) update_req = RequestFactory.Workbook.update_req(workbook_item) server_response = self.put_request(url, update_req) - logger.info('Updated workbook item (ID: {0})'.format(workbook_item.id)) + logger.info("Updated workbook item (ID: {0})".format(workbook_item.id)) updated_workbook = copy.copy(workbook_item) return updated_workbook._parse_common_tags(server_response.content, self.parent_srv.namespace) @api(version="2.3") def update_conn(self, *args, **kwargs): import warnings - warnings.warn('update_conn is deprecated, please use update_connection instead') + + warnings.warn("update_conn is deprecated, please use update_connection instead") return self.update_connection(*args, **kwargs) # Update workbook_connection @@ -123,14 +122,15 @@ def update_connection(self, workbook_item, connection_item): server_response = self.put_request(url, update_req) connection = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Updated workbook item (ID: {0} & connection item {1})'.format(workbook_item.id, - connection_item.id)) + logger.info( + "Updated workbook item (ID: {0} & connection item {1})".format(workbook_item.id, connection_item.id) + ) return connection # Download workbook contents with option of passing in filepath @api(version="2.0") - @parameter_added_in(no_extract='2.5') - @parameter_added_in(include_extract='2.5') + @parameter_added_in(no_extract="2.5") + @parameter_added_in(include_extract="2.5") def download(self, workbook_id, filepath=None, include_extract=True, no_extract=None): if not workbook_id: error = "Workbook ID undefined." @@ -139,22 +139,23 @@ def download(self, workbook_id, filepath=None, include_extract=True, no_extract= if no_extract is False or no_extract is True: import warnings - warnings.warn('no_extract is deprecated, use include_extract instead.', DeprecationWarning) + + warnings.warn("no_extract is deprecated, use include_extract instead.", DeprecationWarning) include_extract = not no_extract if not include_extract: url += "?includeExtract=False" with closing(self.get_request(url, parameters={"stream": True})) as server_response: - _, params = cgi.parse_header(server_response.headers['Content-Disposition']) - filename = to_filename(os.path.basename(params['filename'])) + _, params = cgi.parse_header(server_response.headers["Content-Disposition"]) + filename = to_filename(os.path.basename(params["filename"])) download_path = make_download_path(filepath, filename) - with open(download_path, 'wb') as f: + with open(download_path, "wb") as f: for chunk in server_response.iter_content(1024): # 1KB f.write(chunk) - logger.info('Downloaded workbook to {0} (ID: {1})'.format(download_path, workbook_id)) + logger.info("Downloaded workbook to {0} (ID: {1})".format(download_path, workbook_id)) return os.path.abspath(download_path) # Get all views of workbook @@ -168,16 +169,14 @@ def view_fetcher(): return self._get_views_for_workbook(workbook_item, usage) workbook_item._set_views(view_fetcher) - logger.info('Populated views for workbook (ID: {0})'.format(workbook_item.id)) + logger.info("Populated views for workbook (ID: {0})".format(workbook_item.id)) def _get_views_for_workbook(self, workbook_item, usage): url = "{0}/{1}/views".format(self.baseurl, workbook_item.id) if usage: url += "?includeUsageStatistics=true" server_response = self.get_request(url) - views = ViewItem.from_response(server_response.content, - self.parent_srv.namespace, - workbook_id=workbook_item.id) + views = ViewItem.from_response(server_response.content, self.parent_srv.namespace, workbook_id=workbook_item.id) return views # Get all connections of workbook @@ -191,7 +190,7 @@ def connection_fetcher(): return self._get_workbook_connections(workbook_item) workbook_item._set_connections(connection_fetcher) - logger.info('Populated connections for workbook (ID: {0})'.format(workbook_item.id)) + logger.info("Populated connections for workbook (ID: {0})".format(workbook_item.id)) def _get_workbook_connections(self, workbook_item, req_options=None): url = "{0}/{1}/connections".format(self.baseurl, workbook_item.id) @@ -229,7 +228,7 @@ def image_fetcher(): return self._get_wb_preview_image(workbook_item) workbook_item._set_preview_image(image_fetcher) - logger.info('Populated preview image for workbook (ID: {0})'.format(workbook_item.id)) + logger.info("Populated preview image for workbook (ID: {0})".format(workbook_item.id)) def _get_wb_preview_image(self, workbook_item): url = "{0}/{1}/previewImage".format(self.baseurl, workbook_item.id) @@ -237,32 +236,38 @@ def _get_wb_preview_image(self, workbook_item): preview_image = server_response.content return preview_image - @api(version='2.0') + @api(version="2.0") def populate_permissions(self, item): self._permissions.populate(item) - @api(version='2.0') + @api(version="2.0") def update_permissions(self, resource, rules): return self._permissions.update(resource, rules) - @api(version='2.0') + @api(version="2.0") def delete_permission(self, item, capability_item): return self._permissions.delete(item, capability_item) # Publishes workbook. Chunking method if file over 64MB @api(version="2.0") - @parameter_added_in(as_job='3.0') - @parameter_added_in(connections='2.8') + @parameter_added_in(as_job="3.0") + @parameter_added_in(connections="2.8") def publish( - self, workbook_item, file, mode, - connection_credentials=None, connections=None, as_job=False, - hidden_views=None, skip_connection_check=False + self, + workbook_item, + file, + mode, + connection_credentials=None, + connections=None, + as_job=False, + hidden_views=None, + skip_connection_check=False, ): if connection_credentials is not None: import warnings - warnings.warn("connection_credentials is being deprecated. Use connections instead", - DeprecationWarning) + + warnings.warn("connection_credentials is being deprecated. Use connections instead", DeprecationWarning) try: # Expect file to be a filepath @@ -278,7 +283,7 @@ def publish( if not workbook_item.name: workbook_item.name = os.path.splitext(filename)[0] if file_extension not in ALLOWED_FILE_EXTENSIONS: - error = "Only {} files can be published as workbooks.".format(', '.join(ALLOWED_FILE_EXTENSIONS)) + error = "Only {} files can be published as workbooks.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) except TypeError: @@ -287,12 +292,12 @@ def publish( file_type = get_file_type(file) - if file_type == 'zip': - file_extension = 'twbx' - elif file_type == 'xml': - file_extension = 'twb' + if file_type == "zip": + file_extension = "twbx" + elif file_type == "xml": + file_extension = "twb" else: - error = 'Unsupported file type {}!'.format(file_type) + error = "Unsupported file type {}!".format(file_type) raise ValueError(error) if not workbook_item.name: @@ -304,51 +309,52 @@ def publish( filename = "{}.{}".format(workbook_item.name, file_extension) if not hasattr(self.parent_srv.PublishMode, mode): - error = 'Invalid mode defined.' + error = "Invalid mode defined." raise ValueError(error) # Construct the url with the defined mode url = "{0}?workbookType={1}".format(self.baseurl, file_extension) if mode == self.parent_srv.PublishMode.Overwrite: - url += '&{0}=true'.format(mode.lower()) + url += "&{0}=true".format(mode.lower()) elif mode == self.parent_srv.PublishMode.Append: - error = 'Workbooks cannot be appended.' + error = "Workbooks cannot be appended." raise ValueError(error) if as_job: - url += '&{0}=true'.format('asJob') + url += "&{0}=true".format("asJob") if skip_connection_check: - url += '&{0}=true'.format('skipConnectionCheck') + url += "&{0}=true".format("skipConnectionCheck") # Determine if chunking is required (64MB is the limit for single upload method) if file_size >= FILESIZE_LIMIT: - logger.info('Publishing {0} to server with chunking method (workbook over 64MB)'.format(workbook_item.name)) + logger.info("Publishing {0} to server with chunking method (workbook over 64MB)".format(workbook_item.name)) upload_session_id = Fileuploads.upload_chunks(self.parent_srv, file) url = "{0}&uploadSessionId={1}".format(url, upload_session_id) conn_creds = connection_credentials - xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(workbook_item, - connection_credentials=conn_creds, - connections=connections, - hidden_views=hidden_views) + xml_request, content_type = RequestFactory.Workbook.publish_req_chunked( + workbook_item, connection_credentials=conn_creds, connections=connections, hidden_views=hidden_views + ) else: - logger.info('Publishing {0} to server'.format(filename)) + logger.info("Publishing {0} to server".format(filename)) try: - with open(file, 'rb') as f: + with open(file, "rb") as f: file_contents = f.read() except TypeError: file_contents = file.read() conn_creds = connection_credentials - xml_request, content_type = RequestFactory.Workbook.publish_req(workbook_item, - filename, - file_contents, - connection_credentials=conn_creds, - connections=connections, - hidden_views=hidden_views) - logger.debug('Request xml: {0} '.format(xml_request[:1000])) + xml_request, content_type = RequestFactory.Workbook.publish_req( + workbook_item, + filename, + file_contents, + connection_credentials=conn_creds, + connections=connections, + hidden_views=hidden_views, + ) + logger.debug("Request xml: {0} ".format(xml_request[:1000])) # Send the publishing request to server try: @@ -360,9 +366,9 @@ def publish( if as_job: new_job = JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (JOB_ID: {1}'.format(workbook_item.name, new_job.id)) + logger.info("Published {0} (JOB_ID: {1}".format(workbook_item.name, new_job.id)) return new_job else: new_workbook = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0] - logger.info('Published {0} (ID: {1})'.format(workbook_item.name, new_workbook.id)) + logger.info("Published {0} (ID: {1})".format(workbook_item.name, new_workbook.id)) return new_workbook diff --git a/tableauserverclient/server/filter.py b/tableauserverclient/server/filter.py index aa42a6439..8802321fd 100644 --- a/tableauserverclient/server/filter.py +++ b/tableauserverclient/server/filter.py @@ -11,8 +11,8 @@ def __init__(self, field, operator, value): def __str__(self): value_string = str(self._value) if isinstance(self._value, list): - value_string = value_string.replace(' ', '').replace('\'', '') - return '{0}:{1}:{2}'.format(self.field, self.operator, value_string) + value_string = value_string.replace(" ", "").replace("'", "") + return "{0}:{1}:{2}".format(self.field, self.operator, value_string) @property def value(self): diff --git a/tableauserverclient/server/pager.py b/tableauserverclient/server/pager.py index 0e2382fae..2de84b4d1 100644 --- a/tableauserverclient/server/pager.py +++ b/tableauserverclient/server/pager.py @@ -14,7 +14,7 @@ class Pager(object): def __init__(self, endpoint, request_opts=None, **kwargs): - if hasattr(endpoint, 'get'): + if hasattr(endpoint, "get"): # The simpliest case is to take an Endpoint and call its get endpoint = partial(endpoint.get, **kwargs) self._endpoint = endpoint @@ -30,7 +30,7 @@ def __init__(self, endpoint, request_opts=None, **kwargs): # If we have options we could be starting on any page, backfill the count if self._options: - self._count = ((self._options.pagenumber - 1) * self._options.pagesize) + self._count = (self._options.pagenumber - 1) * self._options.pagesize else: self._count = 0 self._options = RequestOptions() diff --git a/tableauserverclient/server/query.py b/tableauserverclient/server/query.py index c8ba5e6c6..3dbb830fa 100644 --- a/tableauserverclient/server/query.py +++ b/tableauserverclient/server/query.py @@ -4,11 +4,10 @@ def to_camel_case(word): - return word.split('_')[0] + ''.join(x.capitalize() or '_' for x in word.split('_')[1:]) + return word.split("_")[0] + "".join(x.capitalize() or "_" for x in word.split("_")[1:]) class QuerySet: - def __init__(self, model): self.model = model self.request_options = RequestOptions() diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index e34220188..d2e921479 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -13,13 +13,13 @@ def _add_multipart(parts): multipart_part.make_multipart(content_type=content_type) mime_multipart_parts.append(multipart_part) xml_request, content_type = encode_multipart_formdata(mime_multipart_parts) - content_type = ''.join(('multipart/mixed',) + content_type.partition(';')[1:]) + content_type = "".join(("multipart/mixed",) + content_type.partition(";")[1:]) return xml_request, content_type def _tsrequest_wrapped(func): def wrapper(self, *args, **kwargs): - xml_request = ET.Element('tsRequest') + xml_request = ET.Element("tsRequest") func(self, xml_request, *args, **kwargs) return ET.tostring(xml_request) @@ -27,182 +27,184 @@ def wrapper(self, *args, **kwargs): def _add_connections_element(connections_element, connection): - connection_element = ET.SubElement(connections_element, 'connection') - connection_element.attrib['serverAddress'] = connection.server_address + connection_element = ET.SubElement(connections_element, "connection") + connection_element.attrib["serverAddress"] = connection.server_address if connection.server_port: - connection_element.attrib['serverPort'] = connection.server_port + connection_element.attrib["serverPort"] = connection.server_port if connection.connection_credentials: connection_credentials = connection.connection_credentials _add_credentials_element(connection_element, connection_credentials) def _add_hiddenview_element(views_element, view_name): - view_element = ET.SubElement(views_element, 'view') - view_element.attrib['name'] = view_name - view_element.attrib['hidden'] = "true" + view_element = ET.SubElement(views_element, "view") + view_element.attrib["name"] = view_name + view_element.attrib["hidden"] = "true" def _add_credentials_element(parent_element, connection_credentials): - credentials_element = ET.SubElement(parent_element, 'connectionCredentials') - credentials_element.attrib['name'] = connection_credentials.name - credentials_element.attrib['password'] = connection_credentials.password - credentials_element.attrib['embed'] = 'true' if connection_credentials.embed else 'false' + credentials_element = ET.SubElement(parent_element, "connectionCredentials") + credentials_element.attrib["name"] = connection_credentials.name + credentials_element.attrib["password"] = connection_credentials.password + credentials_element.attrib["embed"] = "true" if connection_credentials.embed else "false" if connection_credentials.oauth: - credentials_element.attrib['oAuth'] = 'true' + credentials_element.attrib["oAuth"] = "true" class AuthRequest(object): def signin_req(self, auth_item): - xml_request = ET.Element('tsRequest') + xml_request = ET.Element("tsRequest") - credentials_element = ET.SubElement(xml_request, 'credentials') + credentials_element = ET.SubElement(xml_request, "credentials") for attribute_name, attribute_value in auth_item.credentials.items(): credentials_element.attrib[attribute_name] = attribute_value - site_element = ET.SubElement(credentials_element, 'site') - site_element.attrib['contentUrl'] = auth_item.site_id + site_element = ET.SubElement(credentials_element, "site") + site_element.attrib["contentUrl"] = auth_item.site_id if auth_item.user_id_to_impersonate: - user_element = ET.SubElement(credentials_element, 'user') - user_element.attrib['id'] = auth_item.user_id_to_impersonate + user_element = ET.SubElement(credentials_element, "user") + user_element.attrib["id"] = auth_item.user_id_to_impersonate return ET.tostring(xml_request) def switch_req(self, site_content_url): - xml_request = ET.Element('tsRequest') + xml_request = ET.Element("tsRequest") - site_element = ET.SubElement(xml_request, 'site') - site_element.attrib['contentUrl'] = site_content_url + site_element = ET.SubElement(xml_request, "site") + site_element.attrib["contentUrl"] = site_content_url return ET.tostring(xml_request) class ColumnRequest(object): def update_req(self, column_item): - xml_request = ET.Element('tsRequest') - column_element = ET.SubElement(xml_request, 'column') + xml_request = ET.Element("tsRequest") + column_element = ET.SubElement(xml_request, "column") if column_item.description: - column_element.attrib['description'] = str(column_item.description) + column_element.attrib["description"] = str(column_item.description) return ET.tostring(xml_request) class DataAlertRequest(object): def add_user_to_alert(self, alert_item, user_id): - xml_request = ET.Element('tsRequest') - user_element = ET.SubElement(xml_request, 'user') - user_element.attrib['id'] = user_id + xml_request = ET.Element("tsRequest") + user_element = ET.SubElement(xml_request, "user") + user_element.attrib["id"] = user_id return ET.tostring(xml_request) def update_req(self, alert_item): - xml_request = ET.Element('tsRequest') - dataAlert_element = ET.SubElement(xml_request, 'dataAlert') - dataAlert_element.attrib['subject'] = alert_item.subject - dataAlert_element.attrib['frequency'] = alert_item.frequency.lower() - dataAlert_element.attrib['public'] = alert_item.public + xml_request = ET.Element("tsRequest") + dataAlert_element = ET.SubElement(xml_request, "dataAlert") + dataAlert_element.attrib["subject"] = alert_item.subject + dataAlert_element.attrib["frequency"] = alert_item.frequency.lower() + dataAlert_element.attrib["public"] = alert_item.public - owner = ET.SubElement(dataAlert_element, 'owner') - owner.attrib['id'] = alert_item.owner_id + owner = ET.SubElement(dataAlert_element, "owner") + owner.attrib["id"] = alert_item.owner_id return ET.tostring(xml_request) class DatabaseRequest(object): def update_req(self, database_item): - xml_request = ET.Element('tsRequest') - database_element = ET.SubElement(xml_request, 'database') + xml_request = ET.Element("tsRequest") + database_element = ET.SubElement(xml_request, "database") if database_item.contact_id: - contact_element = ET.SubElement(database_element, 'contact') - contact_element.attrib['id'] = database_item.contact_id + contact_element = ET.SubElement(database_element, "contact") + contact_element.attrib["id"] = database_item.contact_id - database_element.attrib['isCertified'] = str(database_item.certified).lower() + database_element.attrib["isCertified"] = str(database_item.certified).lower() if database_item.certification_note: - database_element.attrib['certificationNote'] = str(database_item.certification_note) + database_element.attrib["certificationNote"] = str(database_item.certification_note) if database_item.description: - database_element.attrib['description'] = str(database_item.description) + database_element.attrib["description"] = str(database_item.description) return ET.tostring(xml_request) class DatasourceRequest(object): def _generate_xml(self, datasource_item, connection_credentials=None, connections=None): - xml_request = ET.Element('tsRequest') - datasource_element = ET.SubElement(xml_request, 'datasource') - datasource_element.attrib['name'] = datasource_item.name + xml_request = ET.Element("tsRequest") + datasource_element = ET.SubElement(xml_request, "datasource") + datasource_element.attrib["name"] = datasource_item.name if datasource_item.description: - datasource_element.attrib['description'] = str(datasource_item.description) + datasource_element.attrib["description"] = str(datasource_item.description) if datasource_item.use_remote_query_agent is not None: - datasource_element.attrib['useRemoteQueryAgent'] = str(datasource_item.use_remote_query_agent).lower() + datasource_element.attrib["useRemoteQueryAgent"] = str(datasource_item.use_remote_query_agent).lower() if datasource_item.ask_data_enablement: - ask_data_element = ET.SubElement(datasource_element, 'askData') - ask_data_element.attrib['enablement'] = datasource_item.ask_data_enablement + ask_data_element = ET.SubElement(datasource_element, "askData") + ask_data_element.attrib["enablement"] = datasource_item.ask_data_enablement - project_element = ET.SubElement(datasource_element, 'project') - project_element.attrib['id'] = datasource_item.project_id + project_element = ET.SubElement(datasource_element, "project") + project_element.attrib["id"] = datasource_item.project_id if connection_credentials is not None and connections is not None: - raise RuntimeError('You cannot set both `connections` and `connection_credentials`') + raise RuntimeError("You cannot set both `connections` and `connection_credentials`") if connection_credentials is not None: _add_credentials_element(datasource_element, connection_credentials) if connections is not None: - connections_element = ET.SubElement(datasource_element, 'connections') + connections_element = ET.SubElement(datasource_element, "connections") for connection in connections: _add_connections_element(connections_element, connection) return ET.tostring(xml_request) def update_req(self, datasource_item): - xml_request = ET.Element('tsRequest') - datasource_element = ET.SubElement(xml_request, 'datasource') + xml_request = ET.Element("tsRequest") + datasource_element = ET.SubElement(xml_request, "datasource") if datasource_item.ask_data_enablement: - ask_data_element = ET.SubElement(datasource_element, 'askData') - ask_data_element.attrib['enablement'] = datasource_item.ask_data_enablement + ask_data_element = ET.SubElement(datasource_element, "askData") + ask_data_element.attrib["enablement"] = datasource_item.ask_data_enablement if datasource_item.project_id: - project_element = ET.SubElement(datasource_element, 'project') - project_element.attrib['id'] = datasource_item.project_id + project_element = ET.SubElement(datasource_element, "project") + project_element.attrib["id"] = datasource_item.project_id if datasource_item.owner_id: - owner_element = ET.SubElement(datasource_element, 'owner') - owner_element.attrib['id'] = datasource_item.owner_id + owner_element = ET.SubElement(datasource_element, "owner") + owner_element.attrib["id"] = datasource_item.owner_id - datasource_element.attrib['isCertified'] = str(datasource_item.certified).lower() + datasource_element.attrib["isCertified"] = str(datasource_item.certified).lower() if datasource_item.certification_note: - datasource_element.attrib['certificationNote'] = str(datasource_item.certification_note) + datasource_element.attrib["certificationNote"] = str(datasource_item.certification_note) if datasource_item.encrypt_extracts is not None: - datasource_element.attrib['encryptExtracts'] = str(datasource_item.encrypt_extracts).lower() + datasource_element.attrib["encryptExtracts"] = str(datasource_item.encrypt_extracts).lower() return ET.tostring(xml_request) def publish_req(self, datasource_item, filename, file_contents, connection_credentials=None, connections=None): xml_request = self._generate_xml(datasource_item, connection_credentials, connections) - parts = {'request_payload': ('', xml_request, 'text/xml'), - 'tableau_datasource': (filename, file_contents, 'application/octet-stream')} + parts = { + "request_payload": ("", xml_request, "text/xml"), + "tableau_datasource": (filename, file_contents, "application/octet-stream"), + } return _add_multipart(parts) def publish_req_chunked(self, datasource_item, connection_credentials=None, connections=None): xml_request = self._generate_xml(datasource_item, connection_credentials, connections) - parts = {'request_payload': ('', xml_request, 'text/xml')} + parts = {"request_payload": ("", xml_request, "text/xml")} return _add_multipart(parts) class FavoriteRequest(object): def _add_to_req(self, id_, target_type, label): - ''' + """ - ''' - xml_request = ET.Element('tsRequest') - favorite_element = ET.SubElement(xml_request, 'favorite') + """ + xml_request = ET.Element("tsRequest") + favorite_element = ET.SubElement(xml_request, "favorite") target = ET.SubElement(favorite_element, target_type) - favorite_element.attrib['label'] = label - target.attrib['id'] = id_ + favorite_element.attrib["label"] = label + target.attrib["id"] = id_ return ET.tostring(xml_request) @@ -221,208 +223,212 @@ def add_workbook_req(self, id_, name): class FileuploadRequest(object): def chunk_req(self, chunk): - parts = {'request_payload': ('', '', 'text/xml'), - 'tableau_file': ('file', chunk, 'application/octet-stream')} + parts = {"request_payload": ("", "", "text/xml"), "tableau_file": ("file", chunk, "application/octet-stream")} return _add_multipart(parts) class FlowRequest(object): def _generate_xml(self, flow_item, connections=None): - xml_request = ET.Element('tsRequest') - flow_element = ET.SubElement(xml_request, 'flow') - flow_element.attrib['name'] = flow_item.name - project_element = ET.SubElement(flow_element, 'project') - project_element.attrib['id'] = flow_item.project_id + xml_request = ET.Element("tsRequest") + flow_element = ET.SubElement(xml_request, "flow") + flow_element.attrib["name"] = flow_item.name + project_element = ET.SubElement(flow_element, "project") + project_element.attrib["id"] = flow_item.project_id if connections is not None: - connections_element = ET.SubElement(flow_element, 'connections') + connections_element = ET.SubElement(flow_element, "connections") for connection in connections: _add_connections_element(connections_element, connection) return ET.tostring(xml_request) def update_req(self, flow_item): - xml_request = ET.Element('tsRequest') - flow_element = ET.SubElement(xml_request, 'flow') + xml_request = ET.Element("tsRequest") + flow_element = ET.SubElement(xml_request, "flow") if flow_item.project_id: - project_element = ET.SubElement(flow_element, 'project') - project_element.attrib['id'] = flow_item.project_id + project_element = ET.SubElement(flow_element, "project") + project_element.attrib["id"] = flow_item.project_id if flow_item.owner_id: - owner_element = ET.SubElement(flow_element, 'owner') - owner_element.attrib['id'] = flow_item.owner_id + owner_element = ET.SubElement(flow_element, "owner") + owner_element.attrib["id"] = flow_item.owner_id return ET.tostring(xml_request) def publish_req(self, flow_item, filename, file_contents, connections=None): xml_request = self._generate_xml(flow_item, connections) - parts = {'request_payload': ('', xml_request, 'text/xml'), - 'tableau_flow': (filename, file_contents, 'application/octet-stream')} + parts = { + "request_payload": ("", xml_request, "text/xml"), + "tableau_flow": (filename, file_contents, "application/octet-stream"), + } return _add_multipart(parts) def publish_req_chunked(self, flow_item, connections=None): xml_request = self._generate_xml(flow_item, connections) - parts = {'request_payload': ('', xml_request, 'text/xml')} + parts = {"request_payload": ("", xml_request, "text/xml")} return _add_multipart(parts) class GroupRequest(object): def add_user_req(self, user_id): - xml_request = ET.Element('tsRequest') - user_element = ET.SubElement(xml_request, 'user') - user_element.attrib['id'] = user_id + xml_request = ET.Element("tsRequest") + user_element = ET.SubElement(xml_request, "user") + user_element.attrib["id"] = user_id return ET.tostring(xml_request) def create_local_req(self, group_item): - xml_request = ET.Element('tsRequest') - group_element = ET.SubElement(xml_request, 'group') - group_element.attrib['name'] = group_item.name + xml_request = ET.Element("tsRequest") + group_element = ET.SubElement(xml_request, "group") + group_element.attrib["name"] = group_item.name if group_item.minimum_site_role is not None: - group_element.attrib['minimumSiteRole'] = group_item.minimum_site_role + group_element.attrib["minimumSiteRole"] = group_item.minimum_site_role return ET.tostring(xml_request) def create_ad_req(self, group_item): - xml_request = ET.Element('tsRequest') - group_element = ET.SubElement(xml_request, 'group') - group_element.attrib['name'] = group_item.name - import_element = ET.SubElement(group_element, 'import') - import_element.attrib['source'] = "ActiveDirectory" + xml_request = ET.Element("tsRequest") + group_element = ET.SubElement(xml_request, "group") + group_element.attrib["name"] = group_item.name + import_element = ET.SubElement(group_element, "import") + import_element.attrib["source"] = "ActiveDirectory" if group_item.domain_name is None: error = "Group Domain undefined." raise ValueError(error) - import_element.attrib['domainName'] = group_item.domain_name + import_element.attrib["domainName"] = group_item.domain_name if group_item.license_mode is not None: - import_element.attrib['grantLicenseMode'] = group_item.license_mode + import_element.attrib["grantLicenseMode"] = group_item.license_mode if group_item.minimum_site_role is not None: - import_element.attrib['siteRole'] = group_item.minimum_site_role + import_element.attrib["siteRole"] = group_item.minimum_site_role return ET.tostring(xml_request) def update_req(self, group_item, default_site_role=None): # (1/8/2021): Deprecated starting v0.15 if default_site_role is not None: import warnings - warnings.simplefilter('always', DeprecationWarning) - warnings.warn('RequestFactory.Group.update_req(...default_site_role="") is deprecated, ' - 'please set the minimum_site_role field of GroupItem', - DeprecationWarning) + + warnings.simplefilter("always", DeprecationWarning) + warnings.warn( + 'RequestFactory.Group.update_req(...default_site_role="") is deprecated, ' + "please set the minimum_site_role field of GroupItem", + DeprecationWarning, + ) group_item.minimum_site_role = default_site_role - xml_request = ET.Element('tsRequest') - group_element = ET.SubElement(xml_request, 'group') - group_element.attrib['name'] = group_item.name - if group_item.domain_name is not None and group_item.domain_name != 'local': + xml_request = ET.Element("tsRequest") + group_element = ET.SubElement(xml_request, "group") + group_element.attrib["name"] = group_item.name + if group_item.domain_name is not None and group_item.domain_name != "local": # Import element is only accepted in the request for AD groups - import_element = ET.SubElement(group_element, 'import') - import_element.attrib['source'] = "ActiveDirectory" - import_element.attrib['domainName'] = group_item.domain_name - import_element.attrib['siteRole'] = group_item.minimum_site_role + import_element = ET.SubElement(group_element, "import") + import_element.attrib["source"] = "ActiveDirectory" + import_element.attrib["domainName"] = group_item.domain_name + import_element.attrib["siteRole"] = group_item.minimum_site_role if group_item.license_mode is not None: - import_element.attrib['grantLicenseMode'] = group_item.license_mode + import_element.attrib["grantLicenseMode"] = group_item.license_mode else: # Local group request does not accept an 'import' element if group_item.minimum_site_role is not None: - group_element.attrib['minimumSiteRole'] = group_item.minimum_site_role + group_element.attrib["minimumSiteRole"] = group_item.minimum_site_role return ET.tostring(xml_request) class PermissionRequest(object): def add_req(self, rules): - xml_request = ET.Element('tsRequest') - permissions_element = ET.SubElement(xml_request, 'permissions') + xml_request = ET.Element("tsRequest") + permissions_element = ET.SubElement(xml_request, "permissions") for rule in rules: - grantee_capabilities_element = ET.SubElement(permissions_element, 'granteeCapabilities') + grantee_capabilities_element = ET.SubElement(permissions_element, "granteeCapabilities") grantee_element = ET.SubElement(grantee_capabilities_element, rule.grantee.tag_name) - grantee_element.attrib['id'] = rule.grantee.id + grantee_element.attrib["id"] = rule.grantee.id - capabilities_element = ET.SubElement(grantee_capabilities_element, 'capabilities') + capabilities_element = ET.SubElement(grantee_capabilities_element, "capabilities") self._add_all_capabilities(capabilities_element, rule.capabilities) return ET.tostring(xml_request) def _add_all_capabilities(self, capabilities_element, capabilities_map): for name, mode in capabilities_map.items(): - capability_element = ET.SubElement(capabilities_element, 'capability') - capability_element.attrib['name'] = name - capability_element.attrib['mode'] = mode + capability_element = ET.SubElement(capabilities_element, "capability") + capability_element.attrib["name"] = name + capability_element.attrib["mode"] = mode class ProjectRequest(object): def update_req(self, project_item): - xml_request = ET.Element('tsRequest') - project_element = ET.SubElement(xml_request, 'project') + xml_request = ET.Element("tsRequest") + project_element = ET.SubElement(xml_request, "project") if project_item.name: - project_element.attrib['name'] = project_item.name + project_element.attrib["name"] = project_item.name if project_item.description: - project_element.attrib['description'] = project_item.description + project_element.attrib["description"] = project_item.description if project_item.content_permissions: - project_element.attrib['contentPermissions'] = project_item.content_permissions + project_element.attrib["contentPermissions"] = project_item.content_permissions if project_item.parent_id is not None: - project_element.attrib['parentProjectId'] = project_item.parent_id + project_element.attrib["parentProjectId"] = project_item.parent_id return ET.tostring(xml_request) def create_req(self, project_item): - xml_request = ET.Element('tsRequest') - project_element = ET.SubElement(xml_request, 'project') - project_element.attrib['name'] = project_item.name + xml_request = ET.Element("tsRequest") + project_element = ET.SubElement(xml_request, "project") + project_element.attrib["name"] = project_item.name if project_item.description: - project_element.attrib['description'] = project_item.description + project_element.attrib["description"] = project_item.description if project_item.content_permissions: - project_element.attrib['contentPermissions'] = project_item.content_permissions + project_element.attrib["contentPermissions"] = project_item.content_permissions if project_item.parent_id: - project_element.attrib['parentProjectId'] = project_item.parent_id + project_element.attrib["parentProjectId"] = project_item.parent_id return ET.tostring(xml_request) class ScheduleRequest(object): def create_req(self, schedule_item): - xml_request = ET.Element('tsRequest') - schedule_element = ET.SubElement(xml_request, 'schedule') - schedule_element.attrib['name'] = schedule_item.name - schedule_element.attrib['priority'] = str(schedule_item.priority) - schedule_element.attrib['type'] = schedule_item.schedule_type - schedule_element.attrib['executionOrder'] = schedule_item.execution_order + xml_request = ET.Element("tsRequest") + schedule_element = ET.SubElement(xml_request, "schedule") + schedule_element.attrib["name"] = schedule_item.name + schedule_element.attrib["priority"] = str(schedule_item.priority) + schedule_element.attrib["type"] = schedule_item.schedule_type + schedule_element.attrib["executionOrder"] = schedule_item.execution_order interval_item = schedule_item.interval_item - schedule_element.attrib['frequency'] = interval_item._frequency - frequency_element = ET.SubElement(schedule_element, 'frequencyDetails') - frequency_element.attrib['start'] = str(interval_item.start_time) - if hasattr(interval_item, 'end_time') and interval_item.end_time is not None: - frequency_element.attrib['end'] = str(interval_item.end_time) - if hasattr(interval_item, 'interval') and interval_item.interval: - intervals_element = ET.SubElement(frequency_element, 'intervals') + schedule_element.attrib["frequency"] = interval_item._frequency + frequency_element = ET.SubElement(schedule_element, "frequencyDetails") + frequency_element.attrib["start"] = str(interval_item.start_time) + if hasattr(interval_item, "end_time") and interval_item.end_time is not None: + frequency_element.attrib["end"] = str(interval_item.end_time) + if hasattr(interval_item, "interval") and interval_item.interval: + intervals_element = ET.SubElement(frequency_element, "intervals") for interval in interval_item._interval_type_pairs(): expression, value = interval - single_interval_element = ET.SubElement(intervals_element, 'interval') + single_interval_element = ET.SubElement(intervals_element, "interval") single_interval_element.attrib[expression] = value return ET.tostring(xml_request) def update_req(self, schedule_item): - xml_request = ET.Element('tsRequest') - schedule_element = ET.SubElement(xml_request, 'schedule') + xml_request = ET.Element("tsRequest") + schedule_element = ET.SubElement(xml_request, "schedule") if schedule_item.name: - schedule_element.attrib['name'] = schedule_item.name + schedule_element.attrib["name"] = schedule_item.name if schedule_item.priority: - schedule_element.attrib['priority'] = str(schedule_item.priority) + schedule_element.attrib["priority"] = str(schedule_item.priority) if schedule_item.execution_order: - schedule_element.attrib['executionOrder'] = schedule_item.execution_order + schedule_element.attrib["executionOrder"] = schedule_item.execution_order if schedule_item.state: - schedule_element.attrib['state'] = schedule_item.state + schedule_element.attrib["state"] = schedule_item.state interval_item = schedule_item.interval_item if interval_item is not None: if interval_item._frequency: - schedule_element.attrib['frequency'] = interval_item._frequency - frequency_element = ET.SubElement(schedule_element, 'frequencyDetails') - frequency_element.attrib['start'] = str(interval_item.start_time) - if hasattr(interval_item, 'end_time') and interval_item.end_time is not None: - frequency_element.attrib['end'] = str(interval_item.end_time) - intervals_element = ET.SubElement(frequency_element, 'intervals') - if hasattr(interval_item, 'interval'): + schedule_element.attrib["frequency"] = interval_item._frequency + frequency_element = ET.SubElement(schedule_element, "frequencyDetails") + frequency_element.attrib["start"] = str(interval_item.start_time) + if hasattr(interval_item, "end_time") and interval_item.end_time is not None: + frequency_element.attrib["end"] = str(interval_item.end_time) + intervals_element = ET.SubElement(frequency_element, "intervals") + if hasattr(interval_item, "interval"): for interval in interval_item._interval_type_pairs(): (expression, value) = interval - single_interval_element = ET.SubElement(intervals_element, 'interval') + single_interval_element = ET.SubElement(intervals_element, "interval") single_interval_element.attrib[expression] = value return ET.tostring(xml_request) @@ -435,11 +441,11 @@ def _add_to_req(self, id_, target_type, task_type=TaskItem.Type.ExtractRefresh): """ - xml_request = ET.Element('tsRequest') - task_element = ET.SubElement(xml_request, 'task') + xml_request = ET.Element("tsRequest") + task_element = ET.SubElement(xml_request, "task") task = ET.SubElement(task_element, task_type) workbook = ET.SubElement(task, target_type) - workbook.attrib['id'] = id_ + workbook.attrib["id"] = id_ return ET.tostring(xml_request) @@ -452,371 +458,377 @@ def add_datasource_req(self, id_, task_type=TaskItem.Type.ExtractRefresh): class SiteRequest(object): def update_req(self, site_item): - xml_request = ET.Element('tsRequest') - site_element = ET.SubElement(xml_request, 'site') + xml_request = ET.Element("tsRequest") + site_element = ET.SubElement(xml_request, "site") if site_item.name: - site_element.attrib['name'] = site_item.name + site_element.attrib["name"] = site_item.name if site_item.content_url: - site_element.attrib['contentUrl'] = site_item.content_url + site_element.attrib["contentUrl"] = site_item.content_url if site_item.admin_mode: - site_element.attrib['adminMode'] = site_item.admin_mode + site_element.attrib["adminMode"] = site_item.admin_mode if site_item.user_quota: - site_element.attrib['userQuota'] = str(site_item.user_quota) + site_element.attrib["userQuota"] = str(site_item.user_quota) if site_item.state: - site_element.attrib['state'] = site_item.state + site_element.attrib["state"] = site_item.state if site_item.storage_quota: - site_element.attrib['storageQuota'] = str(site_item.storage_quota) + site_element.attrib["storageQuota"] = str(site_item.storage_quota) if site_item.disable_subscriptions is not None: - site_element.attrib['disableSubscriptions'] = str(site_item.disable_subscriptions).lower() + site_element.attrib["disableSubscriptions"] = str(site_item.disable_subscriptions).lower() if site_item.subscribe_others_enabled is not None: - site_element.attrib['subscribeOthersEnabled'] = str(site_item.subscribe_others_enabled).lower() + site_element.attrib["subscribeOthersEnabled"] = str(site_item.subscribe_others_enabled).lower() if site_item.revision_limit: - site_element.attrib['revisionLimit'] = str(site_item.revision_limit) + site_element.attrib["revisionLimit"] = str(site_item.revision_limit) if site_item.revision_history_enabled is not None: - site_element.attrib['revisionHistoryEnabled'] = str(site_item.revision_history_enabled).lower() + site_element.attrib["revisionHistoryEnabled"] = str(site_item.revision_history_enabled).lower() if site_item.data_acceleration_mode is not None: - site_element.attrib['dataAccelerationMode'] = str(site_item.data_acceleration_mode).lower() + site_element.attrib["dataAccelerationMode"] = str(site_item.data_acceleration_mode).lower() if site_item.flows_enabled is not None: - site_element.attrib['flowsEnabled'] = str(site_item.flows_enabled).lower() + site_element.attrib["flowsEnabled"] = str(site_item.flows_enabled).lower() if site_item.cataloging_enabled is not None: - site_element.attrib['catalogingEnabled'] = str(site_item.cataloging_enabled).lower() + site_element.attrib["catalogingEnabled"] = str(site_item.cataloging_enabled).lower() if site_item.editing_flows_enabled is not None: - site_element.attrib['editingFlowsEnabled'] = str(site_item.editing_flows_enabled).lower() + site_element.attrib["editingFlowsEnabled"] = str(site_item.editing_flows_enabled).lower() if site_item.scheduling_flows_enabled is not None: - site_element.attrib['schedulingFlowsEnabled'] = str(site_item.scheduling_flows_enabled).lower() + site_element.attrib["schedulingFlowsEnabled"] = str(site_item.scheduling_flows_enabled).lower() if site_item.allow_subscription_attachments is not None: - site_element.attrib['allowSubscriptionAttachments'] = str(site_item.allow_subscription_attachments).lower() + site_element.attrib["allowSubscriptionAttachments"] = str(site_item.allow_subscription_attachments).lower() if site_item.guest_access_enabled is not None: - site_element.attrib['guestAccessEnabled'] = str(site_item.guest_access_enabled).lower() + site_element.attrib["guestAccessEnabled"] = str(site_item.guest_access_enabled).lower() if site_item.cache_warmup_enabled is not None: - site_element.attrib['cacheWarmupEnabled'] = str(site_item.cache_warmup_enabled).lower() + site_element.attrib["cacheWarmupEnabled"] = str(site_item.cache_warmup_enabled).lower() if site_item.commenting_enabled is not None: - site_element.attrib['commentingEnabled'] = str(site_item.commenting_enabled).lower() + site_element.attrib["commentingEnabled"] = str(site_item.commenting_enabled).lower() if site_item.extract_encryption_mode is not None: - site_element.attrib['extractEncryptionMode'] = str(site_item.extract_encryption_mode).lower() + site_element.attrib["extractEncryptionMode"] = str(site_item.extract_encryption_mode).lower() if site_item.request_access_enabled is not None: - site_element.attrib['requestAccessEnabled'] = str(site_item.request_access_enabled).lower() + site_element.attrib["requestAccessEnabled"] = str(site_item.request_access_enabled).lower() if site_item.run_now_enabled is not None: - site_element.attrib['runNowEnabled'] = str(site_item.run_now_enabled).lower() + site_element.attrib["runNowEnabled"] = str(site_item.run_now_enabled).lower() if site_item.tier_creator_capacity is not None: - site_element.attrib['tierCreatorCapacity'] = str(site_item.tier_creator_capacity).lower() + site_element.attrib["tierCreatorCapacity"] = str(site_item.tier_creator_capacity).lower() if site_item.tier_explorer_capacity is not None: - site_element.attrib['tierExplorerCapacity'] = str(site_item.tier_explorer_capacity).lower() + site_element.attrib["tierExplorerCapacity"] = str(site_item.tier_explorer_capacity).lower() if site_item.tier_viewer_capacity is not None: - site_element.attrib['tierViewerCapacity'] = str(site_item.tier_viewer_capacity).lower() + site_element.attrib["tierViewerCapacity"] = str(site_item.tier_viewer_capacity).lower() if site_item.data_alerts_enabled is not None: - site_element.attrib['dataAlertsEnabled'] = str(site_item.data_alerts_enabled) + site_element.attrib["dataAlertsEnabled"] = str(site_item.data_alerts_enabled) if site_item.commenting_mentions_enabled is not None: - site_element.attrib['commentingMentionsEnabled'] = str(site_item.commenting_mentions_enabled).lower() + site_element.attrib["commentingMentionsEnabled"] = str(site_item.commenting_mentions_enabled).lower() if site_item.catalog_obfuscation_enabled is not None: - site_element.attrib['catalogObfuscationEnabled'] = str(site_item.catalog_obfuscation_enabled).lower() + site_element.attrib["catalogObfuscationEnabled"] = str(site_item.catalog_obfuscation_enabled).lower() if site_item.flow_auto_save_enabled is not None: - site_element.attrib['flowAutoSaveEnabled'] = str(site_item.flow_auto_save_enabled).lower() + site_element.attrib["flowAutoSaveEnabled"] = str(site_item.flow_auto_save_enabled).lower() if site_item.web_extraction_enabled is not None: - site_element.attrib['webExtractionEnabled'] = str(site_item.web_extraction_enabled).lower() + site_element.attrib["webExtractionEnabled"] = str(site_item.web_extraction_enabled).lower() if site_item.metrics_content_type_enabled is not None: - site_element.attrib['metricsContentTypeEnabled'] = str(site_item.metrics_content_type_enabled).lower() + site_element.attrib["metricsContentTypeEnabled"] = str(site_item.metrics_content_type_enabled).lower() if site_item.notify_site_admins_on_throttle is not None: - site_element.attrib['notifySiteAdminsOnThrottle'] = str(site_item.notify_site_admins_on_throttle).lower() + site_element.attrib["notifySiteAdminsOnThrottle"] = str(site_item.notify_site_admins_on_throttle).lower() if site_item.authoring_enabled is not None: - site_element.attrib['authoringEnabled'] = str(site_item.authoring_enabled).lower() + site_element.attrib["authoringEnabled"] = str(site_item.authoring_enabled).lower() if site_item.custom_subscription_email_enabled is not None: - site_element.attrib['customSubscriptionEmailEnabled'] = \ - str(site_item.custom_subscription_email_enabled).lower() + site_element.attrib["customSubscriptionEmailEnabled"] = str( + site_item.custom_subscription_email_enabled + ).lower() if site_item.custom_subscription_email is not None: - site_element.attrib['customSubscriptionEmail'] = str(site_item.custom_subscription_email).lower() + site_element.attrib["customSubscriptionEmail"] = str(site_item.custom_subscription_email).lower() if site_item.custom_subscription_footer_enabled is not None: - site_element.attrib['customSubscriptionFooterEnabled'] =\ - str(site_item.custom_subscription_footer_enabled).lower() + site_element.attrib["customSubscriptionFooterEnabled"] = str( + site_item.custom_subscription_footer_enabled + ).lower() if site_item.custom_subscription_footer is not None: - site_element.attrib['customSubscriptionFooter'] = str(site_item.custom_subscription_footer).lower() + site_element.attrib["customSubscriptionFooter"] = str(site_item.custom_subscription_footer).lower() if site_item.ask_data_mode is not None: - site_element.attrib['askDataMode'] = str(site_item.ask_data_mode) + site_element.attrib["askDataMode"] = str(site_item.ask_data_mode) if site_item.named_sharing_enabled is not None: - site_element.attrib['namedSharingEnabled'] = str(site_item.named_sharing_enabled).lower() + site_element.attrib["namedSharingEnabled"] = str(site_item.named_sharing_enabled).lower() if site_item.mobile_biometrics_enabled is not None: - site_element.attrib['mobileBiometricsEnabled'] = str(site_item.mobile_biometrics_enabled).lower() + site_element.attrib["mobileBiometricsEnabled"] = str(site_item.mobile_biometrics_enabled).lower() if site_item.sheet_image_enabled is not None: - site_element.attrib['sheetImageEnabled'] = str(site_item.sheet_image_enabled).lower() + site_element.attrib["sheetImageEnabled"] = str(site_item.sheet_image_enabled).lower() if site_item.derived_permissions_enabled is not None: - site_element.attrib['derivedPermissionsEnabled'] = str(site_item.derived_permissions_enabled).lower() + site_element.attrib["derivedPermissionsEnabled"] = str(site_item.derived_permissions_enabled).lower() if site_item.user_visibility_mode is not None: - site_element.attrib['userVisibilityMode'] = str(site_item.user_visibility_mode) + site_element.attrib["userVisibilityMode"] = str(site_item.user_visibility_mode) if site_item.use_default_time_zone is not None: - site_element.attrib['useDefaultTimeZone'] = str(site_item.use_default_time_zone).lower() + site_element.attrib["useDefaultTimeZone"] = str(site_item.use_default_time_zone).lower() if site_item.time_zone is not None: - site_element.attrib['timeZone'] = str(site_item.time_zone) + site_element.attrib["timeZone"] = str(site_item.time_zone) if site_item.auto_suspend_refresh_enabled is not None: - site_element.attrib['autoSuspendRefreshEnabled'] = str(site_item.auto_suspend_refresh_enabled).lower() + site_element.attrib["autoSuspendRefreshEnabled"] = str(site_item.auto_suspend_refresh_enabled).lower() if site_item.auto_suspend_refresh_inactivity_window is not None: - site_element.attrib['autoSuspendRefreshInactivityWindow'] =\ - str(site_item.auto_suspend_refresh_inactivity_window) + site_element.attrib["autoSuspendRefreshInactivityWindow"] = str( + site_item.auto_suspend_refresh_inactivity_window + ) return ET.tostring(xml_request) def create_req(self, site_item): - xml_request = ET.Element('tsRequest') - site_element = ET.SubElement(xml_request, 'site') - site_element.attrib['name'] = site_item.name - site_element.attrib['contentUrl'] = site_item.content_url + xml_request = ET.Element("tsRequest") + site_element = ET.SubElement(xml_request, "site") + site_element.attrib["name"] = site_item.name + site_element.attrib["contentUrl"] = site_item.content_url if site_item.admin_mode: - site_element.attrib['adminMode'] = site_item.admin_mode + site_element.attrib["adminMode"] = site_item.admin_mode if site_item.user_quota: - site_element.attrib['userQuota'] = str(site_item.user_quota) + site_element.attrib["userQuota"] = str(site_item.user_quota) if site_item.storage_quota: - site_element.attrib['storageQuota'] = str(site_item.storage_quota) + site_element.attrib["storageQuota"] = str(site_item.storage_quota) if site_item.disable_subscriptions is not None: - site_element.attrib['disableSubscriptions'] = str(site_item.disable_subscriptions).lower() + site_element.attrib["disableSubscriptions"] = str(site_item.disable_subscriptions).lower() if site_item.subscribe_others_enabled is not None: - site_element.attrib['subscribeOthersEnabled'] = str(site_item.subscribe_others_enabled).lower() + site_element.attrib["subscribeOthersEnabled"] = str(site_item.subscribe_others_enabled).lower() if site_item.revision_limit: - site_element.attrib['revisionLimit'] = str(site_item.revision_limit) + site_element.attrib["revisionLimit"] = str(site_item.revision_limit) if site_item.data_acceleration_mode is not None: - site_element.attrib['dataAccelerationMode'] = str(site_item.data_acceleration_mode).lower() + site_element.attrib["dataAccelerationMode"] = str(site_item.data_acceleration_mode).lower() if site_item.flows_enabled is not None: - site_element.attrib['flowsEnabled'] = str(site_item.flows_enabled).lower() + site_element.attrib["flowsEnabled"] = str(site_item.flows_enabled).lower() if site_item.editing_flows_enabled is not None: - site_element.attrib['editingFlowsEnabled'] = str(site_item.editing_flows_enabled).lower() + site_element.attrib["editingFlowsEnabled"] = str(site_item.editing_flows_enabled).lower() if site_item.scheduling_flows_enabled is not None: - site_element.attrib['schedulingFlowsEnabled'] = str(site_item.scheduling_flows_enabled).lower() + site_element.attrib["schedulingFlowsEnabled"] = str(site_item.scheduling_flows_enabled).lower() if site_item.allow_subscription_attachments is not None: - site_element.attrib['allowSubscriptionAttachments'] = str(site_item.allow_subscription_attachments).lower() + site_element.attrib["allowSubscriptionAttachments"] = str(site_item.allow_subscription_attachments).lower() if site_item.guest_access_enabled is not None: - site_element.attrib['guestAccessEnabled'] = str(site_item.guest_access_enabled).lower() + site_element.attrib["guestAccessEnabled"] = str(site_item.guest_access_enabled).lower() if site_item.cache_warmup_enabled is not None: - site_element.attrib['cacheWarmupEnabled'] = str(site_item.cache_warmup_enabled).lower() + site_element.attrib["cacheWarmupEnabled"] = str(site_item.cache_warmup_enabled).lower() if site_item.commenting_enabled is not None: - site_element.attrib['commentingEnabled'] = str(site_item.commenting_enabled).lower() + site_element.attrib["commentingEnabled"] = str(site_item.commenting_enabled).lower() if site_item.revision_history_enabled is not None: - site_element.attrib['revisionHistoryEnabled'] = str(site_item.revision_history_enabled).lower() + site_element.attrib["revisionHistoryEnabled"] = str(site_item.revision_history_enabled).lower() if site_item.extract_encryption_mode is not None: - site_element.attrib['extractEncryptionMode'] = str(site_item.extract_encryption_mode).lower() + site_element.attrib["extractEncryptionMode"] = str(site_item.extract_encryption_mode).lower() if site_item.request_access_enabled is not None: - site_element.attrib['requestAccessEnabled'] = str(site_item.request_access_enabled).lower() + site_element.attrib["requestAccessEnabled"] = str(site_item.request_access_enabled).lower() if site_item.run_now_enabled is not None: - site_element.attrib['runNowEnabled'] = str(site_item.run_now_enabled).lower() + site_element.attrib["runNowEnabled"] = str(site_item.run_now_enabled).lower() if site_item.tier_creator_capacity is not None: - site_element.attrib['tierCreatorCapacity'] = str(site_item.tier_creator_capacity).lower() + site_element.attrib["tierCreatorCapacity"] = str(site_item.tier_creator_capacity).lower() if site_item.tier_explorer_capacity is not None: - site_element.attrib['tierExplorerCapacity'] = str(site_item.tier_explorer_capacity).lower() + site_element.attrib["tierExplorerCapacity"] = str(site_item.tier_explorer_capacity).lower() if site_item.tier_viewer_capacity is not None: - site_element.attrib['tierViewerCapacity'] = str(site_item.tier_viewer_capacity).lower() + site_element.attrib["tierViewerCapacity"] = str(site_item.tier_viewer_capacity).lower() if site_item.data_alerts_enabled is not None: - site_element.attrib['dataAlertsEnabled'] = str(site_item.data_alerts_enabled).lower() + site_element.attrib["dataAlertsEnabled"] = str(site_item.data_alerts_enabled).lower() if site_item.commenting_mentions_enabled is not None: - site_element.attrib['commentingMentionsEnabled'] = str(site_item.commenting_mentions_enabled).lower() + site_element.attrib["commentingMentionsEnabled"] = str(site_item.commenting_mentions_enabled).lower() if site_item.catalog_obfuscation_enabled is not None: - site_element.attrib['catalogObfuscationEnabled'] = str(site_item.catalog_obfuscation_enabled).lower() + site_element.attrib["catalogObfuscationEnabled"] = str(site_item.catalog_obfuscation_enabled).lower() if site_item.flow_auto_save_enabled is not None: - site_element.attrib['flowAutoSaveEnabled'] = str(site_item.flow_auto_save_enabled).lower() + site_element.attrib["flowAutoSaveEnabled"] = str(site_item.flow_auto_save_enabled).lower() if site_item.web_extraction_enabled is not None: - site_element.attrib['webExtractionEnabled'] = str(site_item.web_extraction_enabled).lower() + site_element.attrib["webExtractionEnabled"] = str(site_item.web_extraction_enabled).lower() if site_item.metrics_content_type_enabled is not None: - site_element.attrib['metricsContentTypeEnabled'] = str(site_item.metrics_content_type_enabled).lower() + site_element.attrib["metricsContentTypeEnabled"] = str(site_item.metrics_content_type_enabled).lower() if site_item.notify_site_admins_on_throttle is not None: - site_element.attrib['notifySiteAdminsOnThrottle'] = str(site_item.notify_site_admins_on_throttle).lower() + site_element.attrib["notifySiteAdminsOnThrottle"] = str(site_item.notify_site_admins_on_throttle).lower() if site_item.authoring_enabled is not None: - site_element.attrib['authoringEnabled'] = str(site_item.authoring_enabled).lower() + site_element.attrib["authoringEnabled"] = str(site_item.authoring_enabled).lower() if site_item.custom_subscription_email_enabled is not None: - site_element.attrib['customSubscriptionEmailEnabled'] =\ - str(site_item.custom_subscription_email_enabled).lower() + site_element.attrib["customSubscriptionEmailEnabled"] = str( + site_item.custom_subscription_email_enabled + ).lower() if site_item.custom_subscription_email is not None: - site_element.attrib['customSubscriptionEmail'] = str(site_item.custom_subscription_email).lower() + site_element.attrib["customSubscriptionEmail"] = str(site_item.custom_subscription_email).lower() if site_item.custom_subscription_footer_enabled is not None: - site_element.attrib['customSubscriptionFooterEnabled'] =\ - str(site_item.custom_subscription_footer_enabled).lower() + site_element.attrib["customSubscriptionFooterEnabled"] = str( + site_item.custom_subscription_footer_enabled + ).lower() if site_item.custom_subscription_footer is not None: - site_element.attrib['customSubscriptionFooter'] = str(site_item.custom_subscription_footer).lower() + site_element.attrib["customSubscriptionFooter"] = str(site_item.custom_subscription_footer).lower() if site_item.ask_data_mode is not None: - site_element.attrib['askDataMode'] = str(site_item.ask_data_mode) + site_element.attrib["askDataMode"] = str(site_item.ask_data_mode) if site_item.named_sharing_enabled is not None: - site_element.attrib['namedSharingEnabled'] = str(site_item.named_sharing_enabled).lower() + site_element.attrib["namedSharingEnabled"] = str(site_item.named_sharing_enabled).lower() if site_item.mobile_biometrics_enabled is not None: - site_element.attrib['mobileBiometricsEnabled'] = str(site_item.mobile_biometrics_enabled).lower() + site_element.attrib["mobileBiometricsEnabled"] = str(site_item.mobile_biometrics_enabled).lower() if site_item.sheet_image_enabled is not None: - site_element.attrib['sheetImageEnabled'] = str(site_item.sheet_image_enabled).lower() + site_element.attrib["sheetImageEnabled"] = str(site_item.sheet_image_enabled).lower() if site_item.cataloging_enabled is not None: - site_element.attrib['catalogingEnabled'] = str(site_item.cataloging_enabled).lower() + site_element.attrib["catalogingEnabled"] = str(site_item.cataloging_enabled).lower() if site_item.derived_permissions_enabled is not None: - site_element.attrib['derivedPermissionsEnabled'] = str(site_item.derived_permissions_enabled).lower() + site_element.attrib["derivedPermissionsEnabled"] = str(site_item.derived_permissions_enabled).lower() if site_item.user_visibility_mode is not None: - site_element.attrib['userVisibilityMode'] = str(site_item.user_visibility_mode) + site_element.attrib["userVisibilityMode"] = str(site_item.user_visibility_mode) if site_item.use_default_time_zone is not None: - site_element.attrib['useDefaultTimeZone'] = str(site_item.use_default_time_zone).lower() + site_element.attrib["useDefaultTimeZone"] = str(site_item.use_default_time_zone).lower() if site_item.time_zone is not None: - site_element.attrib['timeZone'] = str(site_item.time_zone) + site_element.attrib["timeZone"] = str(site_item.time_zone) if site_item.auto_suspend_refresh_enabled is not None: - site_element.attrib['autoSuspendRefreshEnabled'] = str(site_item.auto_suspend_refresh_enabled).lower() + site_element.attrib["autoSuspendRefreshEnabled"] = str(site_item.auto_suspend_refresh_enabled).lower() if site_item.auto_suspend_refresh_inactivity_window is not None: - site_element.attrib['autoSuspendRefreshInactivityWindow'] =\ - str(site_item.auto_suspend_refresh_inactivity_window) + site_element.attrib["autoSuspendRefreshInactivityWindow"] = str( + site_item.auto_suspend_refresh_inactivity_window + ) return ET.tostring(xml_request) class TableRequest(object): def update_req(self, table_item): - xml_request = ET.Element('tsRequest') - table_element = ET.SubElement(xml_request, 'table') + xml_request = ET.Element("tsRequest") + table_element = ET.SubElement(xml_request, "table") if table_item.contact_id: - contact_element = ET.SubElement(table_element, 'contact') - contact_element.attrib['id'] = table_item.contact_id + contact_element = ET.SubElement(table_element, "contact") + contact_element.attrib["id"] = table_item.contact_id - table_element.attrib['isCertified'] = str(table_item.certified).lower() + table_element.attrib["isCertified"] = str(table_item.certified).lower() if table_item.certification_note: - table_element.attrib['certificationNote'] = str(table_item.certification_note) + table_element.attrib["certificationNote"] = str(table_item.certification_note) if table_item.description: - table_element.attrib['description'] = str(table_item.description) + table_element.attrib["description"] = str(table_item.description) return ET.tostring(xml_request) class TagRequest(object): def add_req(self, tag_set): - xml_request = ET.Element('tsRequest') - tags_element = ET.SubElement(xml_request, 'tags') + xml_request = ET.Element("tsRequest") + tags_element = ET.SubElement(xml_request, "tags") for tag in tag_set: - tag_element = ET.SubElement(tags_element, 'tag') - tag_element.attrib['label'] = tag + tag_element = ET.SubElement(tags_element, "tag") + tag_element.attrib["label"] = tag return ET.tostring(xml_request) class UserRequest(object): def update_req(self, user_item, password): - xml_request = ET.Element('tsRequest') - user_element = ET.SubElement(xml_request, 'user') + xml_request = ET.Element("tsRequest") + user_element = ET.SubElement(xml_request, "user") if user_item.fullname: - user_element.attrib['fullName'] = user_item.fullname + user_element.attrib["fullName"] = user_item.fullname if user_item.email: - user_element.attrib['email'] = user_item.email + user_element.attrib["email"] = user_item.email if user_item.site_role: - if user_item.site_role != 'ServerAdministrator': - user_element.attrib['siteRole'] = user_item.site_role + if user_item.site_role != "ServerAdministrator": + user_element.attrib["siteRole"] = user_item.site_role if user_item.auth_setting: - user_element.attrib['authSetting'] = user_item.auth_setting + user_element.attrib["authSetting"] = user_item.auth_setting if password: - user_element.attrib['password'] = password + user_element.attrib["password"] = password return ET.tostring(xml_request) def add_req(self, user_item): - xml_request = ET.Element('tsRequest') - user_element = ET.SubElement(xml_request, 'user') - user_element.attrib['name'] = user_item.name - user_element.attrib['siteRole'] = user_item.site_role + xml_request = ET.Element("tsRequest") + user_element = ET.SubElement(xml_request, "user") + user_element.attrib["name"] = user_item.name + user_element.attrib["siteRole"] = user_item.site_role if user_item.auth_setting: - user_element.attrib['authSetting'] = user_item.auth_setting + user_element.attrib["authSetting"] = user_item.auth_setting return ET.tostring(xml_request) class WorkbookRequest(object): - def _generate_xml( - self, workbook_item, - connection_credentials=None, connections=None, - hidden_views=None - ): - xml_request = ET.Element('tsRequest') - workbook_element = ET.SubElement(xml_request, 'workbook') - workbook_element.attrib['name'] = workbook_item.name + def _generate_xml(self, workbook_item, connection_credentials=None, connections=None, hidden_views=None): + xml_request = ET.Element("tsRequest") + workbook_element = ET.SubElement(xml_request, "workbook") + workbook_element.attrib["name"] = workbook_item.name if workbook_item.show_tabs: - workbook_element.attrib['showTabs'] = str(workbook_item.show_tabs).lower() - project_element = ET.SubElement(workbook_element, 'project') - project_element.attrib['id'] = str(workbook_item.project_id) + workbook_element.attrib["showTabs"] = str(workbook_item.show_tabs).lower() + project_element = ET.SubElement(workbook_element, "project") + project_element.attrib["id"] = str(workbook_item.project_id) if connection_credentials is not None and connections is not None: - raise RuntimeError('You cannot set both `connections` and `connection_credentials`') + raise RuntimeError("You cannot set both `connections` and `connection_credentials`") if connection_credentials is not None: _add_credentials_element(workbook_element, connection_credentials) if connections is not None: - connections_element = ET.SubElement(workbook_element, 'connections') + connections_element = ET.SubElement(workbook_element, "connections") for connection in connections: _add_connections_element(connections_element, connection) if hidden_views is not None: - views_element = ET.SubElement(workbook_element, 'views') + views_element = ET.SubElement(workbook_element, "views") for view_name in hidden_views: _add_hiddenview_element(views_element, view_name) return ET.tostring(xml_request) def update_req(self, workbook_item): - xml_request = ET.Element('tsRequest') - workbook_element = ET.SubElement(xml_request, 'workbook') + xml_request = ET.Element("tsRequest") + workbook_element = ET.SubElement(xml_request, "workbook") if workbook_item.name: - workbook_element.attrib['name'] = workbook_item.name + workbook_element.attrib["name"] = workbook_item.name if workbook_item.show_tabs is not None: - workbook_element.attrib['showTabs'] = str(workbook_item.show_tabs).lower() + workbook_element.attrib["showTabs"] = str(workbook_item.show_tabs).lower() if workbook_item.project_id: - project_element = ET.SubElement(workbook_element, 'project') - project_element.attrib['id'] = workbook_item.project_id + project_element = ET.SubElement(workbook_element, "project") + project_element.attrib["id"] = workbook_item.project_id if workbook_item.owner_id: - owner_element = ET.SubElement(workbook_element, 'owner') - owner_element.attrib['id'] = workbook_item.owner_id - if workbook_item.data_acceleration_config['acceleration_enabled'] is not None: + owner_element = ET.SubElement(workbook_element, "owner") + owner_element.attrib["id"] = workbook_item.owner_id + if workbook_item.data_acceleration_config["acceleration_enabled"] is not None: data_acceleration_config = workbook_item.data_acceleration_config - data_acceleration_element = ET.SubElement(workbook_element, 'dataAccelerationConfig') - data_acceleration_element.attrib['accelerationEnabled'] = str(data_acceleration_config - ["acceleration_enabled"]).lower() - if data_acceleration_config['accelerate_now'] is not None: - data_acceleration_element.attrib['accelerateNow'] = str(data_acceleration_config - ["accelerate_now"]).lower() + data_acceleration_element = ET.SubElement(workbook_element, "dataAccelerationConfig") + data_acceleration_element.attrib["accelerationEnabled"] = str( + data_acceleration_config["acceleration_enabled"] + ).lower() + if data_acceleration_config["accelerate_now"] is not None: + data_acceleration_element.attrib["accelerateNow"] = str( + data_acceleration_config["accelerate_now"] + ).lower() return ET.tostring(xml_request) def publish_req( - self, workbook_item, filename, file_contents, - connection_credentials=None, connections=None, hidden_views=None + self, workbook_item, filename, file_contents, connection_credentials=None, connections=None, hidden_views=None ): - xml_request = self._generate_xml(workbook_item, - connection_credentials=connection_credentials, - connections=connections, - hidden_views=hidden_views) - - parts = {'request_payload': ('', xml_request, 'text/xml'), - 'tableau_workbook': (filename, file_contents, 'application/octet-stream')} + xml_request = self._generate_xml( + workbook_item, + connection_credentials=connection_credentials, + connections=connections, + hidden_views=hidden_views, + ) + + parts = { + "request_payload": ("", xml_request, "text/xml"), + "tableau_workbook": (filename, file_contents, "application/octet-stream"), + } return _add_multipart(parts) - def publish_req_chunked( - self, workbook_item, connection_credentials=None, connections=None, - hidden_views=None - ): - xml_request = self._generate_xml(workbook_item, - connection_credentials=connection_credentials, - connections=connections, - hidden_views=hidden_views) + def publish_req_chunked(self, workbook_item, connection_credentials=None, connections=None, hidden_views=None): + xml_request = self._generate_xml( + workbook_item, + connection_credentials=connection_credentials, + connections=connections, + hidden_views=hidden_views, + ) - parts = {'request_payload': ('', xml_request, 'text/xml')} + parts = {"request_payload": ("", xml_request, "text/xml")} return _add_multipart(parts) @_tsrequest_wrapped def embedded_extract_req(self, xml_request, include_all=True, datasources=None): - list_element = ET.SubElement(xml_request, 'datasources') + list_element = ET.SubElement(xml_request, "datasources") if include_all: - list_element.attrib['includeAll'] = "true" + list_element.attrib["includeAll"] = "true" else: for datasource_item in datasources: - datasource_element = list_element.SubElement(xml_request, 'datasource') - datasource_element.attrib['id'] = datasource_item.id + datasource_element = list_element.SubElement(xml_request, "datasource") + datasource_element.attrib["id"] = datasource_item.id class Connection(object): @_tsrequest_wrapped def update_req(self, xml_request, connection_item): - connection_element = ET.SubElement(xml_request, 'connection') + connection_element = ET.SubElement(xml_request, "connection") if connection_item.server_address: - connection_element.attrib['serverAddress'] = connection_item.server_address.lower() + connection_element.attrib["serverAddress"] = connection_item.server_address.lower() if connection_item.server_port: - connection_element.attrib['serverPort'] = str(connection_item.server_port) + connection_element.attrib["serverPort"] = str(connection_item.server_port) if connection_item.username: - connection_element.attrib['userName'] = connection_item.username + connection_element.attrib["userName"] = connection_item.username if connection_item.password: - connection_element.attrib['password'] = connection_item.password + connection_element.attrib["password"] = connection_item.password if connection_item.embed_password is not None: - connection_element.attrib['embedPassword'] = str(connection_item.embed_password).lower() + connection_element.attrib["embedPassword"] = str(connection_item.embed_password).lower() class TaskRequest(object): @@ -829,64 +841,64 @@ def run_req(self, xml_request, task_item): class SubscriptionRequest(object): @_tsrequest_wrapped def create_req(self, xml_request, subscription_item): - subscription_element = ET.SubElement(xml_request, 'subscription') + subscription_element = ET.SubElement(xml_request, "subscription") # Main attributes - subscription_element.attrib['subject'] = subscription_item.subject + subscription_element.attrib["subject"] = subscription_item.subject if subscription_item.attach_image is not None: - subscription_element.attrib['attachImage'] = str(subscription_item.attach_image).lower() + subscription_element.attrib["attachImage"] = str(subscription_item.attach_image).lower() if subscription_item.attach_pdf is not None: - subscription_element.attrib['attachPdf'] = str(subscription_item.attach_pdf).lower() + subscription_element.attrib["attachPdf"] = str(subscription_item.attach_pdf).lower() if subscription_item.message is not None: - subscription_element.attrib['message'] = subscription_item.message + subscription_element.attrib["message"] = subscription_item.message if subscription_item.page_orientation is not None: - subscription_element.attrib['pageOrientation'] = subscription_item.page_orientation + subscription_element.attrib["pageOrientation"] = subscription_item.page_orientation if subscription_item.page_size_option is not None: - subscription_element.attrib['pageSizeOption'] = subscription_item.page_size_option + subscription_element.attrib["pageSizeOption"] = subscription_item.page_size_option # Content element - content_element = ET.SubElement(subscription_element, 'content') - content_element.attrib['id'] = subscription_item.target.id - content_element.attrib['type'] = subscription_item.target.type + content_element = ET.SubElement(subscription_element, "content") + content_element.attrib["id"] = subscription_item.target.id + content_element.attrib["type"] = subscription_item.target.type if subscription_item.send_if_view_empty is not None: - content_element.attrib['sendIfViewEmpty'] = str(subscription_item.send_if_view_empty).lower() + content_element.attrib["sendIfViewEmpty"] = str(subscription_item.send_if_view_empty).lower() # Schedule element - schedule_element = ET.SubElement(subscription_element, 'schedule') - schedule_element.attrib['id'] = subscription_item.schedule_id + schedule_element = ET.SubElement(subscription_element, "schedule") + schedule_element.attrib["id"] = subscription_item.schedule_id # User element - user_element = ET.SubElement(subscription_element, 'user') - user_element.attrib['id'] = subscription_item.user_id + user_element = ET.SubElement(subscription_element, "user") + user_element.attrib["id"] = subscription_item.user_id return ET.tostring(xml_request) @_tsrequest_wrapped def update_req(self, xml_request, subscription_item): - subscription = ET.SubElement(xml_request, 'subscription') + subscription = ET.SubElement(xml_request, "subscription") # Main attributes if subscription_item.subject is not None: - subscription.attrib['subject'] = subscription_item.subject + subscription.attrib["subject"] = subscription_item.subject if subscription_item.attach_image is not None: - subscription.attrib['attachImage'] = str(subscription_item.attach_image).lower() + subscription.attrib["attachImage"] = str(subscription_item.attach_image).lower() if subscription_item.attach_pdf is not None: - subscription.attrib['attachPdf'] = str(subscription_item.attach_pdf).lower() + subscription.attrib["attachPdf"] = str(subscription_item.attach_pdf).lower() if subscription_item.page_orientation is not None: - subscription.attrib['pageOrientation'] = subscription_item.page_orientation + subscription.attrib["pageOrientation"] = subscription_item.page_orientation if subscription_item.page_size_option is not None: - subscription.attrib['pageSizeOption'] = subscription_item.page_size_option + subscription.attrib["pageSizeOption"] = subscription_item.page_size_option if subscription_item.suspended is not None: - subscription.attrib['suspended'] = str(subscription_item.suspended).lower() + subscription.attrib["suspended"] = str(subscription_item.suspended).lower() # Schedule element - schedule = ET.SubElement(subscription, 'schedule') + schedule = ET.SubElement(subscription, "schedule") if subscription_item.schedule_id is not None: - schedule.attrib['id'] = subscription_item.schedule_id + schedule.attrib["id"] = subscription_item.schedule_id # Content element - content = ET.SubElement(subscription, 'content') + content = ET.SubElement(subscription, "content") if subscription_item.send_if_view_empty is not None: - content.attrib['sendIfViewEmpty'] = str(subscription_item.send_if_view_empty).lower() + content.attrib["sendIfViewEmpty"] = str(subscription_item.send_if_view_empty).lower() return ET.tostring(xml_request) @@ -899,16 +911,16 @@ def empty_req(self, xml_request): class WebhookRequest(object): @_tsrequest_wrapped def create_req(self, xml_request, webhook_item): - webhook = ET.SubElement(xml_request, 'webhook') - webhook.attrib['name'] = webhook_item.name + webhook = ET.SubElement(xml_request, "webhook") + webhook.attrib["name"] = webhook_item.name - source = ET.SubElement(webhook, 'webhook-source') + source = ET.SubElement(webhook, "webhook-source") ET.SubElement(source, webhook_item._event) - destination = ET.SubElement(webhook, 'webhook-destination') - post = ET.SubElement(destination, 'webhook-destination-http') - post.attrib['method'] = 'POST' - post.attrib['url'] = webhook_item.url + destination = ET.SubElement(webhook, "webhook-destination") + post = ET.SubElement(destination, "webhook-destination-http") + post.attrib["method"] = "POST" + post.attrib["url"] = webhook_item.url return ET.tostring(xml_request) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 22d0a4ef0..23d10b3d6 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -8,11 +8,11 @@ def apply_query_params(self, url): params = self.get_query_params() params_list = ["{}={}".format(k, v) for (k, v) in params.items()] - if '?' in url: - url, existing_params = url.split('?') + if "?" in url: + url, existing_params = url.split("?") params_list.append(existing_params) - return "{0}?{1}".format(url, '&'.join(params_list)) + return "{0}?{1}".format(url, "&".join(params_list)) except NotImplementedError: raise @@ -22,44 +22,44 @@ def get_query_params(self): class RequestOptions(RequestOptionsBase): class Operator: - Equals = 'eq' - GreaterThan = 'gt' - GreaterThanOrEqual = 'gte' - LessThan = 'lt' - LessThanOrEqual = 'lte' - In = 'in' - Has = 'has' + Equals = "eq" + GreaterThan = "gt" + GreaterThanOrEqual = "gte" + LessThan = "lt" + LessThanOrEqual = "lte" + In = "in" + Has = "has" class Field: - Args = 'args' - CompletedAt = 'completedAt' - CreatedAt = 'createdAt' - DomainName = 'domainName' - DomainNickname = 'domainNickname' - HitsTotal = 'hitsTotal' - IsLocal = 'isLocal' - JobType = 'jobType' - LastLogin = 'lastLogin' - MinimumSiteRole = 'minimumSiteRole' - Name = 'name' - Notes = 'notes' - OwnerDomain = 'ownerDomain' - OwnerEmail = 'ownerEmail' - OwnerName = 'ownerName' - Progress = 'progress' - ProjectName = 'projectName' - SiteRole = 'siteRole' - Subtitle = 'subtitle' - Tags = 'tags' - Title = 'title' - TopLevelProject = 'topLevelProject' - Type = 'type' - UpdatedAt = 'updatedAt' - UserCount = 'userCount' + Args = "args" + CompletedAt = "completedAt" + CreatedAt = "createdAt" + DomainName = "domainName" + DomainNickname = "domainNickname" + HitsTotal = "hitsTotal" + IsLocal = "isLocal" + JobType = "jobType" + LastLogin = "lastLogin" + MinimumSiteRole = "minimumSiteRole" + Name = "name" + Notes = "notes" + OwnerDomain = "ownerDomain" + OwnerEmail = "ownerEmail" + OwnerName = "ownerName" + Progress = "progress" + ProjectName = "projectName" + SiteRole = "siteRole" + Subtitle = "subtitle" + Tags = "tags" + Title = "title" + TopLevelProject = "topLevelProject" + Type = "type" + UpdatedAt = "updatedAt" + UserCount = "userCount" class Direction: - Desc = 'desc' - Asc = 'asc' + Desc = "desc" + Asc = "asc" def __init__(self, pagenumber=1, pagesize=100): self.pagenumber = pagenumber @@ -81,19 +81,19 @@ def page_number(self, page_number): def get_query_params(self): params = {} if self.pagenumber: - params['pageNumber'] = self.pagenumber + params["pageNumber"] = self.pagenumber if self.pagesize: - params['pageSize'] = self.pagesize + params["pageSize"] = self.pagesize if len(self.sort) > 0: sort_options = (str(sort_item) for sort_item in self.sort) ordered_sort_options = sorted(sort_options) - params['sort'] = ','.join(ordered_sort_options) + params["sort"] = ",".join(ordered_sort_options) if len(self.filter) > 0: filter_options = (str(filter_item) for filter_item in self.filter) ordered_filter_options = sorted(filter_options) - params['filter'] = ','.join(ordered_filter_options) + params["filter"] = ",".join(ordered_filter_options) if self._all_fields: - params['fields'] = '_all_' + params["fields"] = "_all_" return params @@ -112,7 +112,7 @@ def vf(self, name, value): def _append_view_filters(self, params): for name, value in self.view_filters: - params['vf_' + name] = value + params["vf_" + name] = value class CSVRequestOptions(_FilterOptionsBase): @@ -132,7 +132,7 @@ def max_age(self, value): def get_query_params(self): params = {} if self.max_age != -1: - params['maxAge'] = self.max_age + params["maxAge"] = self.max_age self._append_view_filters(params) return params @@ -141,7 +141,7 @@ def get_query_params(self): class ImageRequestOptions(_FilterOptionsBase): # if 'high' isn't specified, the REST API endpoint returns an image with standard resolution class Resolution: - High = 'high' + High = "high" def __init__(self, imageresolution=None, maxage=-1): super(ImageRequestOptions, self).__init__() @@ -160,9 +160,9 @@ def max_age(self, value): def get_query_params(self): params = {} if self.image_resolution: - params['resolution'] = self.image_resolution + params["resolution"] = self.image_resolution if self.max_age != -1: - params['maxAge'] = self.max_age + params["maxAge"] = self.max_age self._append_view_filters(params) return params @@ -205,13 +205,13 @@ def max_age(self, value): def get_query_params(self): params = {} if self.page_type: - params['type'] = self.page_type + params["type"] = self.page_type if self.orientation: - params['orientation'] = self.orientation + params["orientation"] = self.orientation if self.max_age != -1: - params['maxAge'] = self.max_age + params["maxAge"] = self.max_age self._append_view_filters(params) diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index 6aff0c126..b45098e8a 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -2,9 +2,29 @@ from .exceptions import NotSignedInError from ..namespace import Namespace -from .endpoint import Sites, Views, Users, Groups, Workbooks, Datasources, Projects, Auth, \ - Schedules, ServerInfo, Tasks, Subscriptions, Jobs, Metadata,\ - Databases, Tables, Flows, Webhooks, DataAccelerationReport, Favorites, DataAlerts +from .endpoint import ( + Sites, + Views, + Users, + Groups, + Workbooks, + Datasources, + Projects, + Auth, + Schedules, + ServerInfo, + Tasks, + Subscriptions, + Jobs, + Metadata, + Databases, + Tables, + Flows, + Webhooks, + DataAccelerationReport, + Favorites, + DataAlerts, +) from .endpoint.exceptions import EndpointUnavailableError, ServerInfoEndpointNotFoundError import requests @@ -14,20 +34,14 @@ except ImportError: from distutils.version import LooseVersion as Version -_PRODUCT_TO_REST_VERSION = { - '10.0': '2.3', - '9.3': '2.2', - '9.2': '2.1', - '9.1': '2.0', - '9.0': '2.0' -} +_PRODUCT_TO_REST_VERSION = {"10.0": "2.3", "9.3": "2.2", "9.2": "2.1", "9.1": "2.0", "9.0": "2.0"} class Server(object): class PublishMode: - Append = 'Append' - Overwrite = 'Overwrite' - CreateNew = 'CreateNew' + Append = "Append" + Overwrite = "Overwrite" + CreateNew = "CreateNew" def __init__(self, server_address, use_server_version=False): self._server_address = server_address @@ -84,8 +98,8 @@ def _set_auth(self, site_id, user_id, auth_token): def _get_legacy_version(self): response = self._session.get(self.server_address + "/auth?format=xml") info_xml = ET.fromstring(response.content) - prod_version = info_xml.find('.//product_version').text - version = _PRODUCT_TO_REST_VERSION.get(prod_version, '2.1') # 2.1 + prod_version = info_xml.find(".//product_version").text + version = _PRODUCT_TO_REST_VERSION.get(prod_version, "2.1") # 2.1 return version def _determine_highest_version(self): @@ -107,6 +121,7 @@ def use_server_version(self): def use_highest_version(self): self.use_server_version() import warnings + warnings.warn("use use_server_version instead", DeprecationWarning) def assert_at_least_version(self, version): @@ -114,7 +129,8 @@ def assert_at_least_version(self, version): minimum_supported = Version(version) if server_version < minimum_supported: error = "This endpoint is not available in API version {}. Requires {}".format( - server_version, minimum_supported) + server_version, minimum_supported + ) raise EndpointUnavailableError(error) @property @@ -128,21 +144,21 @@ def namespace(self): @property def auth_token(self): if self._auth_token is None: - error = 'Missing authentication token. You must sign in first.' + error = "Missing authentication token. You must sign in first." raise NotSignedInError(error) return self._auth_token @property def site_id(self): if self._site_id is None: - error = 'Missing site ID. You must sign in first.' + error = "Missing site ID. You must sign in first." raise NotSignedInError(error) return self._site_id @property def user_id(self): if self._user_id is None: - error = 'Missing user ID. You must sign in first.' + error = "Missing user ID. You must sign in first." raise NotSignedInError(error) return self._user_id diff --git a/tableauserverclient/server/sort.py b/tableauserverclient/server/sort.py index f412b8aa3..2d6bc030a 100644 --- a/tableauserverclient/server/sort.py +++ b/tableauserverclient/server/sort.py @@ -4,4 +4,4 @@ def __init__(self, field, direction): self.direction = direction def __str__(self): - return '{0}:{1}'.format(self.field, self.direction) + return "{0}:{1}".format(self.field, self.direction) From 7443d337e3530e459263a2201572727516e75ddb Mon Sep 17 00:00:00 2001 From: t8y8 Date: Wed, 21 Apr 2021 13:32:39 -0700 Subject: [PATCH 05/12] remove pycode --- .github/workflows/run-tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a0917c7b6..45b9548c1 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -25,13 +25,10 @@ jobs: pip install -e .[test] pip install mypy - - name: Lint with pycodestyle - run: | - pycodestyle tableauserverclient test samples - - name: Test with pytest run: | pytest test + - name: Run Mypy but allow failures run: | mypy --show-error-codes --disable-error-code misc tableauserverclient From 3769e0dd95500d1bfcea3869a29593f51b16588e Mon Sep 17 00:00:00 2001 From: Tyler Doyle Date: Tue, 27 Apr 2021 13:36:50 -0700 Subject: [PATCH 06/12] Data Quality Warning Support (#836) Add support for DQWs by endpoint following the pattern used by permissions and tags -- leave by ID and Triggers dqw urls for another PR --- contributing.md | 6 +- tableauserverclient/__init__.py | 1 + tableauserverclient/_version.py | 23 ++- tableauserverclient/models/__init__.py | 9 +- tableauserverclient/models/data_alert_item.py | 6 +- tableauserverclient/models/database_item.py | 24 ++- tableauserverclient/models/datasource_item.py | 17 +- tableauserverclient/models/dqw_item.py | 148 ++++++++++++++++++ tableauserverclient/models/flow_item.py | 73 ++++++++- tableauserverclient/models/job_item.py | 35 ++++- .../models/personal_access_token_auth.py | 5 +- tableauserverclient/models/project_item.py | 23 ++- .../models/property_decorators.py | 7 +- tableauserverclient/models/schedule_item.py | 14 +- tableauserverclient/models/site_item.py | 5 +- tableauserverclient/models/table_item.py | 11 ++ tableauserverclient/models/tableau_auth.py | 10 +- tableauserverclient/models/task_item.py | 14 +- tableauserverclient/models/user_item.py | 51 +++++- tableauserverclient/models/workbook_item.py | 6 +- tableauserverclient/server/__init__.py | 8 +- .../server/endpoint/__init__.py | 6 +- .../server/endpoint/databases_endpoint.py | 18 +++ .../server/endpoint/datasources_endpoint.py | 46 +++++- .../server/endpoint/dqw_endpoint.py | 61 ++++++++ .../server/endpoint/endpoint.py | 22 ++- .../server/endpoint/flows_endpoint.py | 18 +++ .../server/endpoint/groups_endpoint.py | 5 +- .../server/endpoint/permissions_endpoint.py | 7 +- .../server/endpoint/schedules_endpoint.py | 15 +- .../server/endpoint/server_info_endpoint.py | 6 +- .../server/endpoint/tables_endpoint.py | 23 ++- .../server/endpoint/tasks_endpoint.py | 10 +- .../server/endpoint/users_endpoint.py | 14 +- .../server/endpoint/workbooks_endpoint.py | 28 +++- tableauserverclient/server/request_factory.py | 99 +++++++++++- tableauserverclient/server/server.py | 13 +- test/assets/dqw_by_content_type.xml | 9 ++ test/test_database.py | 23 ++- 39 files changed, 852 insertions(+), 67 deletions(-) create mode 100644 tableauserverclient/models/dqw_item.py create mode 100644 tableauserverclient/server/endpoint/dqw_endpoint.py create mode 100644 test/assets/dqw_by_content_type.xml diff --git a/contributing.md b/contributing.md index c7f487ec3..3d5cd3d43 100644 --- a/contributing.md +++ b/contributing.md @@ -67,5 +67,9 @@ python setup.py test Our CI runs include a Python lint run, so you should run this locally and fix complaints before committing as this will fail your checkin. ```shell -pycodestyle tableauserverclient test samples +# this will run the formatter without making changes +black --line-length 120 tableauserverclient --check + +# this will format the directory and code for you +black --line-length 120 tableauserverclient ``` diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index 2eadcdfa1..fcce4e0c7 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -4,6 +4,7 @@ ConnectionItem, DataAlertItem, DatasourceItem, + DQWItem, GroupItem, JobItem, BackgroundJobItem, diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index c8afb10d4..5e73890bd 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -77,7 +77,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env= dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen( - [c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None) + [c] + args, + cwd=cwd, + env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr else None), ) break except EnvironmentError: @@ -243,7 +247,17 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command( - GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root + GITS, + [ + "describe", + "--tags", + "--dirty", + "--always", + "--long", + "--match", + "%s*" % tag_prefix, + ], + cwd=root, ) # --long was added in git-1.5.5 if describe_out is None: @@ -285,7 +299,10 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix) + pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( + full_tag, + tag_prefix, + ) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix) :] diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py index dff12a29d..c0ddc2e75 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -5,11 +5,18 @@ from .data_alert_item import DataAlertItem from .datasource_item import DatasourceItem from .database_item import DatabaseItem +from .dqw_item import DQWItem from .exceptions import UnpopulatedPropertyError from .favorites_item import FavoriteItem from .group_item import GroupItem from .flow_item import FlowItem -from .interval_item import IntervalItem, DailyInterval, WeeklyInterval, MonthlyInterval, HourlyInterval +from .interval_item import ( + IntervalItem, + DailyInterval, + WeeklyInterval, + MonthlyInterval, + HourlyInterval, +) from .job_item import JobItem, BackgroundJobItem from .pagination_item import PaginationItem from .project_item import ProjectItem diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index c924b6ab2..d719469b0 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -1,6 +1,10 @@ import xml.etree.ElementTree as ET -from .property_decorators import property_not_empty, property_is_enum, property_is_boolean +from .property_decorators import ( + property_not_empty, + property_is_enum, + property_is_boolean, +) from .user_item import UserItem from .view_item import ViewItem diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index a319606e4..4934af81b 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -1,6 +1,10 @@ import xml.etree.ElementTree as ET -from .property_decorators import property_is_enum, property_not_empty, property_is_boolean +from .property_decorators import ( + property_is_enum, + property_not_empty, + property_is_boolean, +) from .exceptions import UnpopulatedPropertyError @@ -34,8 +38,17 @@ def __init__(self, name, description=None, content_permissions=None): self._permissions = None self._default_table_permissions = None + self._data_quality_warnings = None + self._tables = None # Not implemented yet + @property + def dqws(self): + if self._data_quality_warnings is None: + error = "Project item must be populated with permissions first." + raise UnpopulatedPropertyError(error) + return self._data_quality_warnings() + @property def content_permissions(self): return self._content_permissions @@ -229,7 +242,14 @@ def _set_tables(self, tables): self._tables = tables def _set_default_permissions(self, permissions, content_type): - setattr(self, "_default_{content}_permissions".format(content=content_type), permissions) + setattr( + self, + "_default_{content}_permissions".format(content=content_type), + permissions, + ) + + def _set_data_quality_warnings(self, dqw): + self._data_quality_warnings = dqw @classmethod def from_response(cls, resp, ns): diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 219df39c2..78c2a44ca 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -1,6 +1,10 @@ import xml.etree.ElementTree as ET from .exceptions import UnpopulatedPropertyError -from .property_decorators import property_not_nullable, property_is_boolean, property_is_enum +from .property_decorators import ( + property_not_nullable, + property_is_boolean, + property_is_enum, +) from .tag_item import TagItem from ..datetime_helpers import parse_datetime import copy @@ -35,6 +39,7 @@ def __init__(self, project_id, name=None): self.tags = set() self._permissions = None + self._data_quality_warnings = None @property def ask_data_enablement(self): @@ -94,6 +99,13 @@ def encrypt_extracts(self): def encrypt_extracts(self, value): self._encrypt_extracts = value + @property + def dqws(self): + if self._data_quality_warnings is None: + error = "Project item must be populated with dqws first." + raise UnpopulatedPropertyError(error) + return self._data_quality_warnings() + @property def has_extracts(self): return self._has_extracts @@ -142,6 +154,9 @@ def _set_connections(self, connections): def _set_permissions(self, permissions): self._permissions = permissions + def _set_data_quality_warnings(self, dqws): + self._data_quality_warnings = dqws + def _parse_common_elements(self, datasource_xml, ns): if not isinstance(datasource_xml, ET.Element): datasource_xml = ET.fromstring(datasource_xml).find(".//t:datasource", namespaces=ns) diff --git a/tableauserverclient/models/dqw_item.py b/tableauserverclient/models/dqw_item.py new file mode 100644 index 000000000..a7f8ec9cb --- /dev/null +++ b/tableauserverclient/models/dqw_item.py @@ -0,0 +1,148 @@ +import xml.etree.ElementTree as ET +from ..datetime_helpers import parse_datetime + + +class DQWItem(object): + class WarningType: + WARNING = "WARNING" + DEPRECATED = "DEPRECATED" + STALE = "STALE" + SENSITIVE_DATA = "SENSITIVE_DATA" + MAINTENANCE = "MAINTENANCE" + + def __init__(self, warning_type="WARNING", message=None, active=True, severe=False): + self._id = None + # content related + self._content_id = None + self._content_type = None + + # DQW related + self.warning_type = warning_type + self.message = message + self.active = active + self.severe = severe + self._created_at = None + self._updated_at = None + + # owner + self._owner_display_name = None + self._owner_id = None + + @property + def id(self): + return self._id + + @property + def content_id(self): + return self._content_id + + @property + def content_type(self): + return self._content_type + + @property + def owner_display_name(self): + return self._owner_display_name + + @property + def owner_id(self): + return self._owner_id + + @property + def warning_type(self): + return self._warning_type + + @warning_type.setter + def warning_type(self, value): + self._warning_type = value + + @property + def message(self): + return self._message + + @message.setter + def message(self, value): + self._message = value + + @property + def active(self): + return self._active + + @active.setter + def active(self, value): + self._active = value + + @property + def severe(self): + return self._severe + + @severe.setter + def severe(self, value): + self._severe = value + + @property + def active(self): + return self._active + + @active.setter + def active(self, value): + self._active = value + + @property + def created_at(self): + return self._created_at + + @created_at.setter + def created_at(self, value): + self._created_at = value + + @property + def updated_at(self): + return self._updated_at + + @updated_at.setter + def updated_at(self, value): + self._updated_at = value + + @classmethod + def from_response(cls, resp, ns): + return cls.from_xml_element(ET.fromstring(resp), ns) + + @classmethod + def from_xml_element(cls, parsed_response, ns): + all_dqws = [] + dqw_elem_list = parsed_response.findall(".//t:dataQualityWarning", namespaces=ns) + for dqw_elem in dqw_elem_list: + dqw = DQWItem() + dqw._id = dqw_elem.get("id", None) + dqw._owner_display_name = dqw_elem.get("userDisplayName", None) + dqw._content_id = dqw_elem.get("contentId", None) + dqw._content_type = dqw_elem.get("contentType", None) + dqw.message = dqw_elem.get("message", None) + dqw.warning_type = dqw_elem.get("type", None) + + is_active = dqw_elem.get("isActive", None) + if is_active is not None: + dqw._active = string_to_bool(is_active) + + is_severe = dqw_elem.get("isSevere", None) + if is_severe is not None: + dqw._severe = string_to_bool(is_severe) + + dqw._created_at = parse_datetime(dqw_elem.get("createdAt", None)) + dqw._updated_at = parse_datetime(dqw_elem.get("updatedAt", None)) + + owner_id = None + owner_tag = dqw_elem.find(".//t:owner", namespaces=ns) + if owner_tag is not None: + owner_id = owner_tag.get("id", None) + dqw._owner_id = owner_id + + all_dqws.append(dqw) + + return all_dqws + + +# Used to convert string represented boolean to a boolean type +def string_to_bool(s): + return s.lower() == "true" diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index 99e857369..d1387f368 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -22,6 +22,7 @@ def __init__(self, project_id, name=None): self._connections = None self._permissions = None + self._data_quality_warnings = None @property def connections(self): @@ -45,6 +46,13 @@ def webpage_url(self): def created_at(self): return self._created_at + @property + def dqws(self): + if self._data_quality_warnings is None: + error = "Project item must be populated with dqws first." + raise UnpopulatedPropertyError(error) + return self._data_quality_warnings() + @property def id(self): return self._id @@ -84,16 +92,51 @@ def _set_connections(self, connections): def _set_permissions(self, permissions): self._permissions = permissions + def _set_data_quality_warnings(self, dqws): + self._data_quality_warnings = dqws + def _parse_common_elements(self, flow_xml, ns): if not isinstance(flow_xml, ET.Element): flow_xml = ET.fromstring(flow_xml).find(".//t:flow", namespaces=ns) if flow_xml is not None: - (_, _, _, _, _, updated_at, _, project_id, project_name, owner_id) = self._parse_element(flow_xml, ns) - self._set_values(None, None, None, None, None, updated_at, None, project_id, project_name, owner_id) + ( + _, + _, + _, + _, + _, + updated_at, + _, + project_id, + project_name, + owner_id, + ) = self._parse_element(flow_xml, ns) + self._set_values( + None, + None, + None, + None, + None, + updated_at, + None, + project_id, + project_name, + owner_id, + ) return self def _set_values( - self, id, name, description, webpage_url, created_at, updated_at, tags, project_id, project_name, owner_id + self, + id, + name, + description, + webpage_url, + created_at, + updated_at, + tags, + project_id, + project_name, + owner_id, ): if id is not None: self._id = id @@ -138,7 +181,16 @@ def from_response(cls, resp, ns): ) = cls._parse_element(flow_xml, ns) flow_item = cls(project_id) flow_item._set_values( - id_, name, description, webpage_url, created_at, updated_at, tags, None, project_name, owner_id + id_, + name, + description, + webpage_url, + created_at, + updated_at, + tags, + None, + project_name, + owner_id, ) all_flow_items.append(flow_item) return all_flow_items @@ -169,4 +221,15 @@ def _parse_element(flow_xml, ns): if owner_elem is not None: owner_id = owner_elem.get("id", None) - return (id_, name, description, webpage_url, created_at, updated_at, tags, project_id, project_name, owner_id) + return ( + id_, + name, + description, + webpage_url, + created_at, + updated_at, + tags, + project_id, + project_name, + owner_id, + ) diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index 7b7ea4921..7a3a50861 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -92,7 +92,17 @@ def _parse_element(cls, element, ns): finish_code = element.get("finishCode", -1) notes = [note.text for note in element.findall(".//t:notes", namespaces=ns)] or None mode = element.get("mode", None) - return cls(id_, type_, progress, created_at, started_at, completed_at, finish_code, notes, mode) + return cls( + id_, + type_, + progress, + created_at, + started_at, + completed_at, + finish_code, + notes, + mode, + ) class BackgroundJobItem(object): @@ -104,7 +114,16 @@ class Status: Cancelled = "Cancelled" def __init__( - self, id_, created_at, priority, job_type, status, title=None, subtitle=None, started_at=None, ended_at=None + self, + id_, + created_at, + priority, + job_type, + status, + title=None, + subtitle=None, + started_at=None, + ended_at=None, ): self._id = id_ self._type = job_type @@ -176,4 +195,14 @@ def _parse_element(cls, element, ns): title = element.get("title", None) subtitle = element.get("subtitle", None) - return cls(id_, created_at, priority, type_, status, title, subtitle, started_at, ended_at) + return cls( + id_, + created_at, + priority, + type_, + status, + title, + subtitle, + started_at, + ended_at, + ) diff --git a/tableauserverclient/models/personal_access_token_auth.py b/tableauserverclient/models/personal_access_token_auth.py index c80a020e8..a95972164 100644 --- a/tableauserverclient/models/personal_access_token_auth.py +++ b/tableauserverclient/models/personal_access_token_auth.py @@ -8,7 +8,10 @@ def __init__(self, token_name, personal_access_token, site_id=""): @property def credentials(self): - return {"personalAccessTokenName": self.token_name, "personalAccessTokenSecret": self.personal_access_token} + return { + "personalAccessTokenName": self.token_name, + "personalAccessTokenSecret": self.personal_access_token, + } def __repr__(self): return "".format(self.token_name, self.personal_access_token) diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index e8434a0ad..bed6def6e 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -90,7 +90,13 @@ def _parse_common_tags(self, project_xml, ns): project_xml = ET.fromstring(project_xml).find(".//t:project", namespaces=ns) if project_xml is not None: - (_, name, description, content_permissions, parent_id) = self._parse_element(project_xml) + ( + _, + name, + description, + content_permissions, + parent_id, + ) = self._parse_element(project_xml) self._set_values(None, name, description, content_permissions, parent_id) return self @@ -112,7 +118,11 @@ def _set_permissions(self, permissions): self._permissions = permissions def _set_default_permissions(self, permissions, content_type): - setattr(self, "_default_{content}_permissions".format(content=content_type), permissions) + setattr( + self, + "_default_{content}_permissions".format(content=content_type), + permissions, + ) @classmethod def from_response(cls, resp, ns): @@ -121,7 +131,14 @@ def from_response(cls, resp, ns): all_project_xml = parsed_response.findall(".//t:project", namespaces=ns) for project_xml in all_project_xml: - (id, name, description, content_permissions, parent_id, owner_id) = cls._parse_element(project_xml) + ( + id, + name, + description, + content_permissions, + parent_id, + owner_id, + ) = cls._parse_element(project_xml) project_item = cls(name) project_item._set_values(id, name, description, content_permissions, parent_id, owner_id) all_project_items.append(project_item) diff --git a/tableauserverclient/models/property_decorators.py b/tableauserverclient/models/property_decorators.py index 153786d4c..b3466dea7 100644 --- a/tableauserverclient/models/property_decorators.py +++ b/tableauserverclient/models/property_decorators.py @@ -152,7 +152,12 @@ def wrapper(self, value): raise ValueError("{} is not type 'dict', cannot update {})".format(value.__class__.__name__, func.__name__)) if len(value) != 4 or not all( attr in value.keys() - for attr in ("acceleration_enabled", "accelerate_now", "last_updated_at", "acceleration_status") + for attr in ( + "acceleration_enabled", + "accelerate_now", + "last_updated_at", + "acceleration_status", + ) ): error = "{} should have 2 keys ".format(func.__name__) error += "'acceleration_enabled' and 'accelerate_now'" diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index b54c20ae9..f8baf0749 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -1,8 +1,18 @@ import xml.etree.ElementTree as ET from datetime import datetime -from .interval_item import IntervalItem, HourlyInterval, DailyInterval, WeeklyInterval, MonthlyInterval -from .property_decorators import property_is_enum, property_not_nullable, property_is_int +from .interval_item import ( + IntervalItem, + HourlyInterval, + DailyInterval, + WeeklyInterval, + MonthlyInterval, +) +from .property_decorators import ( + property_is_enum, + property_not_nullable, + property_is_int, +) from ..datetime_helpers import parse_datetime diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index ac20e6a89..7fb1d116e 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -138,7 +138,10 @@ def content_url(self): @content_url.setter @property_not_nullable - @property_matches(VALID_CONTENT_URL_RE, "content_url can contain only letters, numbers, dashes, and underscores") + @property_matches( + VALID_CONTENT_URL_RE, + "content_url can contain only letters, numbers, dashes, and underscores", + ) def content_url(self, value): self._content_url = value diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index 16f43b98e..2f47400f7 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -17,6 +17,7 @@ def __init__(self, name, description=None): self._schema = None self._columns = None + self._data_quality_warnings = None @property def permissions(self): @@ -25,6 +26,13 @@ def permissions(self): raise UnpopulatedPropertyError(error) return self._permissions() + @property + def dqws(self): + if self._data_quality_warnings is None: + error = "Project item must be populated with dqws first." + raise UnpopulatedPropertyError(error) + return self._data_quality_warnings() + @property def id(self): return self._id @@ -86,6 +94,9 @@ def columns(self): def _set_columns(self, columns): self._columns = columns + def _set_data_quality_warnings(self, dqws): + self._data_quality_warnings = dqws + def _set_values(self, table_values): if "id" in table_values: self._id = table_values["id"] diff --git a/tableauserverclient/models/tableau_auth.py b/tableauserverclient/models/tableau_auth.py index 53aa33992..01787de4e 100644 --- a/tableauserverclient/models/tableau_auth.py +++ b/tableauserverclient/models/tableau_auth.py @@ -18,14 +18,20 @@ def __init__(self, username, password, site=None, site_id="", user_id_to_imperso def site(self): import warnings - warnings.warn("TableauAuth.site is deprecated, use TableauAuth.site_id instead.", DeprecationWarning) + warnings.warn( + "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", + DeprecationWarning, + ) return self.site_id @site.setter def site(self, value): import warnings - warnings.warn("TableauAuth.site is deprecated, use TableauAuth.site_id instead.", DeprecationWarning) + warnings.warn( + "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", + DeprecationWarning, + ) self.site_id = value @property diff --git a/tableauserverclient/models/task_item.py b/tableauserverclient/models/task_item.py index 26b0f348f..65709d5c9 100644 --- a/tableauserverclient/models/task_item.py +++ b/tableauserverclient/models/task_item.py @@ -10,7 +10,10 @@ class Type: DataAcceleration = "dataAcceleration" # This mapping is used to convert task type returned from server - _TASK_TYPE_MAPPING = {"RefreshExtractTask": Type.ExtractRefresh, "MaterializeViewsTask": Type.DataAcceleration} + _TASK_TYPE_MAPPING = { + "RefreshExtractTask": Type.ExtractRefresh, + "MaterializeViewsTask": Type.DataAcceleration, + } def __init__( self, @@ -78,7 +81,14 @@ def _parse_element(cls, element, ns): consecutive_failed_count = int(element.get("consecutiveFailedCount", 0)) id_ = element.get("id", None) return cls( - id_, task_type, priority, consecutive_failed_count, schedule_item.id, schedule_item, last_run_at, target + id_, + task_type, + priority, + consecutive_failed_count, + schedule_item.id, + schedule_item, + last_run_at, + target, ) @staticmethod diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index b9796cbae..65abf4cb6 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -1,6 +1,10 @@ import xml.etree.ElementTree as ET from .exceptions import UnpopulatedPropertyError -from .property_decorators import property_is_enum, property_not_empty, property_not_nullable +from .property_decorators import ( + property_is_enum, + property_not_empty, + property_not_nullable, +) from ..datetime_helpers import parse_datetime from .reference_item import ResourceReference @@ -128,12 +132,31 @@ def _parse_common_tags(self, user_xml, ns): if not isinstance(user_xml, ET.Element): user_xml = ET.fromstring(user_xml).find(".//t:user", namespaces=ns) if user_xml is not None: - (_, _, site_role, _, _, fullname, email, auth_setting, _) = self._parse_element(user_xml, ns) + ( + _, + _, + site_role, + _, + _, + fullname, + email, + auth_setting, + _, + ) = self._parse_element(user_xml, ns) self._set_values(None, None, site_role, None, None, fullname, email, auth_setting, None) return self def _set_values( - self, id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name + self, + id, + name, + site_role, + last_login, + external_auth_user_id, + fullname, + email, + auth_setting, + domain_name, ): if id is not None: self._id = id @@ -173,7 +196,15 @@ def from_response(cls, resp, ns): ) = cls._parse_element(user_xml, ns) user_item = cls(name, site_role) user_item._set_values( - id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name + id, + name, + site_role, + last_login, + external_auth_user_id, + fullname, + email, + auth_setting, + domain_name, ) all_user_items.append(user_item) return all_user_items @@ -198,7 +229,17 @@ def _parse_element(user_xml, ns): if domain_elem is not None: domain_name = domain_elem.get("name", None) - return id, name, site_role, last_login, external_auth_user_id, fullname, email, auth_setting, domain_name + return ( + id, + name, + site_role, + last_login, + external_auth_user_id, + fullname, + email, + auth_setting, + domain_name, + ) def __repr__(self): return "".format(self.id, self.name, self.site_role) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 20597364e..14ca8f33b 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -1,6 +1,10 @@ import xml.etree.ElementTree as ET from .exceptions import UnpopulatedPropertyError -from .property_decorators import property_not_nullable, property_is_boolean, property_is_data_acceleration_config +from .property_decorators import ( + property_not_nullable, + property_is_boolean, + property_is_data_acceleration_config, +) from .tag_item import TagItem from .view_item import ViewItem from .permissions_item import PermissionsRule diff --git a/tableauserverclient/server/__init__.py b/tableauserverclient/server/__init__.py index 314b477c2..c653a8966 100644 --- a/tableauserverclient/server/__init__.py +++ b/tableauserverclient/server/__init__.py @@ -1,5 +1,10 @@ from .request_factory import RequestFactory -from .request_options import CSVRequestOptions, ImageRequestOptions, PDFRequestOptions, RequestOptions +from .request_options import ( + CSVRequestOptions, + ImageRequestOptions, + PDFRequestOptions, + RequestOptions, +) from .filter import Filter from .sort import Sort from .. import ( @@ -7,6 +12,7 @@ DataAlertItem, DatasourceItem, DatabaseItem, + DQWItem, JobItem, BackgroundJobItem, GroupItem, diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py index 5d55509cf..8653c0254 100644 --- a/tableauserverclient/server/endpoint/__init__.py +++ b/tableauserverclient/server/endpoint/__init__.py @@ -6,7 +6,11 @@ from .endpoint import Endpoint from .favorites_endpoint import Favorites from .flows_endpoint import Flows -from .exceptions import ServerResponseError, MissingRequiredFieldError, ServerInfoEndpointNotFoundError +from .exceptions import ( + ServerResponseError, + MissingRequiredFieldError, + ServerInfoEndpointNotFoundError, +) from .groups_endpoint import Groups from .jobs_endpoint import Jobs from .metadata_endpoint import Metadata diff --git a/tableauserverclient/server/endpoint/databases_endpoint.py b/tableauserverclient/server/endpoint/databases_endpoint.py index f9ff014d9..50826ee0b 100644 --- a/tableauserverclient/server/endpoint/databases_endpoint.py +++ b/tableauserverclient/server/endpoint/databases_endpoint.py @@ -2,6 +2,7 @@ from .exceptions import MissingRequiredFieldError from .permissions_endpoint import _PermissionsEndpoint from .default_permissions_endpoint import _DefaultPermissionsEndpoint +from .dqw_endpoint import _DataQualityWarningEndpoint from .. import RequestFactory, DatabaseItem, TableItem, PaginationItem, Permission @@ -16,6 +17,7 @@ def __init__(self, parent_srv): self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) self._default_permissions = _DefaultPermissionsEndpoint(parent_srv, lambda: self.baseurl) + self._data_quality_warnings = _DataQualityWarningEndpoint(parent_srv, "database") @property def baseurl(self): @@ -116,3 +118,19 @@ def update_table_default_permissions(self, item): @api(version="3.5") def delete_table_default_permissions(self, item): self._default_permissions.delete_default_permissions(item, Permission.Resource.Table) + + @api(version="3.5") + def populate_dqw(self, item): + self._data_quality_warnings.populate(item) + + @api(version="3.5") + def update_dqw(self, item, warning): + return self._data_quality_warnings.update(item, warning) + + @api(version="3.5") + def add_dqw(self, item, warning): + return self._data_quality_warnings.add(item, warning) + + @api(version="3.5") + def delete_dqw(self, item): + self._data_quality_warnings.clear(item) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 6d117b2a0..ccdbfa0d1 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -1,11 +1,17 @@ from .endpoint import QuerysetEndpoint, api, parameter_added_in from .exceptions import InternalServerError, MissingRequiredFieldError from .permissions_endpoint import _PermissionsEndpoint +from .dqw_endpoint import _DataQualityWarningEndpoint from .fileuploads_endpoint import Fileuploads from .resource_tagger import _ResourceTagger from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem from ..query import QuerySet -from ...filesys_helpers import to_filename, make_download_path, get_file_type, get_file_object_size +from ...filesys_helpers import ( + to_filename, + make_download_path, + get_file_type, + get_file_object_size, +) from ...models.job_item import JobItem import os @@ -27,6 +33,7 @@ def __init__(self, parent_srv): super(Datasources, self).__init__(parent_srv) self._resource_tagger = _ResourceTagger(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) + self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "datasource") @property def baseurl(self): @@ -95,7 +102,10 @@ def download(self, datasource_id, filepath=None, include_extract=True, no_extrac if no_extract is False or no_extract is True: import warnings - warnings.warn("no_extract is deprecated, use include_extract instead.", DeprecationWarning) + warnings.warn( + "no_extract is deprecated, use include_extract instead.", + DeprecationWarning, + ) include_extract = not no_extract if not include_extract: @@ -174,7 +184,15 @@ def delete_extract(self, datasource_item): @api(version="2.0") @parameter_added_in(connections="2.8") @parameter_added_in(as_job="3.0") - def publish(self, datasource_item, file, mode, connection_credentials=None, connections=None, as_job=False): + def publish( + self, + datasource_item, + file, + mode, + connection_credentials=None, + connections=None, + as_job=False, + ): try: @@ -241,7 +259,11 @@ def publish(self, datasource_item, file, mode, connection_credentials=None, conn file_contents = file.read() xml_request, content_type = RequestFactory.Datasource.publish_req( - datasource_item, filename, file_contents, connection_credentials, connections + datasource_item, + filename, + file_contents, + connection_credentials, + connections, ) # Send the publishing request to server @@ -287,3 +309,19 @@ def update_permissions(self, item, permission_item): @api(version="2.0") def delete_permission(self, item, capability_item): self._permissions.delete(item, capability_item) + + @api(version="3.5") + def populate_dqw(self, item): + self._data_quality_warnings.populate(item) + + @api(version="3.5") + def update_dqw(self, item, warning): + return self._data_quality_warnings.update(item, warning) + + @api(version="3.5") + def add_dqw(self, item, warning): + return self._data_quality_warnings.add(item, warning) + + @api(version="3.5") + def delete_dqw(self, item): + self._data_quality_warnings.clear(item) diff --git a/tableauserverclient/server/endpoint/dqw_endpoint.py b/tableauserverclient/server/endpoint/dqw_endpoint.py new file mode 100644 index 000000000..e19ca7d90 --- /dev/null +++ b/tableauserverclient/server/endpoint/dqw_endpoint.py @@ -0,0 +1,61 @@ +import logging + +from .. import RequestFactory, DQWItem + +from .endpoint import Endpoint +from .exceptions import MissingRequiredFieldError + + +logger = logging.getLogger(__name__) + + +class _DataQualityWarningEndpoint(Endpoint): + def __init__(self, parent_srv, resource_type): + super(_DataQualityWarningEndpoint, self).__init__(parent_srv) + self.resource_type = resource_type + + @property + def baseurl(self): + return "{0}/sites/{1}/dataQualityWarnings/{2}".format( + self.parent_srv.baseurl, self.parent_srv.site_id, self.resource_type + ) + + def add(self, resource, warning): + url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=resource.id) + add_req = RequestFactory.DQW.add_req(warning) + response = self.post_request(url, add_req) + warnings = DQWItem.from_response(response.content, self.parent_srv.namespace) + logger.info("Added dqw for resource {0}".format(resource.id)) + + return warnings + + def update(self, resource, warning): + url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=resource.id) + add_req = RequestFactory.DQW.update_req(warning) + response = self.put_request(url, add_req) + warnings = DQWItem.from_response(response.content, self.parent_srv.namespace) + logger.info("Added dqw for resource {0}".format(resource.id)) + + return warnings + + def clear(self, resource): + url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=resource.id) + return self.delete_request(url) + + def populate(self, item): + if not item.id: + error = "Server item is missing ID. Item must be retrieved from server first." + raise MissingRequiredFieldError(error) + + def dqw_fetcher(): + return self._get_data_quality_warnings(item) + + item._set_data_quality_warnings(dqw_fetcher) + logger.info("Populated permissions for item (ID: {0})".format(item.id)) + + def _get_data_quality_warnings(self, item, req_options=None): + url = "{baseurl}/{content_luid}".format(baseurl=self.baseurl, content_luid=item.id) + server_response = self.get_request(url, req_options) + dqws = DQWItem.from_response(server_response.content, self.parent_srv.namespace) + + return dqws diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 92a20b21a..c7be8fc77 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -1,4 +1,9 @@ -from .exceptions import ServerResponseError, InternalServerError, NonXMLResponseError, EndpointUnavailableError +from .exceptions import ( + ServerResponseError, + InternalServerError, + NonXMLResponseError, + EndpointUnavailableError, +) from functools import wraps from xml.etree.ElementTree import ParseError from ..query import QuerySet @@ -39,7 +44,15 @@ def _safe_to_log(server_response): else: return server_response.content - def _make_request(self, method, url, content=None, auth_token=None, content_type=None, parameters=None): + def _make_request( + self, + method, + url, + content=None, + auth_token=None, + content_type=None, + parameters=None, + ): parameters = parameters or {} parameters.update(self.parent_srv.http_options) parameters["headers"] = Endpoint._make_common_headers(auth_token, content_type) @@ -95,7 +108,10 @@ def get_request(self, url, request_object=None, parameters=None): url = request_object.apply_query_params(url) return self._make_request( - self.parent_srv.session.get, url, auth_token=self.parent_srv.auth_token, parameters=parameters + self.parent_srv.session.get, + url, + auth_token=self.parent_srv.auth_token, + parameters=parameters, ) def delete_request(self, url): diff --git a/tableauserverclient/server/endpoint/flows_endpoint.py b/tableauserverclient/server/endpoint/flows_endpoint.py index 41cbe19cd..475166aad 100644 --- a/tableauserverclient/server/endpoint/flows_endpoint.py +++ b/tableauserverclient/server/endpoint/flows_endpoint.py @@ -1,6 +1,7 @@ from .endpoint import Endpoint, api from .exceptions import InternalServerError, MissingRequiredFieldError from .permissions_endpoint import _PermissionsEndpoint +from .dqw_endpoint import _DataQualityWarningEndpoint from .fileuploads_endpoint import Fileuploads from .resource_tagger import _ResourceTagger from .. import RequestFactory, FlowItem, PaginationItem, ConnectionItem @@ -26,6 +27,7 @@ def __init__(self, parent_srv): super(Flows, self).__init__(parent_srv) self._resource_tagger = _ResourceTagger(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) + self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "flow") @property def baseurl(self): @@ -214,3 +216,19 @@ def update_permissions(self, item, permission_item): @api(version="3.3") def delete_permission(self, item, capability_item): self._permissions.delete(item, capability_item) + + @api(version="3.5") + def populate_dqw(self, item): + self._data_quality_warnings.populate(item) + + @api(version="3.5") + def update_dqw(self, item, warning): + return self._data_quality_warnings.update(item, warning) + + @api(version="3.5") + def add_dqw(self, item, warning): + return self._data_quality_warnings.add(item, warning) + + @api(version="3.5") + def delete_dqw(self, item): + self._data_quality_warnings.clear(item) diff --git a/tableauserverclient/server/endpoint/groups_endpoint.py b/tableauserverclient/server/endpoint/groups_endpoint.py index 4a09872cb..b771e56d8 100644 --- a/tableauserverclient/server/endpoint/groups_endpoint.py +++ b/tableauserverclient/server/endpoint/groups_endpoint.py @@ -33,7 +33,10 @@ def populate_users(self, group_item, req_options=None): # Define an inner function that we bind to the model_item's `.user` property. def user_pager(): - return Pager(lambda options: self._get_users_for_group(group_item, options), req_options) + return Pager( + lambda options: self._get_users_for_group(group_item, options), + req_options, + ) group_item._set_users(user_pager) diff --git a/tableauserverclient/server/endpoint/permissions_endpoint.py b/tableauserverclient/server/endpoint/permissions_endpoint.py index 0992f5ca9..7035837f4 100644 --- a/tableauserverclient/server/endpoint/permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/permissions_endpoint.py @@ -46,7 +46,12 @@ def delete(self, resource, rules): for capability, mode in rule.capabilities.items(): " /permissions/groups/group-id/capability-name/capability-mode" url = "{0}/{1}/permissions/{2}/{3}/{4}/{5}".format( - self.owner_baseurl(), resource.id, rule.grantee.tag_name + "s", rule.grantee.id, capability, mode + self.owner_baseurl(), + resource.id, + rule.grantee.tag_name + "s", + rule.grantee.id, + capability, + mode, ) logger.debug("Removing {0} permission for capabilty {1}".format(mode, capability)) diff --git a/tableauserverclient/server/endpoint/schedules_endpoint.py b/tableauserverclient/server/endpoint/schedules_endpoint.py index 3a5e665fa..d582dca26 100644 --- a/tableauserverclient/server/endpoint/schedules_endpoint.py +++ b/tableauserverclient/server/endpoint/schedules_endpoint.py @@ -65,7 +65,13 @@ def create(self, schedule_item): return new_schedule @api(version="2.8") - def add_to_schedule(self, schedule_id, workbook=None, datasource=None, task_type=TaskItem.Type.ExtractRefresh): + def add_to_schedule( + self, + schedule_id, + workbook=None, + datasource=None, + task_type=TaskItem.Type.ExtractRefresh, + ): def add_to(resource, type_, req_factory): id_ = resource.id url = "{0}/{1}/{2}s".format(self.siteurl, schedule_id, type_) @@ -79,7 +85,12 @@ def add_to(resource, type_, req_factory): logger.info("Added {} to {} to schedule {}".format(type_, id_, schedule_id)) if error is not None or warnings is not None: - return AddResponse(result=False, error=error, warnings=warnings, task_created=task_created) + return AddResponse( + result=False, + error=error, + warnings=warnings, + task_created=task_created, + ) else: return OK diff --git a/tableauserverclient/server/endpoint/server_info_endpoint.py b/tableauserverclient/server/endpoint/server_info_endpoint.py index 98d996b52..8776477d3 100644 --- a/tableauserverclient/server/endpoint/server_info_endpoint.py +++ b/tableauserverclient/server/endpoint/server_info_endpoint.py @@ -1,5 +1,9 @@ from .endpoint import Endpoint, api -from .exceptions import ServerResponseError, ServerInfoEndpointNotFoundError, EndpointUnavailableError +from .exceptions import ( + ServerResponseError, + ServerInfoEndpointNotFoundError, + EndpointUnavailableError, +) from ...models import ServerInfoItem import logging diff --git a/tableauserverclient/server/endpoint/tables_endpoint.py b/tableauserverclient/server/endpoint/tables_endpoint.py index e35535d19..ac53484db 100644 --- a/tableauserverclient/server/endpoint/tables_endpoint.py +++ b/tableauserverclient/server/endpoint/tables_endpoint.py @@ -1,6 +1,7 @@ from .endpoint import api, Endpoint from .exceptions import MissingRequiredFieldError from .permissions_endpoint import _PermissionsEndpoint +from .dqw_endpoint import _DataQualityWarningEndpoint from ..pager import Pager from .. import RequestFactory, TableItem, ColumnItem, PaginationItem @@ -15,6 +16,7 @@ def __init__(self, parent_srv): super(Tables, self).__init__(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) + self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "table") @property def baseurl(self): @@ -70,7 +72,10 @@ def populate_columns(self, table_item, req_options=None): raise MissingRequiredFieldError(error) def column_fetcher(): - return Pager(lambda options: self._get_columns_for_table(table_item, options), req_options) + return Pager( + lambda options: self._get_columns_for_table(table_item, options), + req_options, + ) table_item._set_columns(column_fetcher) logger.info("Populated columns for table (ID: {0}".format(table_item.id)) @@ -113,3 +118,19 @@ def update_permissions(self, item, rules): @api(version="3.5") def delete_permission(self, item, rules): return self._permissions.delete(item, rules) + + @api(version="3.5") + def populate_dqw(self, item): + self._data_quality_warnings.populate(item) + + @api(version="3.5") + def update_dqw(self, item, warning): + return self._data_quality_warnings.update(item, warning) + + @api(version="3.5") + def add_dqw(self, item, warning): + return self._data_quality_warnings.add(item, warning) + + @api(version="3.5") + def delete_dqw(self, item): + self._data_quality_warnings.clear(item) diff --git a/tableauserverclient/server/endpoint/tasks_endpoint.py b/tableauserverclient/server/endpoint/tasks_endpoint.py index abc249721..aaa5069c3 100644 --- a/tableauserverclient/server/endpoint/tasks_endpoint.py +++ b/tableauserverclient/server/endpoint/tasks_endpoint.py @@ -42,7 +42,11 @@ def get_by_id(self, task_id): error = "No Task ID provided" raise ValueError(error) logger.info("Querying a single task by id ({})".format(task_id)) - url = "{}/{}/{}".format(self.baseurl, self.__normalize_task_type(TaskItem.Type.ExtractRefresh), task_id) + url = "{}/{}/{}".format( + self.baseurl, + self.__normalize_task_type(TaskItem.Type.ExtractRefresh), + task_id, + ) server_response = self.get_request(url) return TaskItem.from_response(server_response.content, self.parent_srv.namespace)[0] @@ -53,7 +57,9 @@ def run(self, task_item): raise MissingRequiredFieldError(error) url = "{0}/{1}/{2}/runNow".format( - self.baseurl, self.__normalize_task_type(TaskItem.Type.ExtractRefresh), task_item.id + self.baseurl, + self.__normalize_task_type(TaskItem.Type.ExtractRefresh), + task_item.id, ) run_req = RequestFactory.Task.run_req(task_item) server_response = self.post_request(url, run_req) diff --git a/tableauserverclient/server/endpoint/users_endpoint.py b/tableauserverclient/server/endpoint/users_endpoint.py index 3318e6bb3..6adbf92fb 100644 --- a/tableauserverclient/server/endpoint/users_endpoint.py +++ b/tableauserverclient/server/endpoint/users_endpoint.py @@ -1,6 +1,13 @@ from .endpoint import QuerysetEndpoint, api from .exceptions import MissingRequiredFieldError -from .. import RequestFactory, RequestOptions, UserItem, WorkbookItem, PaginationItem, GroupItem +from .. import ( + RequestFactory, + RequestOptions, + UserItem, + WorkbookItem, + PaginationItem, + GroupItem, +) from ..pager import Pager import copy @@ -105,7 +112,10 @@ def populate_groups(self, user_item, req_options=None): raise MissingRequiredFieldError(error) def groups_for_user_pager(): - return Pager(lambda options: self._get_groups_for_user(user_item, options), req_options) + return Pager( + lambda options: self._get_groups_for_user(user_item, options), + req_options, + ) user_item._set_groups(groups_for_user_pager) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index aa72979dd..df14674c6 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -5,7 +5,12 @@ from .resource_tagger import _ResourceTagger from .. import RequestFactory, WorkbookItem, ConnectionItem, ViewItem, PaginationItem from ...models.job_item import JobItem -from ...filesys_helpers import to_filename, make_download_path, get_file_type, get_file_object_size +from ...filesys_helpers import ( + to_filename, + make_download_path, + get_file_type, + get_file_object_size, +) import os import logging @@ -140,7 +145,10 @@ def download(self, workbook_id, filepath=None, include_extract=True, no_extract= if no_extract is False or no_extract is True: import warnings - warnings.warn("no_extract is deprecated, use include_extract instead.", DeprecationWarning) + warnings.warn( + "no_extract is deprecated, use include_extract instead.", + DeprecationWarning, + ) include_extract = not no_extract if not include_extract: @@ -176,7 +184,11 @@ def _get_views_for_workbook(self, workbook_item, usage): if usage: url += "?includeUsageStatistics=true" server_response = self.get_request(url) - views = ViewItem.from_response(server_response.content, self.parent_srv.namespace, workbook_id=workbook_item.id) + views = ViewItem.from_response( + server_response.content, + self.parent_srv.namespace, + workbook_id=workbook_item.id, + ) return views # Get all connections of workbook @@ -267,7 +279,10 @@ def publish( if connection_credentials is not None: import warnings - warnings.warn("connection_credentials is being deprecated. Use connections instead", DeprecationWarning) + warnings.warn( + "connection_credentials is being deprecated. Use connections instead", + DeprecationWarning, + ) try: # Expect file to be a filepath @@ -333,7 +348,10 @@ def publish( url = "{0}&uploadSessionId={1}".format(url, upload_session_id) conn_creds = connection_credentials xml_request, content_type = RequestFactory.Workbook.publish_req_chunked( - workbook_item, connection_credentials=conn_creds, connections=connections, hidden_views=hidden_views + workbook_item, + connection_credentials=conn_creds, + connections=connections, + hidden_views=hidden_views, ) else: logger.info("Publishing {0} to server".format(filename)) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index d2e921479..c03a4fadc 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -177,7 +177,14 @@ def update_req(self, datasource_item): return ET.tostring(xml_request) - def publish_req(self, datasource_item, filename, file_contents, connection_credentials=None, connections=None): + def publish_req( + self, + datasource_item, + filename, + file_contents, + connection_credentials=None, + connections=None, + ): xml_request = self._generate_xml(datasource_item, connection_credentials, connections) parts = { @@ -193,6 +200,66 @@ def publish_req_chunked(self, datasource_item, connection_credentials=None, conn return _add_multipart(parts) +class DQWRequest(object): + def add_req(self, dqw_item): + xml_request = ET.Element("tsRequest") + dqw_element = ET.SubElement(xml_request, "dataQualityWarning") + + dqw_element.attrib["isActive"] = str(dqw_item.active).lower() + dqw_element.attrib["isSevere"] = str(dqw_item.severe).lower() + + dqw_element.attrib["type"] = dqw_item.warning_type + + if dqw_item.message: + dqw_element.attrib["message"] = str(dqw_item.message) + + return ET.tostring(xml_request) + + def update_req(self, database_item): + xml_request = ET.Element("tsRequest") + dqw_element = ET.SubElement(xml_request, "dataQualityWarning") + + dqw_element.attrib["isActive"] = str(dqw_item.active).lower() + dqw_element.attrib["isSevere"] = str(dqw_item.severe).lower() + + dqw_element.attrib["type"] = dqw_item.warning_type + + if dqw_item.message: + dqw_element.attrib["message"] = str(dqw_item.message) + + return ET.tostring(xml_request) + + +class DQWRequest(object): + def add_req(self, dqw_item): + xml_request = ET.Element("tsRequest") + dqw_element = ET.SubElement(xml_request, "dataQualityWarning") + + dqw_element.attrib["isActive"] = str(dqw_item.active).lower() + dqw_element.attrib["isSevere"] = str(dqw_item.severe).lower() + + dqw_element.attrib["type"] = dqw_item.warning_type + + if dqw_item.message: + dqw_element.attrib["message"] = str(dqw_item.message) + + return ET.tostring(xml_request) + + def update_req(self, database_item): + xml_request = ET.Element("tsRequest") + dqw_element = ET.SubElement(xml_request, "dataQualityWarning") + + dqw_element.attrib["isActive"] = str(dqw_item.active).lower() + dqw_element.attrib["isSevere"] = str(dqw_item.severe).lower() + + dqw_element.attrib["type"] = dqw_item.warning_type + + if dqw_item.message: + dqw_element.attrib["message"] = str(dqw_item.message) + + return ET.tostring(xml_request) + + class FavoriteRequest(object): def _add_to_req(self, id_, target_type, label): """ @@ -223,7 +290,10 @@ def add_workbook_req(self, id_, name): class FileuploadRequest(object): def chunk_req(self, chunk): - parts = {"request_payload": ("", "", "text/xml"), "tableau_file": ("file", chunk, "application/octet-stream")} + parts = { + "request_payload": ("", "", "text/xml"), + "tableau_file": ("file", chunk, "application/octet-stream"), + } return _add_multipart(parts) @@ -724,7 +794,13 @@ def add_req(self, user_item): class WorkbookRequest(object): - def _generate_xml(self, workbook_item, connection_credentials=None, connections=None, hidden_views=None): + def _generate_xml( + self, + workbook_item, + connection_credentials=None, + connections=None, + hidden_views=None, + ): xml_request = ET.Element("tsRequest") workbook_element = ET.SubElement(xml_request, "workbook") workbook_element.attrib["name"] = workbook_item.name @@ -778,7 +854,13 @@ def update_req(self, workbook_item): return ET.tostring(xml_request) def publish_req( - self, workbook_item, filename, file_contents, connection_credentials=None, connections=None, hidden_views=None + self, + workbook_item, + filename, + file_contents, + connection_credentials=None, + connections=None, + hidden_views=None, ): xml_request = self._generate_xml( workbook_item, @@ -793,7 +875,13 @@ def publish_req( } return _add_multipart(parts) - def publish_req_chunked(self, workbook_item, connection_credentials=None, connections=None, hidden_views=None): + def publish_req_chunked( + self, + workbook_item, + connection_credentials=None, + connections=None, + hidden_views=None, + ): xml_request = self._generate_xml( workbook_item, connection_credentials=connection_credentials, @@ -932,6 +1020,7 @@ class RequestFactory(object): DataAlert = DataAlertRequest() Datasource = DatasourceRequest() Database = DatabaseRequest() + DQW = DQWRequest() Empty = EmptyRequest() Favorite = FavoriteRequest() Fileupload = FileuploadRequest() diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index b45098e8a..057c98877 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -25,7 +25,10 @@ Favorites, DataAlerts, ) -from .endpoint.exceptions import EndpointUnavailableError, ServerInfoEndpointNotFoundError +from .endpoint.exceptions import ( + EndpointUnavailableError, + ServerInfoEndpointNotFoundError, +) import requests @@ -34,7 +37,13 @@ except ImportError: from distutils.version import LooseVersion as Version -_PRODUCT_TO_REST_VERSION = {"10.0": "2.3", "9.3": "2.2", "9.2": "2.1", "9.1": "2.0", "9.0": "2.0"} +_PRODUCT_TO_REST_VERSION = { + "10.0": "2.3", + "9.3": "2.2", + "9.2": "2.1", + "9.1": "2.0", + "9.0": "2.0", +} class Server(object): diff --git a/test/assets/dqw_by_content_type.xml b/test/assets/dqw_by_content_type.xml new file mode 100644 index 000000000..c65deb6d9 --- /dev/null +++ b/test/assets/dqw_by_content_type.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/test_database.py b/test/test_database.py index fb9ffbd86..4de623dae 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -11,12 +11,12 @@ GET_XML = 'database_get.xml' POPULATE_PERMISSIONS_XML = 'database_populate_permissions.xml' UPDATE_XML = 'database_update.xml' +GET_DQW_BY_CONTENT = "dqw_by_content_type.xml" class DatabaseTests(unittest.TestCase): def setUp(self): self.server = TSC.Server('http://test') - # Fake signin self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' @@ -81,6 +81,27 @@ def test_populate_permissions(self): TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, }) + def test_populate_data_quality_warning(self): + with open(asset(GET_DQW_BY_CONTENT), 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.get(self.server.databases._data_quality_warnings.baseurl + '/94441d26-9a52-4a42-b0fb-3f94792d1aac', text=response_xml) + single_database = TSC.DatabaseItem('test') + single_database._id = '94441d26-9a52-4a42-b0fb-3f94792d1aac' + + self.server.databases.populate_dqw(single_database) + dqws = single_database.dqws + first_dqw = dqws.pop() + self.assertEqual(first_dqw.id, "c2e0e406-84fb-4f4e-9998-f20dd9306710") + self.assertEqual(first_dqw.warning_type, "WARNING") + self.assertEqual(first_dqw.message, "Hello, World!") + self.assertEqual(first_dqw.owner_id, "eddc8c5f-6af0-40be-b6b0-2c790290a43f") + self.assertEqual(first_dqw.active, True) + self.assertEqual(first_dqw.severe, True) + self.assertEqual(str(first_dqw.created_at), "2021-04-09 18:39:54+00:00") + self.assertEqual(str(first_dqw.updated_at), "2021-04-09 18:39:54+00:00") + + def test_delete(self): with requests_mock.mock() as m: m.delete(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5', status_code=204) From 1c694ebb3da287f66187f0b3bc04d14384a348b6 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 5 May 2021 13:27:00 -0500 Subject: [PATCH 07/12] Correct Data Alert repr (#821) * Correct Data Alert repr * Fix repr on subscription item Co-authored-by: Jordan Woods --- tableauserverclient/models/data_alert_item.py | 4 ++-- tableauserverclient/models/subscription_item.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index d719469b0..a4d11ca5e 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -36,8 +36,8 @@ def __init__(self): self._recipients = None def __repr__(self): - return "".format( + return "".format( **self.__dict__ ) diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index c5ac10168..bc431ed77 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -20,7 +20,7 @@ def __init__(self, subject, schedule_id, user_id, target): def __repr__(self): if self.id is not None: - return " Date: Mon, 10 May 2021 14:48:36 -0700 Subject: [PATCH 08/12] fix default permissions mode (#844) Change from False to True, since that's the real default --- tableauserverclient/models/site_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index 7fb1d116e..f3e918ae5 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -49,7 +49,7 @@ def __init__( tier_viewer_capacity=None, data_alerts_enabled=True, commenting_mentions_enabled=True, - catalog_obfuscation_enabled=False, + catalog_obfuscation_enabled=True, flow_auto_save_enabled=True, web_extraction_enabled=True, metrics_content_type_enabled=True, From 54d7e594214b05cac917802f2a2516c5e65e6e04 Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Fri, 14 May 2021 13:02:32 -0400 Subject: [PATCH 09/12] Fixes revision limit field not propagating to the request (#847) --- tableauserverclient/models/site_item.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index f3e918ae5..ab0211414 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -31,7 +31,7 @@ def __init__( disable_subscriptions=False, subscribe_others_enabled=True, revision_history_enabled=False, - revision_limit=None, + revision_limit=25, data_acceleration_mode=None, flows_enabled=True, cataloging_enabled=True, @@ -76,13 +76,13 @@ def __init__( self._state = None self._status_reason = None self._storage = None - self._revision_limit = None self.user_quota = user_quota self.storage_quota = storage_quota self.content_url = content_url self.disable_subscriptions = disable_subscriptions self.name = name self.revision_history_enabled = revision_history_enabled + self.revision_limit = revision_limit self.subscribe_others_enabled = subscribe_others_enabled self.admin_mode = admin_mode self.data_acceleration_mode = data_acceleration_mode From 5d333f597828819372f5cfdf8104c70476e39bbf Mon Sep 17 00:00:00 2001 From: annematronic <73560907+annematronic@users.noreply.github.com> Date: Tue, 25 May 2021 06:37:48 -0700 Subject: [PATCH 10/12] 841 enhancement rename datasource (#843) * Add support for rename datasource * remove erroneous sys.path line * remove sys import --- tableauserverclient/server/request_factory.py | 2 + test/test_datasource.py | 48 ++++++++++--------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index c03a4fadc..4cbea1443 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -158,6 +158,8 @@ def _generate_xml(self, datasource_item, connection_credentials=None, connection def update_req(self, datasource_item): xml_request = ET.Element("tsRequest") datasource_element = ET.SubElement(xml_request, "datasource") + if datasource_item.name: + datasource_element.attrib["name"] = datasource_item.name if datasource_item.ask_data_enablement: ask_data_element = ET.SubElement(datasource_element, "askData") ask_data_element.attrib["enablement"] = datasource_item.ask_data_enablement diff --git a/test/test_datasource.py b/test/test_datasource.py index 1156069be..e221f0c88 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -11,6 +11,7 @@ from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset + ADD_TAGS_XML = 'datasource_add_tags.xml' GET_XML = 'datasource_get.xml' GET_EMPTY_XML = 'datasource_get_empty.xml' @@ -106,25 +107,28 @@ def test_update(self): response_xml = read_xml_asset(UPDATE_XML) with requests_mock.mock() as m: m.put(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', text=response_xml) - single_datasource = TSC.DatasourceItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') + single_datasource = TSC.DatasourceItem('1d0304cd-3796-429f-b815-7258370b9b74', 'Sample datasource') single_datasource.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' + single_datasource._content_url = 'Sampledatasource' single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' single_datasource.certified = True single_datasource.certification_note = "Warning, here be dragons." - single_datasource = self.server.datasources.update(single_datasource) + updated_datasource = self.server.datasources.update(single_datasource) - self.assertEqual('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', single_datasource.id) - self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', single_datasource.project_id) - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', single_datasource.owner_id) - self.assertEqual(True, single_datasource.certified) - self.assertEqual("Warning, here be dragons.", single_datasource.certification_note) + self.assertEqual(updated_datasource.id, single_datasource.id) + self.assertEqual(updated_datasource.name, single_datasource.name) + self.assertEqual(updated_datasource.content_url, single_datasource.content_url) + self.assertEqual(updated_datasource.project_id, single_datasource.project_id) + self.assertEqual(updated_datasource.owner_id, single_datasource.owner_id) + self.assertEqual(updated_datasource.certified, single_datasource.certified) + self.assertEqual(updated_datasource.certification_note, single_datasource.certification_note) def test_update_copy_fields(self): with open(asset(UPDATE_XML), 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: m.put(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', text=response_xml) - single_datasource = TSC.DatasourceItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') + single_datasource = TSC.DatasourceItem('1d0304cd-3796-429f-b815-7258370b9b74', 'test') single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' single_datasource._project_name = 'Tester' updated_datasource = self.server.datasources.update(single_datasource) @@ -152,7 +156,7 @@ def test_populate_connections(self): response_xml = read_xml_asset(POPULATE_CONNECTIONS_XML) with requests_mock.mock() as m: m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections', text=response_xml) - single_datasource = TSC.DatasourceItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') + single_datasource = TSC.DatasourceItem('1d0304cd-3796-429f-b815-7258370b9b74', 'test') single_datasource.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' self.server.datasources.populate_connections(single_datasource) @@ -180,7 +184,7 @@ def test_update_connection(self): m.put(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/be786ae0-d2bf-4a4b-9b34-e2de8d2d4488', text=response_xml) - single_datasource = TSC.DatasourceItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') + single_datasource = TSC.DatasourceItem('1d0304cd-3796-429f-b815-7258370b9b74') single_datasource.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' self.server.datasources.populate_connections(single_datasource) @@ -201,7 +205,7 @@ def test_populate_permissions(self): response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: m.get(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions', text=response_xml) - single_datasource = TSC.DatasourceItem('test') + single_datasource = TSC.DatasourceItem('1d0304cd-3796-429f-b815-7258370b9b74', 'test') single_datasource._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' self.server.datasources.populate_permissions(single_datasource) @@ -226,7 +230,7 @@ def test_publish(self): response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') publish_mode = self.server.PublishMode.CreateNew new_datasource = self.server.datasources.publish(new_datasource, @@ -247,7 +251,7 @@ def test_publish_a_non_packaged_file_object(self): response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') publish_mode = self.server.PublishMode.CreateNew with open(asset('SampleDS.tds'), 'rb') as file_object: @@ -269,7 +273,7 @@ def test_publish_a_packaged_file_object(self): response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') publish_mode = self.server.PublishMode.CreateNew # Create a dummy tdsx file in memory @@ -299,7 +303,7 @@ def test_publish_async(self): response_xml = read_xml_asset(PUBLISH_XML_ASYNC) with requests_mock.mock() as m: m.post(baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('SampleDS', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') publish_mode = self.server.PublishMode.CreateNew new_job = self.server.datasources.publish(new_datasource, @@ -380,40 +384,40 @@ def test_download_extract_only(self): os.remove(file_path) def test_update_missing_id(self): - single_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + single_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') self.assertRaises(TSC.MissingRequiredFieldError, self.server.datasources.update, single_datasource) def test_publish_missing_path(self): - new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') self.assertRaises(IOError, self.server.datasources.publish, new_datasource, '', self.server.PublishMode.CreateNew) def test_publish_missing_mode(self): - new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, asset('SampleDS.tds'), None) def test_publish_invalid_file_type(self): - new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, asset('SampleWB.twbx'), self.server.PublishMode.Append) def test_publish_hyper_file_object_raises_exception(self): - new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') with open(asset('World Indicators.hyper')) as file_object: self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, file_object, self.server.PublishMode.Append) def test_publish_tde_file_object_raises_exception(self): - new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') tds_asset = asset(os.path.join('Data', 'Tableau Samples', 'World Indicators.tde')) with open(tds_asset) as file_object: self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, file_object, self.server.PublishMode.Append) def test_publish_file_object_of_unknown_type_raises_exception(self): - new_datasource = TSC.DatasourceItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') with BytesIO() as file_object: file_object.write(bytes.fromhex('89504E470D0A1A0A')) From 65c9df2d4c325c9f6d45702a756b9cf5c165a2d5 Mon Sep 17 00:00:00 2001 From: Ovini Nanayakkara <44311587+ovinis@users.noreply.github.com> Date: Mon, 28 Jun 2021 13:29:42 -0700 Subject: [PATCH 11/12] fixed Issue# 840- Missing content permission field LockedToProjectWithoutNested (#856) Co-authored-by: Ovini Nanayakkara --- tableauserverclient/_version.py | 8 ++++---- tableauserverclient/models/project_item.py | 1 + test/assets/project_content_permission.xml | 4 ++++ test/test_database.py | 4 ++-- test/test_project.py | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 test/assets/project_content_permission.xml diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 5e73890bd..1737a980a 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -120,7 +120,7 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return { - "version": dirname[len(parentdir_prefix) :], + "version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, @@ -187,7 +187,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " - tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) + tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -204,7 +204,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix) :] + r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return { @@ -304,7 +304,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): tag_prefix, ) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix) :] + pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index bed6def6e..3a7d01143 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -10,6 +10,7 @@ class ProjectItem(object): class ContentPermissions: LockedToProject = "LockedToProject" ManagedByOwner = "ManagedByOwner" + LockedToProjectWithoutNested = "LockedToProjectWithoutNested" def __init__(self, name, description=None, content_permissions=None, parent_id=None): self._content_permissions = None diff --git a/test/assets/project_content_permission.xml b/test/assets/project_content_permission.xml new file mode 100644 index 000000000..18341e2ac --- /dev/null +++ b/test/assets/project_content_permission.xml @@ -0,0 +1,4 @@ + + + + diff --git a/test/test_database.py b/test/test_database.py index 4de623dae..e7c6a6fb6 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -85,7 +85,8 @@ def test_populate_data_quality_warning(self): with open(asset(GET_DQW_BY_CONTENT), 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: - m.get(self.server.databases._data_quality_warnings.baseurl + '/94441d26-9a52-4a42-b0fb-3f94792d1aac', text=response_xml) + m.get(self.server.databases._data_quality_warnings.baseurl + '/94441d26-9a52-4a42-b0fb-3f94792d1aac', + text=response_xml) single_database = TSC.DatabaseItem('test') single_database._id = '94441d26-9a52-4a42-b0fb-3f94792d1aac' @@ -101,7 +102,6 @@ def test_populate_data_quality_warning(self): self.assertEqual(str(first_dqw.created_at), "2021-04-09 18:39:54+00:00") self.assertEqual(str(first_dqw.updated_at), "2021-04-09 18:39:54+00:00") - def test_delete(self): with requests_mock.mock() as m: m.delete(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5', status_code=204) diff --git a/test/test_project.py b/test/test_project.py index 045f0a43e..be43b063e 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -9,6 +9,7 @@ GET_XML = asset('project_get.xml') UPDATE_XML = asset('project_update.xml') +SET_CONTENT_PERMISSIONS_XML = asset('project_content_permission.xml') CREATE_XML = asset('project_create.xml') POPULATE_PERMISSIONS_XML = 'project_populate_permissions.xml' POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML = 'project_populate_workbook_default_permissions.xml' @@ -83,6 +84,23 @@ def test_update(self): self.assertEqual('LockedToProject', single_project.content_permissions) self.assertEqual('9a8f2265-70f3-4494-96c5-e5949d7a1120', single_project.parent_id) + def test_content_permission_locked_to_project_without_nested(self): + with open(SET_CONTENT_PERMISSIONS_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.put(self.baseurl + '/cb3759e5-da4a-4ade-b916-7e2b4ea7ec86', text=response_xml) + project_item = TSC.ProjectItem(name='Test Project Permissions', + content_permissions='LockedToProjectWithoutNested', + description='Project created for testing', + parent_id='7687bc43-a543-42f3-b86f-80caed03a813') + project_item._id = 'cb3759e5-da4a-4ade-b916-7e2b4ea7ec86' + project_item = self.server.projects.update(project_item) + self.assertEqual('cb3759e5-da4a-4ade-b916-7e2b4ea7ec86', project_item.id) + self.assertEqual('Test Project Permissions', project_item.name) + self.assertEqual('Project created for testing', project_item.description) + self.assertEqual('LockedToProjectWithoutNested', project_item.content_permissions) + self.assertEqual('7687bc43-a543-42f3-b86f-80caed03a813', project_item.parent_id) + def test_update_datasource_default_permission(self): response_xml = read_xml_asset(UPDATE_DATASOURCE_DEFAULT_PERMISSIONS_XML) with requests_mock.mock() as m: From 770d48a6767846e18cb35a7fa3151e372eebfa32 Mon Sep 17 00:00:00 2001 From: Ovini Nanayakkara <44311587+ovinis@users.noreply.github.com> Date: Fri, 16 Jul 2021 12:31:03 -0400 Subject: [PATCH 12/12] Updated Changelog and contributors for the new release (#863) * Updated Changelog and contributors for the new release * updated Changelog to reflect Issue 844 accurately Co-authored-by: Ovini Nanayakkara --- CHANGELOG.md | 10 ++++++++++ CONTRIBUTORS.md | 2 ++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45a44b251..c4c9197f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.16.0 (15 July 2021) +* Documentation updates (#800, #818, #839, #842) +* Fixed data alert repr in subscription item (#821) +* Added support for Data Quality Warning (#836) +* Added support for renaming datasources (#843) +* Improved Datasource tests (#843) +* Updated catalog obfuscation field (#844) +* Fixed revision limit field in site_item.py file (#847) +* Added the Missing content permission field- LockedToProjectWithoutNested (#856) + ## 0.15.0 (16 Feb 2021) * Added support for python version 3.9 (#744) * Added support for 'Get View by ID' (#750) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2a19b1317..74b20d93d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -44,6 +44,7 @@ The following people have contributed to this project to make it possible, and w * [Terrence Jones](https://github.com/tjones-commits) * [John Vandenberg](https://github.com/jayvdb) * [Lee Boynton](https://github.com/lboynton) +* [annematronic](https://github.com/annematronic) ## Core Team @@ -57,3 +58,4 @@ The following people have contributed to this project to make it possible, and w * [Jac Fitzgerald](https://github.com/jacalata) * [Dan Zucker](https://github.com/dzucker-tab) * [Brian Cantoni](https://github.com/bcantoni) +* [Ovini Nanayakkara](https://github.com/ovinis)