Skip to content

Commit 7de7937

Browse files
committed
Merge branch 'development' into jac/client-version-header
2 parents 7752118 + 88473b1 commit 7de7937

File tree

15 files changed

+235
-72
lines changed

15 files changed

+235
-72
lines changed

samples/explore_site.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
####
2+
# This script demonstrates how to use the Tableau Server Client
3+
# to interact with sites.
4+
####
5+
6+
import argparse
7+
import logging
8+
import os.path
9+
import sys
10+
11+
import tableauserverclient as TSC
12+
13+
14+
def main():
15+
16+
parser = argparse.ArgumentParser(description="Explore site updates by the Server API.")
17+
# Common options; please keep those in sync across all samples
18+
parser.add_argument("--server", "-s", required=True, help="server address")
19+
parser.add_argument("--site", "-S", help="site name")
20+
parser.add_argument(
21+
"--token-name", "-p", required=True, help="name of the personal access token used to sign into the server"
22+
)
23+
parser.add_argument(
24+
"--token-value", "-v", required=True, help="value of the personal access token used to sign into the server"
25+
)
26+
parser.add_argument(
27+
"--logging-level",
28+
"-l",
29+
choices=["debug", "info", "error"],
30+
default="error",
31+
help="desired logging level (set to error by default)",
32+
)
33+
34+
parser.add_argument("--delete")
35+
parser.add_argument("--create")
36+
parser.add_argument("--url")
37+
parser.add_argument("--new_site_name")
38+
parser.add_argument("--user_quota")
39+
parser.add_argument("--storage_quota")
40+
parser.add_argument("--status")
41+
42+
args = parser.parse_args()
43+
44+
# Set logging level based on user input, or error by default
45+
logging_level = getattr(logging, args.logging_level.upper())
46+
logging.basicConfig(level=logging_level)
47+
48+
# SIGN IN
49+
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
50+
server = TSC.Server(args.server, use_server_version=True)
51+
new_site = None
52+
with server.auth.sign_in(tableau_auth):
53+
current_site = server.sites.get_by_id(server.site_id)
54+
55+
if args.delete:
56+
print("You can only delete the site you are currently in")
57+
print("Delete site `{}`?".format(current_site.name))
58+
# server.sites.delete(server.site_id)
59+
60+
elif args.create:
61+
new_site = TSC.SiteItem(args.create, args.url or args.create)
62+
site_item = server.sites.create(new_site)
63+
print(site_item)
64+
# to do anything further with the site, you need to log into it
65+
# if a PAT is required, that means going to the UI to create one
66+
67+
else:
68+
new_site = current_site
69+
print(current_site, "current user quota:", current_site.user_quota)
70+
print("Remember, you can only update the site you are currently in")
71+
if args.url:
72+
new_site.content_url = args.url
73+
if args.user_quota:
74+
new_site.user_quota = args.user_quota
75+
try:
76+
updated_site = server.sites.update(new_site)
77+
print(updated_site, "new user quota:", updated_site.user_quota)
78+
except TSC.ServerResponseError as e:
79+
print(e)
80+
81+
82+
if __name__ == "__main__":
83+
main()

samples/list.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ def main():
5959
count = 0
6060
for resource in TSC.Pager(endpoint.get, options):
6161
count = count + 1
62-
print(resource.id, resource.name)
62+
# endpoint.populate_connections(resource)
63+
print(resource.name[:18], " ") # , resource._connections())
64+
if count > 100:
65+
break
6366
print("Total: {}".format(count))
6467

6568

tableauserverclient/datetime_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import datetime
22

3-
# This code below is from the python documentation for
4-
# tzinfo: https://docs.python.org/2.3/lib/datetime-tzinfo.html
53

64
ZERO = datetime.timedelta(0)
75
HOUR = datetime.timedelta(hours=1)
86

97

8+
# This class is a concrete implementation of the abstract base class tzinfo
9+
# docs: https://docs.python.org/2.3/lib/datetime-tzinfo.html
1010
class UTC(datetime.tzinfo):
1111
"""UTC"""
1212

tableauserverclient/models/site_item.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import warnings
22
import xml.etree.ElementTree as ET
33

