Skip to content

Commit 72eb3c8

Browse files
jacalataLGraberdependabot[bot]
authored
0.28 - JWT Auth (tableau#1288)
* Code coverage and pretty printing (tableau#1283) * implement str and repr for a bunch more classes * also: JWT, user-impersonation (cherry picked from commit 4887a62) * fix code coverage action * use reflection to find all models for comprehensive testing. --------- Co-authored-by: Lee Graber <[email protected]> * update publish action (tableau#1286) * 0.27 (tableau#1272) * Bump urllib3 from 2.0.4 to 2.0.6 (tableau#1287) * Bump urllib3 from 2.0.4 to 2.0.6 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.6. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.0.4...2.0.6) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <[email protected]> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: Lee Graber <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 7ed0a43 commit 72eb3c8

27 files changed

+207
-90
lines changed

.github/workflows/code-coverage.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
3131
# https://github.com/marketplace/actions/pytest-coverage-comment
3232
- name: Generate coverage report
33-
run: pytest --junitxml=pytest.xml --cov=tableauserverclient tests/ | tee pytest-coverage.txt
33+
run: pytest --junitxml=pytest.xml --cov=tableauserverclient test/ | tee pytest-coverage.txt
3434

3535
- name: Comment on pull request with coverage
3636
uses: MishaKav/pytest-coverage-comment@main

.github/workflows/publish-pypi.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
fetch-depth: 0
2020
- uses: actions/setup-python@v4
2121
with:
22-
python-version: 3.7
22+
python-version: 3.9
2323
- name: Build dist files
2424
run: |
2525
python -m pip install --upgrade pip

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ For more information on installing and using TSC, see the documentation:
1919

2020

2121
## License
22-
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python?ref=badge_large)
22+
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python?ref=badge_large)

