Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.28 - JWT Auth #1288

Merged
merged 4 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

# https://github.com/marketplace/actions/pytest-coverage-comment
- name: Generate coverage report
run: pytest --junitxml=pytest.xml --cov=tableauserverclient tests/ | tee pytest-coverage.txt
run: pytest --junitxml=pytest.xml --cov=tableauserverclient test/ | tee pytest-coverage.txt

- name: Comment on pull request with coverage
uses: MishaKav/pytest-coverage-comment@main
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: 3.7
python-version: 3.9
- name: Build dist files
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ For more information on installing and using TSC, see the documentation:


## License
[![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)
[![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)
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies = [
'defusedxml>=0.7.1', # latest as at 7/31/23
'packaging>=23.1', # latest as at 7/31/23
'requests>=2.31', # latest as at 7/31/23
'urllib3==2.0.4', # latest as at 7/31/23
'urllib3==2.0.6', # latest as at 7/31/23
]
requires-python = ">=3.7"
classifiers = [
Expand All @@ -31,7 +31,8 @@ classifiers = [
repository = "https://github.com/tableau/server-client-python"

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

[tool.black]
line-length = 120
Expand Down
43 changes: 42 additions & 1 deletion tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
from ._version import get_versions
from .namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE
from .models import *
from .models import (
BackgroundJobItem,
ColumnItem,
ConnectionCredentials,
ConnectionItem,
CustomViewItem,
DQWItem,
DailyInterval,
DataAlertItem,
DatabaseItem,
DatasourceItem,
FavoriteItem,
FlowItem,
FlowRunItem,
FileuploadItem,
GroupItem,
HourlyInterval,
IntervalItem,
JobItem,
JWTAuth,
MetricItem,
MonthlyInterval,
PaginationItem,
Permission,
PermissionsRule,
PersonalAccessTokenAuth,
ProjectItem,
RevisionItem,
ScheduleItem,
SiteItem,
ServerInfoItem,
SubscriptionItem,
TableItem,
TableauAuth,
Target,
TaskItem,
UserItem,
ViewItem,
WebhookItem,
WeeklyInterval,
WorkbookItem,
)
from .server import (
CSVRequestOptions,
ExcelRequestOptions,
Expand Down
2 changes: 1 addition & 1 deletion tableauserverclient/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .site_item import SiteItem
from .subscription_item import SubscriptionItem
from .table_item import TableItem
from .tableau_auth import Credentials, TableauAuth, PersonalAccessTokenAuth
from .tableau_auth import Credentials, TableauAuth, PersonalAccessTokenAuth, JWTAuth
from .tableau_types import Resource, TableauItem, plural_type
from .tag_item import TagItem
from .target import Target
Expand Down
3 changes: 3 additions & 0 deletions tableauserverclient/models/column_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ def __init__(self, name, description=None):
self.description = description
self.name = name

def __repr__(self):
return f"<{self.__class__.__name__} {self._id} {self.name} {self.description}>"

@property
def id(self):
return self._id
Expand Down
7 changes: 7 additions & 0 deletions tableauserverclient/models/connection_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ def __init__(self, name, password, embed=True, oauth=False):
self.embed = embed
self.oauth = oauth

def __repr__(self):
if self.password:
print = "redacted"
else:
print = "None"
return f"<{self.__class__.__name__} name={self.name} password={print} embed={self.embed} oauth={self.oauth} >"

@property
def embed(self):
return self._embed
Expand Down
3 changes: 3 additions & 0 deletions tableauserverclient/models/data_acceleration_report_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ def avg_non_accelerated_plt(self):
def __init__(self, comparison_records):
self._comparison_records = comparison_records

def __repr__(self):
return f"<(deprecated)DataAccelerationReportItem site={self.site} sheet={sheet_uri}>"

@property
def comparison_records(self):
return self._comparison_records
Expand Down
5 changes: 1 addition & 4 deletions tableauserverclient/models/group_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ def __init__(self, name=None, domain_name=None) -> None:
self.name: Optional[str] = name
self.domain_name: Optional[str] = domain_name

def __str__(self):
def __repr__(self):
return "{}({!r})".format(self.__class__.__name__, self.__dict__)

__repr__ = __str__

@property
def domain_name(self) -> Optional[str]:
return self._domain_name
Expand All @@ -48,7 +46,6 @@ def name(self) -> Optional[str]:
return self._name

@name.setter
@property_not_empty
def name(self, value: str) -> None:
self._name = value

Expand Down
12 changes: 12 additions & 0 deletions tableauserverclient/models/interval_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def __init__(self, start_time, end_time, interval_value):
self.end_time = end_time
self.interval = interval_value

def __repr__(self):
return f"<{self.__class__.__name__} start={self.start_time} end={self.end_time} interval={self.interval}>"

@property
def _frequency(self):
return IntervalItem.Frequency.Hourly
Expand Down Expand Up @@ -86,6 +89,9 @@ def __init__(self, start_time, *interval_values):
self.start_time = start_time
self.interval = interval_values

def __repr__(self):
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"

@property
def _frequency(self):
return IntervalItem.Frequency.Daily
Expand Down Expand Up @@ -114,6 +120,9 @@ def __init__(self, start_time, *interval_values):
self.start_time = start_time
self.interval = interval_values

def __repr__(self):
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"

@property
def _frequency(self):
return IntervalItem.Frequency.Weekly
Expand Down Expand Up @@ -148,6 +157,9 @@ def __init__(self, start_time, interval_value):
self.start_time = start_time
self.interval = str(interval_value)

def __repr__(self):
return f"<{self.__class__.__name__} start={self.start_time} interval={self.interval}>"

@property
def _frequency(self):
return IntervalItem.Frequency.Monthly
Expand Down
11 changes: 10 additions & 1 deletion tableauserverclient/models/job_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,15 @@ def flow_run(self, value):
def updated_at(self) -> Optional[datetime.datetime]:
return self._updated_at

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

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

@classmethod
def from_response(cls, xml, ns) -> List["JobItem"]:
parsed_response = fromstring(xml)
Expand Down Expand Up @@ -202,6 +205,12 @@ def __init__(
self._title = title
self._subtitle = subtitle

def __str__(self):
return f"<{self.__class__.name} {self._id} {self._type}>"

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

@property
def id(self) -> str:
return self._id
Expand Down
5 changes: 4 additions & 1 deletion tableauserverclient/models/metric_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,12 @@ def view_id(self, value: Optional[str]) -> None:
def _set_permissions(self, permissions):
self._permissions = permissions

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

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

@classmethod
def from_response(
cls,
Expand Down
3 changes: 3 additions & 0 deletions tableauserverclient/models/pagination_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ def __init__(self):
self._page_size = None
self._total_available = None

def __repr__(self):
return f"<PaginationItem page_number={self._page_number} page_size={self._page_size} total={self._total_available}>"

@property
def page_number(self) -> int:
return self._page_number
Expand Down
11 changes: 7 additions & 4 deletions tableauserverclient/models/permissions_item.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import logging
import xml.etree.ElementTree as ET
from typing import Dict, List, Optional

Expand All @@ -17,6 +16,9 @@ class Mode:
Allow = "Allow"
Deny = "Deny"

def __repr__(self):
return "<Enum Mode: Allow | Deny>"

class Capability:
AddComment = "AddComment"
ChangeHierarchy = "ChangeHierarchy"
Expand All @@ -39,17 +41,18 @@ class Capability:
CreateRefreshMetrics = "CreateRefreshMetrics"
SaveAs = "SaveAs"

def __repr__(self):
return "<Enum Capability: AddComment | ChangeHierarchy | ChangePermission ... (17 more) >"


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

def __str__(self):
def __repr__(self):
return "<PermissionsRule grantee={}, capabilities={}>".format(self.grantee, self.capabilities)

__repr__ = __str__

@classmethod
def from_response(cls, resp, ns=None) -> List["PermissionsRule"]:
parsed_response = fromstring(resp)
Expand Down
5 changes: 4 additions & 1 deletion tableauserverclient/models/schedule_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ def __init__(self, name: str, priority: int, schedule_type: str, execution_order
self.priority: int = priority
self.schedule_type: str = schedule_type

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

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

@property
def created_at(self) -> Optional[datetime]:
return self._created_at
Expand Down
2 changes: 1 addition & 1 deletion tableauserverclient/models/server_info_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, product_version, build_number, rest_api_version):
self._build_number = build_number
self._rest_api_version = rest_api_version

def __str__(self):
def __repr__(self):
return (
"ServerInfoItem: [product version: "
+ self._product_version
Expand Down
3 changes: 3 additions & 0 deletions tableauserverclient/models/site_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def __str__(self):
+ ">"
)

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

class AdminMode:
ContentAndUsers: str = "ContentAndUsers"
ContentOnly: str = "ContentOnly"
Expand Down
6 changes: 6 additions & 0 deletions tableauserverclient/models/table_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ def __init__(self, name, description=None):
self._columns = None
self._data_quality_warnings = None

def __str__(self):
return f"<{self.__class__.__name__} {self._id} {self._name} >"

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

@property
def permissions(self):
if self._permissions is None:
Expand Down
21 changes: 16 additions & 5 deletions tableauserverclient/models/tableau_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ def credentials(self):
return {"name": self.username, "password": self.password}

def __repr__(self):
return "<Credentials username={} password={}>".format(self.username, "<redacted>")
if self.user_id_to_impersonate:
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
else:
uid = ""
return f"<Credentials username={self.username} password=redacted (site={self.site_id}{uid})>"

@property
def site(self):
Expand All @@ -56,6 +60,7 @@ def site(self, value):
self.site_id = value


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

def __repr__(self):
return "<PersonalAccessToken name={} token={}>(site={})".format(
self.token_name, self.personal_access_token[:2] + "...", self.site_id
if self.user_id_to_impersonate:
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
else:
uid = ""
return (
f"<PersonalAccessToken name={self.token_name} token={self.personal_access_token[:2]}..."
f"(site={self.site_id}{uid} >"
)


# A standard JWT generated specifically for Tableau
class JWTAuth(Credentials):
def __init__(self, jwt=None, site_id=None, user_id_to_impersonate=None):
def __init__(self, jwt: str, site_id=None, user_id_to_impersonate=None):
if jwt is None:
raise TabError("Must provide a JWT token when using JWT authentication")
super().__init__(site_id, user_id_to_impersonate)
Expand All @@ -93,4 +104,4 @@ def __repr__(self):
uid = f", user_id_to_impersonate=f{self.user_id_to_impersonate}"
else:
uid = ""
return f"<{self.__class__.__qualname__}(jwt={self.jwt[:5]}..., site_id={self.site_id}{uid})>"
return f"<{self.__class__.__qualname__} jwt={self.jwt[:5]}... (site={self.site_id}{uid})>"
5 changes: 4 additions & 1 deletion tableauserverclient/models/user_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,13 @@ def __init__(

return None

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

def __repr__(self):
return self.__str__() + " { " + ", ".join(" % s: % s" % item for item in vars(self).items()) + "}"

@property
def auth_setting(self) -> Optional[str]:
return self._auth_setting
Expand Down
Loading
Loading