Skip to content

Commit ef9e7fd

Browse files
jacalataMrwanBaghdadjorwoodsbcantoni
authored
Add custom session injection, Fix bug for http_options (#1119)
* ssl-verify is an option, not a header * Allow injection of session_factory to allow use of a custom session * show server info (#1118) * Fix bug in exposing ExcelRequestOptions and test (#1123) * Fix a few pylint errors (#1124) Co-authored-by: Marwan Baghdad <[email protected]> Co-authored-by: jorwoods <[email protected]> Co-authored-by: Brian Cantoni <[email protected]>
1 parent f653e15 commit ef9e7fd

17 files changed

+174
-69
lines changed

contributing.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,22 @@ pytest
6666
pip install .
6767
```
6868

69+
### Debugging Tools
70+
See what your outgoing requests look like: https://requestbin.net/ (unaffiliated link not under our control)
71+
72+
6973
### Before Committing
7074

7175
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.
7276

7377
```shell
7478
# this will run the formatter without making changes
75-
black --line-length 120 tableauserverclient test samples --check
79+
black . --check
7680

7781
# this will format the directory and code for you
78-
black --line-length 120 tableauserverclient test samples
82+
black .
7983

8084
# this will run type checking
8185
pip install mypy
82-
mypy --show-error-codes --disable-error-code misc --disable-error-code import tableauserverclient test
86+
mypy tableauserverclient test samples
8387
```

samples/create_group.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def main():
4646
logging.basicConfig(level=logging_level)
4747

4848
tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site)
49-
server = TSC.Server(args.server, use_server_version=True)
49+
server = TSC.Server(args.server, use_server_version=True, http_options={"verify": False})
5050
with server.auth.sign_in(tableau_auth):
5151
# this code shows 3 different error codes that mean "resource is already in collection"
5252
# 409009: group already exists on server

samples/initialize_server.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ def main():
5656

5757
# Create the site if it doesn't exist
5858
if existing_site is None:
59-
print("Site not found: {0} Creating it...").format(args.site_id)
59+
print("Site not found: {0} Creating it...".format(args.site_id))
6060
new_site = TSC.SiteItem(
6161
name=args.site_id,
6262
content_url=args.site_id.replace(" ", ""),
6363
admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers,
6464
)
6565
server.sites.create(new_site)
6666
else:
67-
print("Site {0} exists. Moving on...").format(args.site_id)
67+
print("Site {0} exists. Moving on...".format(args.site_id))
6868

6969
################################################################################
7070
# Step 3: Sign-in to our target site
@@ -87,7 +87,7 @@ def main():
8787

8888
# Create our project if it doesn't exist
8989
if project is None:
90-
print("Project not found: {0} Creating it...").format(args.project)
90+
print("Project not found: {0} Creating it...".format(args.project))
9191
new_project = TSC.ProjectItem(name=args.project)
9292
project = server_upload.projects.create(new_project)
9393

tableauserverclient/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
)
4040
from .server import (
4141
CSVRequestOptions,
42+
ExcelRequestOptions,
4243
ImageRequestOptions,
4344
PDFRequestOptions,
4445
RequestOptions,

tableauserverclient/models/flow_item.py

-4
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,6 @@ def description(self, value: str) -> None:
9393
def project_name(self) -> Optional[str]:
9494
return self._project_name
9595

96-
@property
97-
def flow_type(self): # What is this? It doesn't seem to get set anywhere.
98-
return self._flow_type
99-
10096
@property
10197
def updated_at(self) -> Optional["datetime.datetime"]:
10298
return self._updated_at

tableauserverclient/models/permissions_item.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def from_response(cls, resp, ns=None) -> List["PermissionsRule"]:
6969
mode = capability_xml.get("mode")
7070

7171
if name is None or mode is None:
72-
logger.error("Capability was not valid: ", capability_xml)
72+
logger.error("Capability was not valid: {}".format(capability_xml))
7373
raise UnpopulatedPropertyError()
7474
else:
7575
capability_dict[name] = mode

tableauserverclient/models/revision_item.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ def user_name(self) -> Optional[str]:
5353

5454
def __repr__(self):
5555
return (
56-
"<RevisionItem# revisionNumber={_revision_number} " "current={_current} deleted={_deleted} user={_user_id}>"
57-
).format(**self.__dict__)
56+
"<RevisionItem# revisionNumber={_revision_number} "
57+
"current={_current} deleted={_deleted} user={_user_id}>".format(**self.__dict__)
58+
)
5859

5960
@classmethod
6061
def from_response(cls, resp: bytes, ns, resource_item) -> List["RevisionItem"]:

tableauserverclient/models/server_info_item.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import warnings
2+
import xml
3+
14
from defusedxml.ElementTree import fromstring
25

36

@@ -32,7 +35,11 @@ def rest_api_version(self):
3235

3336
@classmethod
3437
def from_response(cls, resp, ns):
35-
parsed_response = fromstring(resp)
38+
try:
39+
parsed_response = fromstring(resp)
40+
except xml.etree.ElementTree.ParseError as error:
41+
warnings.warn("Unexpected response for ServerInfo: {}".format(resp))
42+
return cls("Unknown", "Unknown", "Unknown")
3643
product_version_tag = parsed_response.find(".//t:productVersion", namespaces=ns)
3744
rest_api_version_tag = parsed_response.find(".//t:restApiVersion", namespaces=ns)
3845

tableauserverclient/models/site_item.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import warnings
22
import xml.etree.ElementTree as ET
33

4-
from distutils.version import Version
54
from defusedxml.ElementTree import fromstring
65
from .property_decorators import (
76
property_is_enum,

tableauserverclient/models/tableau_auth.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def credentials(self):
99
+"This method returns values to set as an attribute on the credentials element of the request"
1010

1111
def __repr__(self):
12-
display = "All Credentials types must have a debug display that does not print secrets"
12+
return "All Credentials types must have a debug display that does not print secrets"
1313

1414

1515
def deprecate_site_attribute():

tableauserverclient/server/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .request_factory import RequestFactory
33
from .request_options import (
44
CSVRequestOptions,
5+
ExcelRequestOptions,
56
ImageRequestOptions,
67
PDFRequestOptions,
78
RequestOptions,

tableauserverclient/server/endpoint/databases_endpoint.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def update_table_default_permissions(self, item):
116116

117117
@api(version="3.5")
118118
def delete_table_default_permissions(self, item):
119-
self._default_permissions.delete_default_permissions(item, Resource.Table)
119+
self._default_permissions.delete_default_permission(item, Resource.Table)
120120

121121
@api(version="3.5")
122122
def populate_dqw(self, item):

tableauserverclient/server/endpoint/endpoint.py

+13-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import requests
22
import logging
3-
from distutils.version import LooseVersion as Version
3+
from packaging.version import Version
44
from functools import wraps
55
from xml.etree.ElementTree import ParseError
66
from typing import Any, Callable, Dict, Optional, TYPE_CHECKING
@@ -11,9 +11,12 @@
1111
NonXMLResponseError,
1212
EndpointUnavailableError,
1313
)
14-
from .. import endpoint
1514
from ..query import QuerySet
1615
from ... import helpers
16+
from ..._version import get_versions
17+
18+
__TSC_VERSION__ = get_versions()["version"]
19+
del get_versions
1720

1821
logger = logging.getLogger("tableau.endpoint")
1922

@@ -22,34 +25,25 @@
2225
XML_CONTENT_TYPE = "text/xml"
2326
JSON_CONTENT_TYPE = "application/json"
2427

28+
USERAGENT_HEADER = "User-Agent"
29+
2530
if TYPE_CHECKING:
2631
from ..server import Server
2732
from requests import Response
2833

2934

30-
_version_header: Optional[str] = None
31-
32-
3335
class Endpoint(object):
3436
def __init__(self, parent_srv: "Server"):
35-
global _version_header
3637
self.parent_srv = parent_srv
3738

3839
@staticmethod
3940
def _make_common_headers(auth_token, content_type):
40-
global _version_header
41-
42-
if not _version_header:
43-
from ..server import __TSC_VERSION__
44-
45-
_version_header = __TSC_VERSION__
46-
4741
headers = {}
4842
if auth_token is not None:
4943
headers["x-tableau-auth"] = auth_token
5044
if content_type is not None:
5145
headers["content-type"] = content_type
52-
headers["User-Agent"] = "Tableau Server Client/{}".format(_version_header)
46+
headers["User-Agent"] = "Tableau Server Client/{}".format(__TSC_VERSION__)
5347
return headers
5448

5549
def _make_request(
@@ -62,9 +56,9 @@ def _make_request(
6256
parameters: Optional[Dict[str, Any]] = None,
6357
) -> "Response":
6458
parameters = parameters or {}
65-
parameters.update(self.parent_srv.http_options)
6659
if "headers" not in parameters:
6760
parameters["headers"] = {}
61+
parameters.update(self.parent_srv.http_options)
6862
parameters["headers"].update(Endpoint._make_common_headers(auth_token, content_type))
6963

7064
if content is not None:
@@ -89,14 +83,12 @@ def _check_status(self, server_response, url: str = None):
8983
if server_response.status_code >= 500:
9084
raise InternalServerError(server_response, url)
9185
elif server_response.status_code not in Success_codes:
92-
# todo: is an error reliably of content-type application/xml?
9386
try:
9487
raise ServerResponseError.from_response(server_response.content, self.parent_srv.namespace, url)
9588
except ParseError:
96-
# This will happen if we get a non-success HTTP code that
97-
# doesn't return an xml error object (like metadata endpoints or 503 pages)
98-
# we convert this to a better exception and pass through the raw
99-
# response body
89+
# This will happen if we get a non-success HTTP code that doesn't return an xml error object
90+
# e.g metadata endpoints, 503 pages, totally different servers
91+
# we convert this to a better exception and pass through the raw response body
10092
raise NonXMLResponseError(server_response.content)
10193
except Exception:
10294
# anything else re-raise here
@@ -194,7 +186,7 @@ def api(version):
194186
def _decorator(func):
195187
@wraps(func)
196188
def wrapper(self, *args, **kwargs):
197-
self.parent_srv.assert_at_least_version(version, "endpoint")
189+
self.parent_srv.assert_at_least_version(version, self.__class__.__name__)
198190
return func(self, *args, **kwargs)
199191

200192
return wrapper

tableauserverclient/server/endpoint/server_info_endpoint.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212

1313

1414
class ServerInfo(Endpoint):
15+
def __init__(self, server):
16+
self.parent_srv = server
17+
self._info = None
18+
19+
@property
20+
def serverInfo(self):
21+
if not self._info:
22+
self.get()
23+
return self._info
24+
25+
def __repr__(self):
26+
return "<Endpoint {}>".format(self.serverInfo)
27+
1528
@property
1629
def baseurl(self):
1730
return "{0}/serverInfo".format(self.parent_srv.baseurl)
@@ -23,10 +36,10 @@ def get(self):
2336
server_response = self.get_unauthenticated_request(self.baseurl)
2437
except ServerResponseError as e:
2538
if e.code == "404003":
26-
raise ServerInfoEndpointNotFoundError
39+
raise ServerInfoEndpointNotFoundError(e)
2740
if e.code == "404001":
28-
raise EndpointUnavailableError
41+
raise EndpointUnavailableError(e)
2942
raise e
3043

31-
server_info = ServerInfoItem.from_response(server_response.content, self.parent_srv.namespace)
32-
return server_info
44+
self._info = ServerInfoItem.from_response(server_response.content, self.parent_srv.namespace)
45+
return self._info

0 commit comments

Comments
 (0)