pyproject.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dependencies = [
1515
'defusedxml>=0.7.1', # latest as at 7/31/23
1616
'packaging>=23.1', # latest as at 7/31/23
1717
'requests>=2.31', # latest as at 7/31/23
18-
'urllib3==2.0.4', # latest as at 7/31/23
18+
'urllib3==2.0.6', # latest as at 7/31/23
1919
]
2020
requires-python = ">=3.7"
2121
classifiers = [
@@ -31,7 +31,8 @@ classifiers = [
3131
repository = "https://github.com/tableau/server-client-python"
3232

3333
[project.optional-dependencies]
34-
test = ["argparse", "black==23.7", "mock", "mypy==1.4", "pytest>=7.0", "pytest-subtests", "requests-mock>=1.0,<2.0"]
34+
test = ["argparse", "black==23.7", "mock", "mypy==1.4", "pytest>=7.0", "pytest-cov", "pytest-subtests",
35+
"requests-mock>=1.0,<2.0"]
3536

3637
[tool.black]
3738
line-length = 120

tableauserverclient/__init__.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,47 @@
11
from ._version import get_versions
22
from .namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE
3-
from .models import *
3+
from .models import (
4+
BackgroundJobItem,
5+
ColumnItem,
6+
ConnectionCredentials,
7+
ConnectionItem,
8+
CustomViewItem,
9+
DQWItem,
10+
DailyInterval,
11+
DataAlertItem,
12+
DatabaseItem,
13+
DatasourceItem,
14+
FavoriteItem,
15+
FlowItem,
16+
FlowRunItem,
17+
FileuploadItem,
18+
GroupItem,
19+
HourlyInterval,
20+
IntervalItem,
21+
JobItem,
22+
JWTAuth,
23+
MetricItem,
24+
MonthlyInterval,
25+
PaginationItem,
26+
Permission,
27+
PermissionsRule,
28+
PersonalAccessTokenAuth,
29+
ProjectItem,
30+
RevisionItem,
31+
ScheduleItem,
32+
SiteItem,
33+
ServerInfoItem,
34+
SubscriptionItem,
35+
TableItem,
36+
TableauAuth,
37+
Target,
38+
TaskItem,
39+
UserItem,
40+
ViewItem,
41+
WebhookItem,
42+
WeeklyInterval,
43+
WorkbookItem,
44+
)
445
from .server import (
546
CSVRequestOptions,
647
ExcelRequestOptions,

tableauserverclient/models/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from .site_item import SiteItem
3232
from .subscription_item import SubscriptionItem
3333
from .table_item import TableItem
34-
from .tableau_auth import Credentials, TableauAuth, PersonalAccessTokenAuth
34+
from .tableau_auth import Credentials, TableauAuth, PersonalAccessTokenAuth, JWTAuth
3535
from .tableau_types import Resource, TableauItem, plural_type
3636
from .tag_item import TagItem
3737
from .target import Target

tableauserverclient/models/column_item.py

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ def __init__(self, name, description=None):
99
self.description = description
1010
self.name = name
1111

12+
def __repr__(self):
13+
return f"<{self.__class__.__name__} {self._id} {self.name} {self.description}>"
14+
1215
@property
1316
def id(self):
1417
return self._id

tableauserverclient/models/connection_credentials.py

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ def __init__(self, name, password, embed=True, oauth=False):
1515
self.embed = embed
1616
self.oauth = oauth
1717

18+
def __repr__(self):
19+
if self.password:
20+
print = "redacted"
21+
else:
22+
print = "None"
23+
return f"<{self.__class__.__name__} name={self.name} password={print} embed={self.embed} oauth={self.oauth} >"
24+
1825
@property
1926
def embed(self):
2027
return self._embed

tableauserverclient/models/data_acceleration_report_item.py

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def avg_non_accelerated_plt(self):
4646
def __init__(self, comparison_records):
4747
self._comparison_records = comparison_records
4848

49+
def __repr__(self):
50+
return f"<(deprecated)DataAccelerationReportItem site={self.site} sheet={sheet_uri}>"
51+
4952
@property
5053
def comparison_records(self):
5154
return self._comparison_records

tableauserverclient/models/group_item.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ def __init__(self, name=None, domain_name=None) -> None:
2626
self.name: Optional[str] = name
2727
self.domain_name: Optional[str] = domain_name
2828

29-
def __str__(self):
29+
def __repr__(self):
3030
return "{}({!r})".format(self.__class__.__name__, self.__dict__)
3131

32-
__repr__ = __str__
33-
3432
@property
3533
def domain_name(self) -> Optional[str]:
3634
return self._domain_name
@@ -48,7 +46,6 @@ def name(self) -> Optional[str]:
4846
return self._name
4947

5048
@name.setter
51-
@property_not_empty
5249
def name(self, value: str) -> None:
5350
self._name = value
5451

tableauserverclient/models/interval_item.py

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ def __init__(self, start_time, end_time, interval_value):
3131
self.end_time = end_time
3232
self.interval = interval_value
3333

34+
def __repr__(self):
35+
return f"<{self.__class__.__name__} start={self.start_time} end={self.end_time} interval={self.interval}>"
36+
3437
@property
3538
def _frequency(self):
3639
return IntervalItem.Frequency.Hourly
@@ -86,6 +89,9 @@ def __init__(self, start_time, *interval_values):
8689
self.start_time = start_time
8790
self.interval = interval_values
8891

92+
def __repr__(self):
93+
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"
94+
8995
@property
9096
def _frequency(self):
9197
return IntervalItem.Frequency.Daily
@@ -114,6 +120,9 @@ def __init__(self, start_time, *interval_values):
114120
self.start_time = start_time
115121
self.interval = interval_values
116122

123+
def __repr__(self):
124+
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"
125+
117126
@property
118127
def _frequency(self):
119128
return IntervalItem.Frequency.Weekly
@@ -148,6 +157,9 @@ def __init__(self, start_time, interval_value):
148157
self.start_time = start_time
149158
self.interval = str(interval_value)
150159

160+
def __repr__(self):
161+
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"
162+
151163
@property
152164
def _frequency(self):
153165
return IntervalItem.Frequency.Monthly

tableauserverclient/models/job_item.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,15 @@ def flow_run(self, value):
117117
def updated_at(self) -> Optional[datetime.datetime]:
118118
return self._updated_at
119119

120-
def __repr__(self):
120+
def __str__(self):
121121
return (
122122
"<Job#{_id} {_type} created_at({_created_at}) started_at({_started_at}) updated_at({_updated_at}) completed_at({_completed_at})"
123123
" progress ({_progress}) finish_code({_finish_code})>".format(**self.__dict__)
124124
)
125125

126+
def __repr__(self):
127+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
128+
126129
@classmethod
127130
def from_response(cls, xml, ns) -> List["JobItem"]:
128131
parsed_response = fromstring(xml)
@@ -202,6 +205,12 @@ def __init__(
202205
self._title = title
203206
self._subtitle = subtitle
204207

208+
def __str__(self):
209+
return f"<{self.__class__.name} {self._id} {self._type}>"
210+
211+
def __repr__(self):
212+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
213+
205214
@property
206215
def id(self) -> str:
207216
return self._id

tableauserverclient/models/metric_item.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,12 @@ def view_id(self, value: Optional[str]) -> None:
115115
def _set_permissions(self, permissions):
116116
self._permissions = permissions
117117

118-
def __repr__(self):
118+
def __str__(self):
119119
return "<MetricItem# name={_name} id={_id} owner_id={_owner_id}>".format(**vars(self))
120120

121+
def __repr__(self):
122+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
123+
121124
@classmethod
122125
def from_response(
123126
cls,

tableauserverclient/models/pagination_item.py

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ def __init__(self):
77
self._page_size = None
88
self._total_available = None
99

10+
def __repr__(self):
11+
return f"<PaginationItem page_number={self._page_number} page_size={self._page_size} total={self._total_available}>"
12+
1013
@property
1114
def page_number(self) -> int:
1215
return self._page_number

tableauserverclient/models/permissions_item.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import logging
21
import xml.etree.ElementTree as ET
32
from typing import Dict, List, Optional
43

@@ -17,6 +16,9 @@ class Mode:
1716
Allow = "Allow"
1817
Deny = "Deny"
1918

19+
def __repr__(self):
20+
return "<Enum Mode: Allow | Deny>"
21+
2022
class Capability:
2123
AddComment = "AddComment"
2224
ChangeHierarchy = "ChangeHierarchy"
@@ -39,17 +41,18 @@ class Capability:
3941
CreateRefreshMetrics = "CreateRefreshMetrics"
4042
SaveAs = "SaveAs"
4143

44+
def __repr__(self):
45+
return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"
46+
4247

4348
class PermissionsRule(object):
4449
def __init__(self, grantee: ResourceReference, capabilities: Dict[str, str]) -> None:
4550
self.grantee = grantee
4651
self.capabilities = capabilities
4752

48-
def __str__(self):
53+
def __repr__(self):
4954
return "<PermissionsRule grantee={}, capabilities={}>".format(self.grantee, self.capabilities)
5055

51-
__repr__ = __str__
52-
5356
@classmethod
5457
def from_response(cls, resp, ns=None) -> List["PermissionsRule"]:
5558
parsed_response = fromstring(resp)

tableauserverclient/models/schedule_item.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,12 @@ def __init__(self, name: str, priority: int, schedule_type: str, execution_order
4848
self.priority: int = priority
4949
self.schedule_type: str = schedule_type
5050

51-
def __repr__(self):
51+
def __str__(self):
5252
return '<Schedule#{_id} "{_name}" {interval_item}>'.format(**vars(self))
5353

54+
def __repr__(self):
55+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
56+
5457
@property
5558
def created_at(self) -> Optional[datetime]:
5659
return self._created_at

tableauserverclient/models/server_info_item.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def __init__(self, product_version, build_number, rest_api_version):
1212
self._build_number = build_number
1313
self._rest_api_version = rest_api_version
1414

15-
def __str__(self):
15+
def __repr__(self):
1616
return (
1717
"ServerInfoItem: [product version: "
1818
+ self._product_version

tableauserverclient/models/site_item.py

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def __str__(self):
3939
+ ">"
4040
)
4141

42+
def __repr__(self):
43+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
44+
4245
class AdminMode:
4346
ContentAndUsers: str = "ContentAndUsers"
4447
ContentOnly: str = "ContentOnly"

tableauserverclient/models/table_item.py

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ def __init__(self, name, description=None):
1919
self._columns = None
2020
self._data_quality_warnings = None
2121

22+
def __str__(self):
23+
return f"<{self.__class__.__name__} {self._id} {self._name} >"
24+
25+
def __repr__(self):
26+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
27+
2228
@property
2329
def permissions(self):
2430
if self._permissions is None:

tableauserverclient/models/tableau_auth.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ def credentials(self):
4343
return {"name": self.username, "password": self.password}
4444

4545
def __repr__(self):
46-
return "<Credentials username={} password={}>".format(self.username, "<redacted>")
46+
if self.user_id_to_impersonate:
47+
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
48+
else:
49+
uid = ""
50+
return f"<Credentials username={self.username} password=redacted (site={self.site_id}{uid})>"
4751

4852
@property
4953
def site(self):
@@ -56,6 +60,7 @@ def site(self, value):
5660
self.site_id = value
5761

5862

63+
# A Tableau-generated Personal Access Token
5964
class PersonalAccessTokenAuth(Credentials):
6065
def __init__(self, token_name, personal_access_token, site_id=None, user_id_to_impersonate=None):
6166
if personal_access_token is None or token_name is None:
@@ -72,13 +77,19 @@ def credentials(self):
7277
}
7378

7479
def __repr__(self):
75-
return "<PersonalAccessToken name={} token={}>(site={})".format(
76-
self.token_name, self.personal_access_token[:2] + "...", self.site_id
80+
if self.user_id_to_impersonate:
81+
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
82+
else:
83+
uid = ""
84+
return (
85+
f"<PersonalAccessToken name={self.token_name} token={self.personal_access_token[:2]}..."
86+
f"(site={self.site_id}{uid} >"
7787
)
7888

7989

90+
# A standard JWT generated specifically for Tableau
8091
class JWTAuth(Credentials):
81-
def __init__(self, jwt=None, site_id=None, user_id_to_impersonate=None):
92+
def __init__(self, jwt: str, site_id=None, user_id_to_impersonate=None):
8293
if jwt is None:
8394
raise TabError("Must provide a JWT token when using JWT authentication")
8495
super().__init__(site_id, user_id_to_impersonate)
@@ -93,4 +104,4 @@ def __repr__(self):
93104
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
94105
else:
95106
uid = ""
96-
return f"<{self.__class__.__qualname__}(jwt={self.jwt[:5]}..., site_id={self.site_id}{uid})>"
107+
return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... (site={self.site_id}{uid})>"

tableauserverclient/models/user_item.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,13 @@ def __init__(
6767

6868
return None
6969

70-
def __repr__(self) -> str:
70+
def __str__(self) -> str:
7171
str_site_role = self.site_role or "None"
7272
return "<User {} name={} role={}>".format(self.id, self.name, str_site_role)
7373

74+
def __repr__(self):
75+
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"
76+
7477
@property
7578
def auth_setting(self) -> Optional[str]:
7679
return self._auth_setting

0 commit comments

Comments
 (0)