4+
from distutils.version import Version
45
from defusedxml.ElementTree import fromstring
5-
66
from .property_decorators import (
77
property_is_enum,
88
property_is_boolean,
@@ -14,7 +14,10 @@
1414

1515
VALID_CONTENT_URL_RE = r"^[a-zA-Z0-9_\-]*$"
1616

17-
from typing import List, Optional, Union
17+
from typing import List, Optional, Union, TYPE_CHECKING
18+
19+
if TYPE_CHECKING:
20+
from tableauserverclient.server import Server
1821

1922

2023
class SiteItem(object):
@@ -23,6 +26,19 @@ class SiteItem(object):
2326
_tier_explorer_capacity: Optional[int] = None
2427
_tier_viewer_capacity: Optional[int] = None
2528

29+
def __str__(self):
30+
return (
31+
"<"
32+
+ __name__
33+
+ ": "
34+
+ (self.name or "unnamed")
35+
+ ", "
36+
+ (self.id or "unknown-id")
37+
+ ", "
38+
+ (self.state or "unknown-state")
39+
+ ">"
40+
)
41+
2642
class AdminMode:
2743
ContentAndUsers: str = "ContentAndUsers"
2844
ContentOnly: str = "ContentOnly"
@@ -261,18 +277,24 @@ def cataloging_enabled(self) -> bool:
261277
def cataloging_enabled(self, value: bool):
262278
self._cataloging_enabled = value
263279

280+
def is_default(self) -> bool:
281+
return self.name.lower() == "default"
282+
283+
@staticmethod
284+
def use_new_flow_settings(parent_srv: "Server") -> bool:
285+
return parent_srv is not None and parent_srv.check_at_least_version("3.10")
286+
264287
@property
265288
def flows_enabled(self) -> bool:
266289
return self._flows_enabled
267290

268291
@flows_enabled.setter
269292
@property_is_boolean
270293
def flows_enabled(self, value: bool) -> None:
294+
# Flows Enabled' is not a supported site setting in API Version [3.17].
295+
# In Version 3.10+ use the more granular settings 'Editing Flows Enabled' and/or 'Scheduling Flows Enabled'
271296
self._flows_enabled = value
272297

273-
def is_default(self) -> bool:
274-
return self.name.lower() == "default"
275-
276298
@property
277299
def editing_flows_enabled(self) -> bool:
278300
return self._editing_flows_enabled

tableauserverclient/server/endpoint/endpoint.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def _make_request(
7575
logger.debug("request content: {}".format(helpers.strings.redact_xml(content[:1000])))
7676

7777
server_response = method(url, **parameters)
78-
self._check_status(server_response)
78+
self._check_status(server_response, url)
7979

8080
loggable_response = self.log_response_safely(server_response)
8181
logger.debug("Server response from {0}:\n\t{1}".format(url, loggable_response))
@@ -85,9 +85,9 @@ def _make_request(
8585

8686
return server_response
8787

88-
def _check_status(self, server_response):
88+
def _check_status(self, server_response, request_url=None):
8989
if server_response.status_code >= 500:
90-
raise InternalServerError(server_response)
90+
raise InternalServerError(server_response, request_url)
9191
elif server_response.status_code not in Success_codes:
9292
# todo: is an error reliably of content-type application/xml?
9393
try:
@@ -124,7 +124,7 @@ def get_request(self, url, request_object=None, parameters=None):
124124
if request_object is not None:
125125
try:
126126
# Query param delimiters don't need to be encoded for versions before 3.7 (2020.1)
127-
self.parent_srv.assert_at_least_version("3.7")
127+
self.parent_srv.assert_at_least_version("3.7", "Query param encoding")
128128
parameters = parameters or {}
129129
parameters["params"] = request_object.get_query_params()
130130
except EndpointUnavailableError:
@@ -194,7 +194,7 @@ def api(version):
194194
def _decorator(func):
195195
@wraps(func)
196196
def wrapper(self, *args, **kwargs):
197-
self.parent_srv.assert_at_least_version(version)
197+
self.parent_srv.assert_at_least_version(version, "endpoint")
198198
return func(self, *args, **kwargs)
199199

200200
return wrapper

tableauserverclient/server/endpoint/exceptions.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from defusedxml.ElementTree import fromstring
22

33

4-
class ServerResponseError(Exception):
4+
class TableauError(Exception):
5+
pass
6+
7+
8+
class ServerResponseError(TableauError):
59
def __init__(self, code, summary, detail):
610
self.code = code
711
self.summary = summary
@@ -23,40 +27,41 @@ def from_response(cls, resp, ns):
2327
return error_response
2428

2529

26-
class InternalServerError(Exception):
27-
def __init__(self, server_response):
30+
class InternalServerError(TableauError):
31+
def __init__(self, server_response, request_url):
2832
self.code = server_response.status_code
2933
self.content = server_response.content
34+
self.url = request_url or "server"
3035

3136
def __str__(self):
32-
return "\n\nError status code: {0}\n{1}".format(self.code, self.content)
37+
return "\n\nInternal error {0} at {1}\n{2}".format(self.code, self.url, self.content)
3338

3439

35-
class MissingRequiredFieldError(Exception):
40+
class MissingRequiredFieldError(TableauError):
3641
pass
3742

3843

39-
class ServerInfoEndpointNotFoundError(Exception):
44+
class ServerInfoEndpointNotFoundError(TableauError):
4045
pass
4146

4247

43-
class EndpointUnavailableError(Exception):
48+
class EndpointUnavailableError(TableauError):
4449
pass
4550

4651

47-
class ItemTypeNotAllowed(Exception):
52+
class ItemTypeNotAllowed(TableauError):
4853
pass
4954

5055

51-
class NonXMLResponseError(Exception):
56+
class NonXMLResponseError(TableauError):
5257
pass
5358

5459

55-
class InvalidGraphQLQuery(Exception):
60+
class InvalidGraphQLQuery(TableauError):
5661
pass
5762

5863

59-
class GraphQLError(Exception):
64+
class GraphQLError(TableauError):
6065
def __init__(self, error_payload):
6166
self.error = error_payload
6267

@@ -66,7 +71,7 @@ def __str__(self):
6671
return pformat(self.error)
6772

6873

69-
class JobFailedException(Exception):
74+
class JobFailedException(TableauError):
7075
def __init__(self, job):
7176
self.notes = job.notes
7277
self.job = job
@@ -79,7 +84,7 @@ class JobCancelledException(JobFailedException):
7984
pass
8085

8186

82-
class FlowRunFailedException(Exception):
87+
class FlowRunFailedException(TableauError):
8388
def __init__(self, flow_run):
8489
self.background_job_id = flow_run.background_job_id
8590
self.flow_run = flow_run

tableauserverclient/server/endpoint/jobs_endpoint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def get(
2929
if isinstance(job_id, RequestOptionsBase):
3030
req_options = job_id
3131

32-
self.parent_srv.assert_at_least_version("3.1")
32+
self.parent_srv.assert_at_least_version("3.1", "Jobs.get_by_id(job_id)")
3333
server_response = self.get_request(self.baseurl, req_options)
3434
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
3535
jobs = BackgroundJobItem.from_response(server_response.content, self.parent_srv.namespace)

0 commit comments

Comments
 (0)