From a48129960fea967d62a00cb70d1500aff08fbbd4 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 1 Feb 2021 22:18:52 -0600 Subject: [PATCH 01/69] Silence property decorator warnings Following notes from GVR on https://github.com/python/mypy/issues/1362 --- tableauserverclient/models/column_item.py | 2 +- .../models/connection_credentials.py | 4 ++-- tableauserverclient/models/data_alert_item.py | 6 +++--- tableauserverclient/models/database_item.py | 15 ++++++++------- tableauserverclient/models/datasource_item.py | 12 ++++++------ tableauserverclient/models/flow_item.py | 2 +- tableauserverclient/models/group_item.py | 6 +++--- tableauserverclient/models/interval_item.py | 10 +++++----- tableauserverclient/models/project_item.py | 11 ++++++----- tableauserverclient/models/schedule_item.py | 10 +++++----- tableauserverclient/models/site_item.py | 16 ++++++++-------- tableauserverclient/models/table_item.py | 4 ++-- tableauserverclient/models/user_item.py | 6 +++--- tableauserverclient/models/workbook_item.py | 6 +++--- tableauserverclient/server/request_options.py | 6 +++--- 15 files changed, 59 insertions(+), 57 deletions(-) diff --git a/tableauserverclient/models/column_item.py b/tableauserverclient/models/column_item.py index 9bf198220..f77b63fbf 100644 --- a/tableauserverclient/models/column_item.py +++ b/tableauserverclient/models/column_item.py @@ -17,7 +17,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index c883a515a..80a7e4b37 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -19,7 +19,7 @@ def __init__(self, name, password, embed=True, oauth=False): def embed(self): return self._embed - @embed.setter + @embed.setter # type: ignore @property_is_boolean def embed(self, value): self._embed = value @@ -28,7 +28,7 @@ def embed(self, value): def oauth(self): return self._oauth - @oauth.setter + @oauth.setter # type: ignore @property_is_boolean def oauth(self, value): self._oauth = value diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index 559050b4b..306d032ae 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -43,7 +43,7 @@ def id(self): def subject(self): return self._subject - @subject.setter + @subject.setter # type: ignore @property_not_empty def subject(self, value): self._subject = value @@ -52,7 +52,7 @@ def subject(self, value): def frequency(self): return self._frequency - @frequency.setter + @frequency.setter # type: ignore @property_is_enum(Frequency) def frequency(self, value): self._frequency = value @@ -61,7 +61,7 @@ def frequency(self, value): def public(self): return self._public - @public.setter + @public.setter # type: ignore @property_is_boolean def public(self, value): self._public = value diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index 5a7e74737..abd08f8bd 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -40,6 +40,11 @@ def __init__(self, name, description=None, content_permissions=None): def content_permissions(self): return self._content_permissions + @content_permissions.setter # type: ignore + @property_is_enum(ContentPermissions) + def content_permissions(self, value): + self._content_permissions = value + @property def permissions(self): if self._permissions is None: @@ -54,10 +59,6 @@ def default_table_permissions(self): raise UnpopulatedPropertyError(error) return self._default_table_permissions() - @content_permissions.setter - @property_is_enum(ContentPermissions) - def content_permissions(self, value): - self._content_permissions = value @property def id(self): @@ -67,7 +68,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -76,7 +77,7 @@ def name(self, value): def description(self): return self._description - @description.setter + @description.setter # type: ignore def description(self, value): self._description = value @@ -88,7 +89,7 @@ def embedded(self): def certified(self): return self._certified - @certified.setter + @certified.setter # type: ignore @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index a50d5a412..2dcd5045e 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -40,7 +40,7 @@ def __init__(self, project_id, name=None): def ask_data_enablement(self): return self._ask_data_enablement - @ask_data_enablement.setter + @ask_data_enablement.setter # type: ignore @property_is_enum(AskDataEnablement) def ask_data_enablement(self, value): self._ask_data_enablement = value @@ -71,7 +71,7 @@ def created_at(self): def certified(self): return self._certified - @certified.setter + @certified.setter # type: ignore @property_not_nullable @property_is_boolean def certified(self, value): @@ -81,7 +81,7 @@ def certified(self, value): def certification_note(self): return self._certification_note - @certification_note.setter + @certification_note.setter # type: ignore def certification_note(self, value): self._certification_note = value @@ -89,7 +89,7 @@ def certification_note(self, value): def encrypt_extracts(self): return self._encrypt_extracts - @encrypt_extracts.setter + @encrypt_extracts.setter # type: ignore @property_is_boolean def encrypt_extracts(self, value): self._encrypt_extracts = value @@ -106,7 +106,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter + @project_id.setter # type: ignore @property_not_nullable def project_id(self, value): self._project_id = value @@ -127,7 +127,7 @@ def updated_at(self): def use_remote_query_agent(self): return self._use_remote_query_agent - @use_remote_query_agent.setter + @use_remote_query_agent.setter # type: ignore @property_is_boolean def use_remote_query_agent(self, value): self._use_remote_query_agent = value diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index c978d8175..14eef5000 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -53,7 +53,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter + @project_id.setter # type: ignore @property_not_nullable def project_id(self, value): self._project_id = value diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index af9465dfb..83aa39895 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -37,7 +37,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -46,7 +46,7 @@ def name(self, value): def license_mode(self): return self._license_mode - @license_mode.setter + @license_mode.setter # type: ignore @property_is_enum(LicenseMode) def license_mode(self, value): self._license_mode = value @@ -55,7 +55,7 @@ def license_mode(self, value): def minimum_site_role(self): return self._minimum_site_role - @minimum_site_role.setter + @minimum_site_role.setter # type: ignore @property_is_enum(UserItem.Roles) def minimum_site_role(self, value): self._minimum_site_role = value diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index cbc148e88..c876ce339 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -40,7 +40,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -50,7 +50,7 @@ def start_time(self, value): def end_time(self): return self._end_time - @end_time.setter + @end_time.setter # type: ignore @property_is_valid_time @property_not_nullable def end_time(self, value): @@ -95,7 +95,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -115,7 +115,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -149,7 +149,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index 4cfbcb4e9..665de2c6a 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -28,6 +28,11 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N def content_permissions(self): return self._content_permissions + @content_permissions.setter # type: ignore + @property_is_enum(ContentPermissions) + def content_permissions(self, value): + self._content_permissions = value + @property def permissions(self): if self._permissions is None: @@ -56,10 +61,6 @@ def default_flow_permissions(self): raise UnpopulatedPropertyError(error) return self._default_flow_permissions() - @content_permissions.setter - @property_is_enum(ContentPermissions) - def content_permissions(self, value): - self._content_permissions = value @property def id(self): @@ -69,7 +70,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index c93ffe922..dd6527978 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -49,7 +49,7 @@ def end_schedule_at(self): def execution_order(self): return self._execution_order - @execution_order.setter + @execution_order.setter # type: ignore @property_is_enum(ExecutionOrder) def execution_order(self, value): self._execution_order = value @@ -62,7 +62,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_nullable def name(self, value): self._name = value @@ -75,7 +75,7 @@ def next_run_at(self): def priority(self): return self._priority - @priority.setter + @priority.setter # type: ignore @property_is_int(range=(1, 100)) def priority(self, value): self._priority = value @@ -84,7 +84,7 @@ def priority(self, value): def schedule_type(self): return self._schedule_type - @schedule_type.setter + @schedule_type.setter # type: ignore @property_is_enum(Type) @property_not_nullable def schedule_type(self, value): @@ -94,7 +94,7 @@ def schedule_type(self, value): def state(self): return self._state - @state.setter + @state.setter # type: ignore @property_is_enum(State) def state(self, value): self._state = value diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index 1ba854e72..2506fecac 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -41,7 +41,7 @@ def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_ def admin_mode(self): return self._admin_mode - @admin_mode.setter + @admin_mode.setter # type: ignore @property_is_enum(AdminMode) def admin_mode(self, value): self._admin_mode = value @@ -50,7 +50,7 @@ def admin_mode(self, value): def content_url(self): return self._content_url - @content_url.setter + @content_url.setter # type: ignore @property_not_nullable @property_matches(VALID_CONTENT_URL_RE, "content_url can contain only letters, numbers, dashes, and underscores") def content_url(self, value): @@ -60,7 +60,7 @@ def content_url(self, value): def disable_subscriptions(self): return self._disable_subscriptions - @disable_subscriptions.setter + @disable_subscriptions.setter # type: ignore @property_is_boolean def disable_subscriptions(self, value): self._disable_subscriptions = value @@ -73,7 +73,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -86,7 +86,7 @@ def num_users(self): def revision_history_enabled(self): return self._revision_history_enabled - @revision_history_enabled.setter + @revision_history_enabled.setter # type: ignore @property_is_boolean def revision_history_enabled(self, value): self._revision_history_enabled = value @@ -95,7 +95,7 @@ def revision_history_enabled(self, value): def revision_limit(self): return self._revision_limit - @revision_limit.setter + @revision_limit.setter # type: ignore @property_is_int((2, 10000), allowed=[-1]) def revision_limit(self, value): self._revision_limit = value @@ -104,7 +104,7 @@ def revision_limit(self, value): def state(self): return self._state - @state.setter + @state.setter # type: ignore @property_is_enum(State) def state(self, value): self._state = value @@ -121,7 +121,7 @@ def storage(self): def subscribe_others_enabled(self): return self._subscribe_others_enabled - @subscribe_others_enabled.setter + @subscribe_others_enabled.setter # type: ignore @property_is_boolean def subscribe_others_enabled(self, value): self._subscribe_others_enabled = value diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index 2f00ef2b7..43300c2b4 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -33,7 +33,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -50,7 +50,7 @@ def description(self, value): def certified(self): return self._certified - @certified.setter + @certified.setter # type: ignore @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index 9be38210f..a46277cd0 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -53,7 +53,7 @@ def __init__(self, name=None, site_role=None, auth_setting=None): def auth_setting(self): return self._auth_setting - @auth_setting.setter + @auth_setting.setter # type: ignore @property_is_enum(Auth) def auth_setting(self, value): self._auth_setting = value @@ -78,7 +78,7 @@ def last_login(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -87,7 +87,7 @@ def name(self, value): def site_role(self): return self._site_role - @site_role.setter + @site_role.setter # type: ignore @property_not_nullable @property_is_enum(Roles) def site_role(self, value): diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 3a3ddcdf9..a120b9d25 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -86,7 +86,7 @@ def preview_image(self): def project_id(self): return self._project_id - @project_id.setter + @project_id.setter # type: ignore @property_not_nullable def project_id(self, value): self._project_id = value @@ -99,7 +99,7 @@ def project_name(self): def show_tabs(self): return self._show_tabs - @show_tabs.setter + @show_tabs.setter # type: ignore @property_is_boolean def show_tabs(self, value): self._show_tabs = value @@ -133,7 +133,7 @@ def views(self): def data_acceleration_config(self): return self._data_acceleration_config - @data_acceleration_config.setter + @data_acceleration_config.setter # type: ignore @property_is_data_acceleration_config def data_acceleration_config(self, value): self._data_acceleration_config = value diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 22d0a4ef0..163c46b3e 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -124,7 +124,7 @@ def __init__(self, maxage=-1): def max_age(self): return self._max_age - @max_age.setter + @max_age.setter # type: ignore @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -152,7 +152,7 @@ def __init__(self, imageresolution=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter + @max_age.setter # type: ignore @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -197,7 +197,7 @@ def __init__(self, page_type=None, orientation=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter + @max_age.setter # type: ignore @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value From 5209c1f14e70391ffe5fb6f384fdb3d215e7792e Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 1 Feb 2021 22:32:16 -0600 Subject: [PATCH 02/69] Remove refs to distutils2 Development of distutils has stopped. Removing refs to it. https://pypi.org/project/Distutils2/ --- tableauserverclient/server/endpoint/endpoint.py | 5 +---- tableauserverclient/server/server.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index dc504242a..4b3c092bb 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -4,10 +4,7 @@ from ..query import QuerySet import logging -try: - from distutils2.version import NormalizedVersion as Version -except ImportError: - from distutils.version import LooseVersion as Version +from distutils.version import LooseVersion as Version logger = logging.getLogger('tableau.endpoint') diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index 6aff0c126..9d53fd34c 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -9,10 +9,7 @@ import requests -try: - from distutils2.version import NormalizedVersion as Version -except ImportError: - from distutils.version import LooseVersion as Version +from distutils.version import LooseVersion as Version _PRODUCT_TO_REST_VERSION = { '10.0': '2.3', From 2028935d9009e06320bc4240ee8867392f7cc238 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 1 Feb 2021 22:35:00 -0600 Subject: [PATCH 03/69] Ignore problem variable in _version.py --- tableauserverclient/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 9f576606a..5d26b4a82 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -52,7 +52,7 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} +LONG_VERSION_PY = {} # type: ignore HANDLERS = {} From ddd84f6f961971c551451bbd224f71ad20e635ea Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 2 Feb 2021 22:09:39 -0600 Subject: [PATCH 04/69] Linting changes --- tableauserverclient/_version.py | 2 +- tableauserverclient/models/column_item.py | 2 +- .../models/connection_credentials.py | 4 ++-- tableauserverclient/models/data_alert_item.py | 6 +++--- tableauserverclient/models/database_item.py | 11 +++++------ tableauserverclient/models/datasource_item.py | 12 ++++++------ tableauserverclient/models/flow_item.py | 2 +- tableauserverclient/models/group_item.py | 6 +++--- tableauserverclient/models/interval_item.py | 10 +++++----- tableauserverclient/models/project_item.py | 5 ++--- tableauserverclient/models/schedule_item.py | 10 +++++----- tableauserverclient/models/site_item.py | 16 ++++++++-------- tableauserverclient/models/table_item.py | 4 ++-- tableauserverclient/models/user_item.py | 6 +++--- tableauserverclient/models/workbook_item.py | 6 +++--- tableauserverclient/server/request_options.py | 6 +++--- 16 files changed, 53 insertions(+), 55 deletions(-) diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 5d26b4a82..2afee59f0 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -52,7 +52,7 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} # type: ignore +LONG_VERSION_PY = {} # type: ignore HANDLERS = {} diff --git a/tableauserverclient/models/column_item.py b/tableauserverclient/models/column_item.py index f77b63fbf..d5edc8245 100644 --- a/tableauserverclient/models/column_item.py +++ b/tableauserverclient/models/column_item.py @@ -17,7 +17,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index 80a7e4b37..47787a6df 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -19,7 +19,7 @@ def __init__(self, name, password, embed=True, oauth=False): def embed(self): return self._embed - @embed.setter # type: ignore + @embed.setter # type: ignore @property_is_boolean def embed(self, value): self._embed = value @@ -28,7 +28,7 @@ def embed(self, value): def oauth(self): return self._oauth - @oauth.setter # type: ignore + @oauth.setter # type: ignore @property_is_boolean def oauth(self, value): self._oauth = value diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index 306d032ae..c1ba48d6d 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -43,7 +43,7 @@ def id(self): def subject(self): return self._subject - @subject.setter # type: ignore + @subject.setter # type: ignore @property_not_empty def subject(self, value): self._subject = value @@ -52,7 +52,7 @@ def subject(self, value): def frequency(self): return self._frequency - @frequency.setter # type: ignore + @frequency.setter # type: ignore @property_is_enum(Frequency) def frequency(self, value): self._frequency = value @@ -61,7 +61,7 @@ def frequency(self, value): def public(self): return self._public - @public.setter # type: ignore + @public.setter # type: ignore @property_is_boolean def public(self, value): self._public = value diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index abd08f8bd..9467406f3 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -40,11 +40,11 @@ def __init__(self, name, description=None, content_permissions=None): def content_permissions(self): return self._content_permissions - @content_permissions.setter # type: ignore + @content_permissions.setter # type: ignore @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value - + @property def permissions(self): if self._permissions is None: @@ -59,7 +59,6 @@ def default_table_permissions(self): raise UnpopulatedPropertyError(error) return self._default_table_permissions() - @property def id(self): return self._id @@ -68,7 +67,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -77,7 +76,7 @@ def name(self, value): def description(self): return self._description - @description.setter # type: ignore + @description.setter # type: ignore def description(self, value): self._description = value @@ -89,7 +88,7 @@ def embedded(self): def certified(self): return self._certified - @certified.setter # type: ignore + @certified.setter # type: ignore @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 2dcd5045e..3e4252474 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -40,7 +40,7 @@ def __init__(self, project_id, name=None): def ask_data_enablement(self): return self._ask_data_enablement - @ask_data_enablement.setter # type: ignore + @ask_data_enablement.setter # type: ignore @property_is_enum(AskDataEnablement) def ask_data_enablement(self, value): self._ask_data_enablement = value @@ -71,7 +71,7 @@ def created_at(self): def certified(self): return self._certified - @certified.setter # type: ignore + @certified.setter # type: ignore @property_not_nullable @property_is_boolean def certified(self, value): @@ -81,7 +81,7 @@ def certified(self, value): def certification_note(self): return self._certification_note - @certification_note.setter # type: ignore + @certification_note.setter # type: ignore def certification_note(self, value): self._certification_note = value @@ -89,7 +89,7 @@ def certification_note(self, value): def encrypt_extracts(self): return self._encrypt_extracts - @encrypt_extracts.setter # type: ignore + @encrypt_extracts.setter # type: ignore @property_is_boolean def encrypt_extracts(self, value): self._encrypt_extracts = value @@ -106,7 +106,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter # type: ignore + @project_id.setter # type: ignore @property_not_nullable def project_id(self, value): self._project_id = value @@ -127,7 +127,7 @@ def updated_at(self): def use_remote_query_agent(self): return self._use_remote_query_agent - @use_remote_query_agent.setter # type: ignore + @use_remote_query_agent.setter # type: ignore @property_is_boolean def use_remote_query_agent(self, value): self._use_remote_query_agent = value diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index 14eef5000..8660b32c4 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -53,7 +53,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter # type: ignore + @project_id.setter # type: ignore @property_not_nullable def project_id(self, value): self._project_id = value diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index 83aa39895..5bab2b5db 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -37,7 +37,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -46,7 +46,7 @@ def name(self, value): def license_mode(self): return self._license_mode - @license_mode.setter # type: ignore + @license_mode.setter # type: ignore @property_is_enum(LicenseMode) def license_mode(self, value): self._license_mode = value @@ -55,7 +55,7 @@ def license_mode(self, value): def minimum_site_role(self): return self._minimum_site_role - @minimum_site_role.setter # type: ignore + @minimum_site_role.setter # type: ignore @property_is_enum(UserItem.Roles) def minimum_site_role(self, value): self._minimum_site_role = value diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index c876ce339..e5e841a46 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -40,7 +40,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -50,7 +50,7 @@ def start_time(self, value): def end_time(self): return self._end_time - @end_time.setter # type: ignore + @end_time.setter # type: ignore @property_is_valid_time @property_not_nullable def end_time(self, value): @@ -95,7 +95,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -115,7 +115,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -149,7 +149,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter # type: ignore @property_is_valid_time @property_not_nullable def start_time(self, value): diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index 665de2c6a..ddfc20766 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -28,7 +28,7 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N def content_permissions(self): return self._content_permissions - @content_permissions.setter # type: ignore + @content_permissions.setter # type: ignore @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -61,7 +61,6 @@ def default_flow_permissions(self): raise UnpopulatedPropertyError(error) return self._default_flow_permissions() - @property def id(self): return self._id @@ -70,7 +69,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index dd6527978..74b9ef199 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -49,7 +49,7 @@ def end_schedule_at(self): def execution_order(self): return self._execution_order - @execution_order.setter # type: ignore + @execution_order.setter # type: ignore @property_is_enum(ExecutionOrder) def execution_order(self, value): self._execution_order = value @@ -62,7 +62,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_nullable def name(self, value): self._name = value @@ -75,7 +75,7 @@ def next_run_at(self): def priority(self): return self._priority - @priority.setter # type: ignore + @priority.setter # type: ignore @property_is_int(range=(1, 100)) def priority(self, value): self._priority = value @@ -84,7 +84,7 @@ def priority(self, value): def schedule_type(self): return self._schedule_type - @schedule_type.setter # type: ignore + @schedule_type.setter # type: ignore @property_is_enum(Type) @property_not_nullable def schedule_type(self, value): @@ -94,7 +94,7 @@ def schedule_type(self, value): def state(self): return self._state - @state.setter # type: ignore + @state.setter # type: ignore @property_is_enum(State) def state(self, value): self._state = value diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index 2506fecac..081f47bd5 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -41,7 +41,7 @@ def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_ def admin_mode(self): return self._admin_mode - @admin_mode.setter # type: ignore + @admin_mode.setter # type: ignore @property_is_enum(AdminMode) def admin_mode(self, value): self._admin_mode = value @@ -50,7 +50,7 @@ def admin_mode(self, value): def content_url(self): return self._content_url - @content_url.setter # type: ignore + @content_url.setter # type: ignore @property_not_nullable @property_matches(VALID_CONTENT_URL_RE, "content_url can contain only letters, numbers, dashes, and underscores") def content_url(self, value): @@ -60,7 +60,7 @@ def content_url(self, value): def disable_subscriptions(self): return self._disable_subscriptions - @disable_subscriptions.setter # type: ignore + @disable_subscriptions.setter # type: ignore @property_is_boolean def disable_subscriptions(self, value): self._disable_subscriptions = value @@ -73,7 +73,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -86,7 +86,7 @@ def num_users(self): def revision_history_enabled(self): return self._revision_history_enabled - @revision_history_enabled.setter # type: ignore + @revision_history_enabled.setter # type: ignore @property_is_boolean def revision_history_enabled(self, value): self._revision_history_enabled = value @@ -95,7 +95,7 @@ def revision_history_enabled(self, value): def revision_limit(self): return self._revision_limit - @revision_limit.setter # type: ignore + @revision_limit.setter # type: ignore @property_is_int((2, 10000), allowed=[-1]) def revision_limit(self, value): self._revision_limit = value @@ -104,7 +104,7 @@ def revision_limit(self, value): def state(self): return self._state - @state.setter # type: ignore + @state.setter # type: ignore @property_is_enum(State) def state(self, value): self._state = value @@ -121,7 +121,7 @@ def storage(self): def subscribe_others_enabled(self): return self._subscribe_others_enabled - @subscribe_others_enabled.setter # type: ignore + @subscribe_others_enabled.setter # type: ignore @property_is_boolean def subscribe_others_enabled(self, value): self._subscribe_others_enabled = value diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index 43300c2b4..eb79483ce 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -33,7 +33,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -50,7 +50,7 @@ def description(self, value): def certified(self): return self._certified - @certified.setter # type: ignore + @certified.setter # type: ignore @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index a46277cd0..6a9257f26 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -53,7 +53,7 @@ def __init__(self, name=None, site_role=None, auth_setting=None): def auth_setting(self): return self._auth_setting - @auth_setting.setter # type: ignore + @auth_setting.setter # type: ignore @property_is_enum(Auth) def auth_setting(self, value): self._auth_setting = value @@ -78,7 +78,7 @@ def last_login(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter # type: ignore @property_not_empty def name(self, value): self._name = value @@ -87,7 +87,7 @@ def name(self, value): def site_role(self): return self._site_role - @site_role.setter # type: ignore + @site_role.setter # type: ignore @property_not_nullable @property_is_enum(Roles) def site_role(self, value): diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index a120b9d25..976f3d801 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -86,7 +86,7 @@ def preview_image(self): def project_id(self): return self._project_id - @project_id.setter # type: ignore + @project_id.setter # type: ignore @property_not_nullable def project_id(self, value): self._project_id = value @@ -99,7 +99,7 @@ def project_name(self): def show_tabs(self): return self._show_tabs - @show_tabs.setter # type: ignore + @show_tabs.setter # type: ignore @property_is_boolean def show_tabs(self, value): self._show_tabs = value @@ -133,7 +133,7 @@ def views(self): def data_acceleration_config(self): return self._data_acceleration_config - @data_acceleration_config.setter # type: ignore + @data_acceleration_config.setter # type: ignore @property_is_data_acceleration_config def data_acceleration_config(self, value): self._data_acceleration_config = value diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 163c46b3e..74bb7987a 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -124,7 +124,7 @@ def __init__(self, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore + @max_age.setter # type: ignore @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -152,7 +152,7 @@ def __init__(self, imageresolution=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore + @max_age.setter # type: ignore @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -197,7 +197,7 @@ def __init__(self, page_type=None, orientation=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore + @max_age.setter # type: ignore @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value From 3a8faeb6250a761c96100af2d70a543525609c22 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 2 Feb 2021 22:09:51 -0600 Subject: [PATCH 05/69] Mypy checking in pipeline --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9085632f4..ba5095b74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,3 +15,4 @@ script: # Tests - python setup.py test - pycodestyle tableauserverclient test samples + - mypy tableauserverclient From 43c8dd7d6ff709346e6fe8d1993b053e06ab3c77 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 2 Feb 2021 22:19:28 -0600 Subject: [PATCH 06/69] Mypy ignore imports --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 6136b814a..1debabe18 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,3 +26,6 @@ smoke=pytest [tool:pytest] testpaths = test smoke addopts = --junitxml=./test.junit.xml + +[mypy] +ignore_missing_imports = True From db1de416aadecae91325302c210932957b345464 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 08:20:47 -0600 Subject: [PATCH 07/69] Install mypy in pipeline --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ba5095b74..6baed676d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: install: - "pip install -e ." - "pip install pycodestyle" + - "pip install mypy" # command to run tests script: # Tests From ac9b06b6e6a93a17839c3bbed3cfde609c6b15a6 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 08:32:19 -0600 Subject: [PATCH 08/69] Fix mypy errors in datasource_item --- tableauserverclient/models/datasource_item.py | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 3e4252474..62c268c21 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -2,8 +2,12 @@ from .exceptions import UnpopulatedPropertyError from .property_decorators import property_not_nullable, property_is_boolean, property_is_enum from .tag_item import TagItem -from ..datetime_helpers import parse_datetime +from ..datetime_helpers import parse_datetime, datetime import copy +from .permissions_item import PermissionsRule +from .connection_item import ConnectionItem + +from typing import Dict, List, Mapping, Optional, Sequence, Set, Tuple, TypeVar class DatasourceItem(object): @@ -12,7 +16,7 @@ class AskDataEnablement: Disabled = 'Disabled' SiteDefault = 'SiteDefault' - def __init__(self, project_id, name=None): + def __init__(self, project_id: str, name: str = None) -> None: self._ask_data_enablement = None self._certified = None self._certification_note = None @@ -23,7 +27,7 @@ def __init__(self, project_id, name=None): self._encrypt_extracts = None self._has_extracts = None self._id = None - self._initial_tags = set() + self._initial_tags: Set = set() self._project_name = None self._updated_at = None self._use_remote_query_agent = None @@ -31,58 +35,60 @@ def __init__(self, project_id, name=None): self.description = None self.name = name self.owner_id = None - self.project_id = project_id - self.tags = set() + self.project_id = project_id # type: ignore + self.tags: Set = set() self._permissions = None + return + @property - def ask_data_enablement(self): + def ask_data_enablement(self) -> Optional['DatasourceItem.AskDataEnablement']: return self._ask_data_enablement @ask_data_enablement.setter # type: ignore @property_is_enum(AskDataEnablement) - def ask_data_enablement(self, value): + def ask_data_enablement(self, value: Optional['DatasourceItem.AskDataEnablement']): self._ask_data_enablement = value @property - def connections(self): + def connections(self) -> Optional[List['ConnectionItem']]: if self._connections is None: error = 'Datasource item must be populated with connections first.' raise UnpopulatedPropertyError(error) return self._connections() @property - def permissions(self): + def permissions(self) -> Optional[List['PermissionsRule']]: if self._permissions is None: error = "Project item must be populated with permissions first." raise UnpopulatedPropertyError(error) return self._permissions() @property - def content_url(self): + def content_url(self) -> Optional[str]: return self._content_url @property - def created_at(self): + def created_at(self) -> Optional['datetime.datetime']: return self._created_at @property - def certified(self): + def certified(self) -> Optional[bool]: return self._certified @certified.setter # type: ignore @property_not_nullable @property_is_boolean - def certified(self, value): + def certified(self, value: Optional[bool]): self._certified = value @property - def certification_note(self): + def certification_note(self) -> Optional[str]: return self._certification_note @certification_note.setter # type: ignore - def certification_note(self, value): + def certification_note(self, value: Optional[str]): self._certification_note = value @property @@ -91,49 +97,49 @@ def encrypt_extracts(self): @encrypt_extracts.setter # type: ignore @property_is_boolean - def encrypt_extracts(self, value): + def encrypt_extracts(self, value: Optional[bool]): self._encrypt_extracts = value @property - def has_extracts(self): + def has_extracts(self) -> Optional[bool]: return self._has_extracts @property - def id(self): + def id(self) -> Optional[str]: return self._id @property - def project_id(self): + def project_id(self) -> str: return self._project_id @project_id.setter # type: ignore @property_not_nullable - def project_id(self, value): + def project_id(self, value: str): self._project_id = value @property - def project_name(self): + def project_name(self) -> Optional[str]: return self._project_name @property - def datasource_type(self): + def datasource_type(self) -> Optional[str]: return self._datasource_type @property - def updated_at(self): + def updated_at(self) -> Optional['datetime.datetime']: return self._updated_at @property - def use_remote_query_agent(self): + def use_remote_query_agent(self) -> Optional[bool]: return self._use_remote_query_agent @use_remote_query_agent.setter # type: ignore @property_is_boolean - def use_remote_query_agent(self, value): + def use_remote_query_agent(self, value: bool): self._use_remote_query_agent = value @property - def webpage_url(self): + def webpage_url(self) -> Optional[str]: return self._webpage_url def _set_connections(self, connections): @@ -195,7 +201,7 @@ def _set_values(self, ask_data_enablement, certified, certification_note, conten self._webpage_url = webpage_url @classmethod - def from_response(cls, resp, ns): + def from_response(cls, resp: str, ns: Dict) -> List['DatasourceItem']: all_datasource_items = list() parsed_response = ET.fromstring(resp) all_datasource_xml = parsed_response.findall('.//t:datasource', namespaces=ns) @@ -213,7 +219,7 @@ def from_response(cls, resp, ns): return all_datasource_items @staticmethod - def _parse_element(datasource_xml, ns): + def _parse_element(datasource_xml: ET.Element, ns: Dict) -> Tuple: certification_note = datasource_xml.get('certificationNote', None) certified = str(datasource_xml.get('isCertified', None)).lower() == 'true' content_url = datasource_xml.get('contentUrl', None) From 9a17beb3b6033eed6d48eb6844ca99accf912a5e Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 08:44:35 -0600 Subject: [PATCH 09/69] Add pre-commit config --- .pre-commit-config.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..803ebce08 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,31 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + +- repo: local + hooks: + - id: unittest + name: unittest + entry: python -m unittest discover + language: python + 'types': [python] + pass_filenames: false + stages: [commit] + - id: pycodestyle + name: pycodestyle + entry: pycodestyle tableauserverclient samples test + language: python + 'types': [python] + pass_filenames: false + stages: [commit] + - id: mypy + name: mypy + entry: mypy tableauserverclient samples + language: python + 'types': [python] + pass_filenames: false + stages: [commit] \ No newline at end of file From 9be8bd72fb2adfc1b7d002f7a0f67ad4f3211d9e Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 08:55:52 -0600 Subject: [PATCH 10/69] Add pre-commit config --- .pre-commit-config.yaml | 45 ++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 803ebce08..ff56cdd79 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,28 +4,35 @@ repos: hooks: - id: check-yaml - id: end-of-file-fixer + files: '.*\.py' - id: trailing-whitespace - + files: '.*\.py' + - repo: local hooks: - id: unittest - name: unittest - entry: python -m unittest discover - language: python - 'types': [python] - pass_filenames: false - stages: [commit] + name: unittest + entry: python -m unittest discover + language: python + 'types': [python] + pass_filenames: false + additional_dependencies: + - requests + - mock + - requests-mock + - pytest + stages: [commit] - id: pycodestyle - name: pycodestyle - entry: pycodestyle tableauserverclient samples test - language: python - 'types': [python] - pass_filenames: false - stages: [commit] + name: pycodestyle + entry: pycodestyle tableauserverclient samples test + language: python + 'types': [python] + pass_filenames: false + stages: [commit] - id: mypy - name: mypy - entry: mypy tableauserverclient samples - language: python - 'types': [python] - pass_filenames: false - stages: [commit] \ No newline at end of file + name: mypy + entry: mypy tableauserverclient samples + language: python + 'types': [python] + pass_filenames: false + stages: [commit] From 13bde24a87b28a344357d7b36a0acf75174ec162 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 09:30:19 -0600 Subject: [PATCH 11/69] Type hint on datasources_endpoint --- .../server/endpoint/datasources_endpoint.py | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 0d1e497b4..be06bd67f 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -3,10 +3,11 @@ from .permissions_endpoint import _PermissionsEndpoint from .fileuploads_endpoint import Fileuploads from .resource_tagger import _ResourceTagger -from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem +from .. import RequestFactory, DatasourceItem, PaginationItem, ConnectionItem, RequestOptions from ..query import QuerySet from ...filesys_helpers import to_filename, make_download_path, get_file_type, get_file_object_size from ...models.job_item import JobItem +from ...models import ConnectionCredentials import os import logging @@ -14,6 +15,9 @@ import cgi from contextlib import closing +from pathlib import Path +from typing import List, Dict, Sequence, Mapping, Optional, Tuple, Union, TYPE_CHECKING, Any, BinaryIO + # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -21,20 +25,25 @@ logger = logging.getLogger('tableau.endpoint.datasources') +if TYPE_CHECKING: + from ..server import Server + from ...models import PermissionsRule class Datasources(QuerysetEndpoint): - def __init__(self, parent_srv): + def __init__(self, parent_srv: 'Server') -> None: super(Datasources, self).__init__(parent_srv) self._resource_tagger = _ResourceTagger(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) + return + @property - def baseurl(self): + def baseurl(self) -> str: return "{0}/sites/{1}/datasources".format(self.parent_srv.baseurl, self.parent_srv.site_id) # Get all datasources @api(version="2.0") - def get(self, req_options=None): + def get(self, req_options: RequestOptions = None) -> Tuple[List[DatasourceItem], PaginationItem]: logger.info('Querying all datasources on site') url = self.baseurl server_response = self.get_request(url, req_options) @@ -44,7 +53,7 @@ def get(self, req_options=None): # Get 1 datasource by id @api(version="2.0") - def get_by_id(self, datasource_id): + def get_by_id(self, datasource_id: str) -> DatasourceItem: if not datasource_id: error = "Datasource ID undefined." raise ValueError(error) @@ -55,7 +64,7 @@ def get_by_id(self, datasource_id): # Populate datasource item's connections @api(version="2.0") - def populate_connections(self, datasource_item): + def populate_connections(self, datasource_item: DatasourceItem) -> None: if not datasource_item.id: error = 'Datasource item missing ID. Datasource must be retrieved from server first.' raise MissingRequiredFieldError(error) @@ -74,7 +83,7 @@ def _get_datasource_connections(self, datasource_item, req_options=None): # Delete 1 datasource by id @api(version="2.0") - def delete(self, datasource_id): + def delete(self, datasource_id: str) -> None: if not datasource_id: error = "Datasource ID undefined." raise ValueError(error) @@ -86,7 +95,8 @@ def delete(self, datasource_id): @api(version="2.0") @parameter_added_in(no_extract='2.5') @parameter_added_in(include_extract='2.5') - def download(self, datasource_id, filepath=None, include_extract=True, no_extract=None): + def download(self, datasource_id: str, filepath: str = None, include_extract: bool = True, + no_extract: Optional[bool] = None) -> str: if not datasource_id: error = "Datasource ID undefined." raise ValueError(error) @@ -115,7 +125,7 @@ def download(self, datasource_id, filepath=None, include_extract=True, no_extrac # Update datasource @api(version="2.0") - def update(self, datasource_item): + def update(self, datasource_item: DatasourceItem) -> DatasourceItem: if not datasource_item.id: error = 'Datasource item missing ID. Datasource must be retrieved from server first.' raise MissingRequiredFieldError(error) @@ -133,7 +143,7 @@ def update(self, datasource_item): # Update datasource connections @api(version="2.3") - def update_connection(self, datasource_item, connection_item): + def update_connection(self, datasource_item: DatasourceItem, connection_item: ConnectionItem) -> ConnectionItem: url = "{0}/{1}/connections/{2}".format(self.baseurl, datasource_item.id, connection_item.id) update_req = RequestFactory.Connection.update_req(connection_item) @@ -145,7 +155,7 @@ def update_connection(self, datasource_item, connection_item): return connection @api(version="2.8") - def refresh(self, datasource_item): + def refresh(self, datasource_item: DatasourceItem) -> JobItem: id_ = getattr(datasource_item, 'id', datasource_item) url = "{0}/{1}/refresh".format(self.baseurl, id_) empty_req = RequestFactory.Empty.empty_req() @@ -154,7 +164,7 @@ def refresh(self, datasource_item): return new_job @api(version='3.5') - def create_extract(self, datasource_item, encrypt=False): + def create_extract(self, datasource_item: DatasourceItem, encrypt: bool = False) -> JobItem: id_ = getattr(datasource_item, 'id', datasource_item) url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt) empty_req = RequestFactory.Empty.empty_req() @@ -163,7 +173,7 @@ def create_extract(self, datasource_item, encrypt=False): return new_job @api(version='3.5') - def delete_extract(self, datasource_item): + def delete_extract(self, datasource_item: DatasourceItem) -> None: id_ = getattr(datasource_item, 'id', datasource_item) url = "{0}/{1}/deleteExtract".format(self.baseurl, id_) empty_req = RequestFactory.Empty.empty_req() @@ -173,17 +183,19 @@ 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: DatasourceItem, file: Union[str, Path, BinaryIO], mode: str, + connection_credentials: ConnectionCredentials = None, connections: Sequence[ConnectionItem] = None, + as_job: bool = False) -> Union[DatasourceItem, JobItem]: try: - if not os.path.isfile(file): + if not os.path.isfile(file): # type: ignore error = "File path does not lead to an existing file." raise IOError(error) - filename = os.path.basename(file) + filename = os.path.basename(file) # type: ignore file_extension = os.path.splitext(filename)[1][1:] - file_size = os.path.getsize(file) + file_size = os.path.getsize(file) # type: ignore # If name is not defined, grab the name from the file to publish if not datasource_item.name: @@ -234,10 +246,10 @@ def publish(self, datasource_item, file, mode, connection_credentials=None, conn logger.info('Publishing {0} to server'.format(filename)) try: - with open(file, 'rb') as f: + with open(file, 'rb') as f: # type: ignore file_contents = f.read() except TypeError: - file_contents = file.read() + file_contents = file.read() # type: ignore xml_request, content_type = RequestFactory.Datasource.publish_req(datasource_item, filename, @@ -267,7 +279,7 @@ def publish(self, datasource_item, file, mode, connection_credentials=None, conn return new_datasource @api(version='2.0') - def populate_permissions(self, item): + def populate_permissions(self, item: DatasourceItem) -> None: self._permissions.populate(item) @api(version='2.0') @@ -279,9 +291,9 @@ def update_permission(self, item, permission_item): self._permissions.update(item, permission_item) @api(version='2.0') - def update_permissions(self, item, permission_item): + def update_permissions(self, item: DatasourceItem, permission_item: 'PermissionsRule') -> None: self._permissions.update(item, permission_item) @api(version='2.0') - def delete_permission(self, item, capability_item): + def delete_permission(self, item: DatasourceItem, capability_item: 'PermissionsRule') -> None: self._permissions.delete(item, capability_item) From 5ea3e17bcd602ec758ced12d4db5cfadcf3b7093 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 15:04:44 -0600 Subject: [PATCH 12/69] update_permissions takes a list of rules --- tableauserverclient/server/endpoint/datasources_endpoint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index be06bd67f..a69d683d1 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -29,6 +29,7 @@ from ..server import Server from ...models import PermissionsRule + class Datasources(QuerysetEndpoint): def __init__(self, parent_srv: 'Server') -> None: super(Datasources, self).__init__(parent_srv) @@ -291,7 +292,7 @@ def update_permission(self, item, permission_item): self._permissions.update(item, permission_item) @api(version='2.0') - def update_permissions(self, item: DatasourceItem, permission_item: 'PermissionsRule') -> None: + def update_permissions(self, item: DatasourceItem, permission_item: List['PermissionsRule']) -> None: self._permissions.update(item, permission_item) @api(version='2.0') From d4315de169ad1290aa0fc4ed1dee1f802859ba7a Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 15:13:12 -0600 Subject: [PATCH 13/69] Run mypy on tests as well --- .pre-commit-config.yaml | 2 +- test/test_regression_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff56cdd79..7ce52e210 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: stages: [commit] - id: mypy name: mypy - entry: mypy tableauserverclient samples + entry: mypy tableauserverclient samples test language: python 'types': [python] pass_filenames: false diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index 281f3fbca..68826ccf6 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -3,7 +3,7 @@ try: from unittest import mock except ImportError: - import mock + import mock # type: ignore import tableauserverclient.server.request_factory as factory from tableauserverclient.server.endpoint import Endpoint From 0acf44ca24699afde5507426f5492afbb23536cb Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 3 Feb 2021 15:17:49 -0600 Subject: [PATCH 14/69] Add mypy test and samples to travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6baed676d..3a57d48aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,4 +16,4 @@ script: # Tests - python setup.py test - pycodestyle tableauserverclient test samples - - mypy tableauserverclient + - mypy tableauserverclient test samples From 45df4ffc167573b5b60eb117cfb3f2d73c8e4ffc Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 3 Feb 2021 22:03:47 -0600 Subject: [PATCH 15/69] Make type hint imports conditional --- tableauserverclient/models/datasource_item.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 62c268c21..0da3f45b2 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -2,12 +2,15 @@ from .exceptions import UnpopulatedPropertyError from .property_decorators import property_not_nullable, property_is_boolean, property_is_enum from .tag_item import TagItem -from ..datetime_helpers import parse_datetime, datetime +from ..datetime_helpers import parse_datetime import copy -from .permissions_item import PermissionsRule -from .connection_item import ConnectionItem -from typing import Dict, List, Mapping, Optional, Sequence, Set, Tuple, TypeVar +from typing import Dict, List, Mapping, Optional, Sequence, Set, Tuple, TypeVar, TYPE_CHECKING + +if TYPE_CHECKING: + from .permissions_item import PermissionsRule + from .connection_item import ConnectionItem + import datetime class DatasourceItem(object): From bac281125ad9c91e4de739f2990912a391ca43b9 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Thu, 4 Feb 2021 07:39:48 -0600 Subject: [PATCH 16/69] Annotate workbookitem init --- tableauserverclient/models/workbook_item.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 976f3d801..6b9d3066b 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -7,15 +7,20 @@ from ..datetime_helpers import parse_datetime import copy +from typing import Dict, List, Mapping, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + ... + class WorkbookItem(object): - def __init__(self, project_id, name=None, show_tabs=False): + def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) -> None: self._connections = None self._content_url = None self._webpage_url = None self._created_at = None self._id = None - self._initial_tags = set() + self._initial_tags: set = set() self._pdf = None self._preview_image = None self._project_name = None @@ -25,15 +30,17 @@ def __init__(self, project_id, name=None, show_tabs=False): self.name = name self._description = None self.owner_id = None - self.project_id = project_id - self.show_tabs = show_tabs - self.tags = set() - self.data_acceleration_config = {'acceleration_enabled': None, + self.project_id = project_id # type: ignore + self.show_tabs = show_tabs # type: ignore + self.tags: set = set() + self.data_acceleration_config = {'acceleration_enabled': None, # type: ignore 'accelerate_now': None, 'last_updated_at': None, 'acceleration_status': None} self._permissions = None + return + @property def connections(self): if self._connections is None: From e19b28d7c7a416630b7090fc7931c671b0503b99 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Thu, 4 Feb 2021 07:50:34 -0600 Subject: [PATCH 17/69] Continue typing workbookitem --- tableauserverclient/models/workbook_item.py | 38 +++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 6b9d3066b..9df4af344 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -7,10 +7,12 @@ from ..datetime_helpers import parse_datetime import copy -from typing import Dict, List, Mapping, Optional, TYPE_CHECKING +from typing import Dict, List, Mapping, Optional, TYPE_CHECKING, Union if TYPE_CHECKING: - ... + from .connection_item import ConnectionItem + from .permissions_item import PermissionsRule + import datetime class WorkbookItem(object): @@ -42,37 +44,37 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - return @property - def connections(self): + def connections(self) -> List['ConnectionItem']: if self._connections is None: error = "Workbook item must be populated with connections first." raise UnpopulatedPropertyError(error) return self._connections() @property - def permissions(self): + def permissions(self) -> List['PermissionsRule']: if self._permissions is None: error = "Workbook item must be populated with permissions first." raise UnpopulatedPropertyError(error) return self._permissions() @property - def content_url(self): + def content_url(self) -> Optional[str]: return self._content_url @property - def webpage_url(self): + def webpage_url(self) -> Optional[str]: return self._webpage_url @property - def created_at(self): + def created_at(self) -> Optional['datetime.datetime']: return self._created_at @property - def description(self): + def description(self) -> Optional[str]: return self._description @property - def id(self): + def id(self) -> Optional[str]: return self._id @property @@ -90,25 +92,25 @@ def preview_image(self): return self._preview_image() @property - def project_id(self): + def project_id(self) -> Optional[str]: return self._project_id @project_id.setter # type: ignore @property_not_nullable - def project_id(self, value): + def project_id(self, value: str): self._project_id = value @property - def project_name(self): + def project_name(self) -> Optional[str]: return self._project_name @property - def show_tabs(self): + def show_tabs(self) -> bool: return self._show_tabs @show_tabs.setter # type: ignore @property_is_boolean - def show_tabs(self, value): + def show_tabs(self, value: bool): self._show_tabs = value @property @@ -116,11 +118,11 @@ def size(self): return self._size @property - def updated_at(self): + def updated_at(self) -> Optional['datetime.datetime']: return self._updated_at @property - def views(self): + def views(self) -> List[ViewItem]: # Views can be set in an initial workbook response OR by a call # to Server. Without getting too fancy, I think we can rely on # returning a list from the response, until they call @@ -210,7 +212,7 @@ def _set_values(self, id, name, content_url, webpage_url, created_at, descriptio self.data_acceleration_config = data_acceleration_config @classmethod - def from_response(cls, resp, ns): + def from_response(cls, resp: str, ns: Optional[Dict[Union[str, str], Union[str, str]]]) -> List['WorkbookItem']: all_workbook_items = list() parsed_response = ET.fromstring(resp) all_workbook_xml = parsed_response.findall('.//t:workbook', namespaces=ns) @@ -300,5 +302,5 @@ def parse_data_acceleration_config(data_acceleration_elem): # Used to convert string represented boolean to a boolean type -def string_to_bool(s): +def string_to_bool(s: str) -> bool: return s.lower() == 'true' From 2a77b28248b995f59e0ae74929164e534d5922dc Mon Sep 17 00:00:00 2001 From: jorwoods Date: Thu, 4 Feb 2021 07:55:02 -0600 Subject: [PATCH 18/69] Begin annotations on workbook endpoint --- .../server/endpoint/workbooks_endpoint.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 62f94f99a..1e2bbed15 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -13,6 +13,12 @@ import cgi from contextlib import closing +from typing import Dict, List, Mapping, Optional, Tuple, TYPE_CHECKING, Union + +if TYPE_CHECKING: + from ..server import Server + from ..request_options import RequestOptions + # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -22,18 +28,20 @@ class Workbooks(QuerysetEndpoint): - def __init__(self, parent_srv): + def __init__(self, parent_srv: 'Server') -> None: super(Workbooks, self).__init__(parent_srv) self._resource_tagger = _ResourceTagger(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) + return + @property - def baseurl(self): + def baseurl(self) -> str: return "{0}/sites/{1}/workbooks".format(self.parent_srv.baseurl, self.parent_srv.site_id) # Get all workbooks on site @api(version="2.0") - def get(self, req_options=None): + def get(self, req_options: Optional['RequestOptions'] = None) -> Tuple[List[WorkbookItem], PaginationItem]: logger.info('Querying all workbooks on site') url = self.baseurl server_response = self.get_request(url, req_options) @@ -45,7 +53,7 @@ def get(self, req_options=None): # Get 1 workbook @api(version="2.0") - def get_by_id(self, workbook_id): + def get_by_id(self, workbook_id: str) -> WorkbookItem: if not workbook_id: error = "Workbook ID undefined." raise ValueError(error) @@ -55,7 +63,7 @@ def get_by_id(self, workbook_id): return WorkbookItem.from_response(server_response.content, self.parent_srv.namespace)[0] @api(version="2.8") - def refresh(self, workbook_id): + def refresh(self, workbook_id: str) -> JobItem: id_ = getattr(workbook_id, 'id', workbook_id) url = "{0}/{1}/refresh".format(self.baseurl, id_) empty_req = RequestFactory.Empty.empty_req() From 7efb7a616b9d9535aaf2137db39fb6ca944e6162 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 17 Feb 2021 22:45:36 -0600 Subject: [PATCH 19/69] Squashed commit of the following: commit 2609e278f39277db0c05d559fd71ea680a57cafa Merge: d107c48 3a8ec6c Author: Chris Shin Date: Wed Feb 17 16:00:42 2021 -0800 Merge pull request #803 from tableau/release-0.15 syncing development branch with v0.15 changelog commit d107c4827cd9ea59cdb9f618952eea35aef98197 Author: Tyler Doyle Date: Wed Feb 17 15:38:14 2021 -0800 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. commit 3a8ec6caf4f78bc7fcf55905a5d7429e6defe861 Merge: 004ab31 a5b9252 Author: Chris Shin Date: Tue Feb 16 16:51:37 2021 -0800 Merge branch 'master' into release-0.15 commit 004ab31140b3a4ee6157c36d8c6c434597151dd8 Author: Chris Shin Date: Tue Feb 16 16:49:19 2021 -0800 Updates changelog and contributors list for v0.15 commit 6c7a87b3e4e119621490f049d38d234a1c840a5b Author: Chris Shin Date: Tue Feb 16 15:09:45 2021 -0800 Removes travis and adds linting/testing into github action (#798) * Removes travis and adds github workflow * Addressing code review feedback commit 9179637b8cd3eb00ad7a12a44de9083e4ad39344 Author: Lee Boynton Date: Tue Feb 16 23:09:22 2021 +0000 Add support for getting groups that a user belongs to (#799) * Add support for getting groups that a user belongs to * Use more descriptive name for pager function commit fe992ee909cf7a01749504388b84f3afadf43bf5 Author: Chris Shin Date: Tue Feb 16 14:33:59 2021 -0800 [Tasks] Translate task type from server to TSC enum (#796) * Adds task type mapping to translate server response * Updates tests to match server response for task type * Fixes pycodestyle error commit f64fcf9a1c6d775a953aaedd966ca3c16fcb79fd Author: John Vandenberg Date: Wed Feb 17 01:21:46 2021 +0800 MANIFEST.in: Add docs and test data (#780) * Update publish_workbook.py (#694) * Update publish_workbook.py Added below arguments, without this there is a sign-in error on publishing a test file to Tableau Online parser.add_argument('--sitename', '-S', default='', help='sitename required') tableau_auth = TSC.TableauAuth(args.username, password,site_id=args.sitename) * Update publish_workbook.py Edits (as requested) to publish workbooks on Tableau Online which removes the Sign-in Error. * Update publish_workbook.py * Merge pull request #745 from tableau/fix_732 Server versions before 2020.1 do not accept encoded query param delimiters * Merge pull request #757 from tableau/fix_754 Fixes issue #754 by moving file read logic inside generator * Updates changelog for v0.14.1 * MANIFEST.in: Add docs and test data Closes https://github.com/tableau/server-client-python/issues/779 Co-authored-by: Chris Shin Co-authored-by: Madhura Selvarajan commit 88a01886b26165bd73bb8c4dd061efa4a3083a44 Author: Chris Shin Date: Tue Feb 16 09:17:35 2021 -0800 [Subscriptions] Add new fields and ability to update (#794) * Add fields and parsing logic * Update subscription create request * Adds update request to subscriptions * Changes subscription change request creation to use tsrequest annotation * Update tests for parsing new fields * Fixes codestyle issues * Removes user and schedule name * Fixes test failure commit 026bca8dd48f7fea85473599261bb673f6ad43be Author: Chris Shin Date: Thu Feb 11 15:34:08 2021 -0800 Adds skipConnectionCheck to publish workbook (#791) * Adds skipConnectionCheck flag to publish workbook * Removes unnecessary lines * Fixes style error commit 857199bba6c4d1c4b40199e02321707f8f0ea638 Author: tjones-commits <70481977+tjones-commits@users.noreply.github.com> Date: Fri Feb 5 17:09:32 2021 -0500 Update site properties and functions (#777) * Update publish_workbook.py (#694) * Update publish_workbook.py Added below arguments, without this there is a sign-in error on publishing a test file to Tableau Online parser.add_argument('--sitename', '-S', default='', help='sitename required') tableau_auth = TSC.TableauAuth(args.username, password,site_id=args.sitename) * Update publish_workbook.py Edits (as requested) to publish workbooks on Tableau Online which removes the Sign-in Error. * Update publish_workbook.py * Merge pull request #745 from tableau/fix_732 Server versions before 2020.1 do not accept encoded query param delimiters * Merge pull request #757 from tableau/fix_754 Fixes issue #754 by moving file read logic inside generator * Updates changelog for v0.14.1 * update the site item to reflect api response * update test model * remove extra test assets * trimming line length * unit test all properties. fix some properties. Remove extra code * make requested changes * make requested changes Co-authored-by: Chris Shin Co-authored-by: Madhura Selvarajan Co-authored-by: Terrence Jones commit a5b925222ae437bcbf38788c79aa72abce79d6e8 Merge: bcb881c 1fc349c Author: Chris Shin Date: Thu Dec 10 09:29:42 2020 -0800 Merge pull request #759 from tableau/0.14.0-patch v0.14.1 patch release * Fixed filter query issue for server version below 2020.1 (#745) * Fixed large workbook/datasource publish issue (#757) commit 1fc349c215e86167021a303c080c4823d3116d7c Author: Chris Shin Date: Wed Dec 9 16:30:03 2020 -0800 Updates changelog for v0.14.1 commit 41fd8f2af366f7da86c17cbae4fb5cc89895c970 Author: Chris Shin Date: Wed Dec 9 15:49:48 2020 -0800 Merge pull request #757 from tableau/fix_754 Fixes issue #754 by moving file read logic inside generator commit 138476f08b0c6275d3f24fff7654d95daf9a0d72 Author: Chris Shin Date: Wed Dec 9 15:49:23 2020 -0800 Merge pull request #745 from tableau/fix_732 Server versions before 2020.1 do not accept encoded query param delimiters commit bcb881c0e9d477d93339266126c527427d123410 Merge: 8d51355 1e089b4 Author: Chris Shin Date: Mon Nov 9 09:19:23 2020 -0800 Merge pull request #725 from tableau/development Syncing master with v0.14.0 changes from development. commit 8d513558597a55d80795a05733839d2338be3744 Author: Madhura Selvarajan Date: Wed Sep 23 10:45:27 2020 -0400 Update publish_workbook.py (#694) * Update publish_workbook.py Added below arguments, without this there is a sign-in error on publishing a test file to Tableau Online parser.add_argument('--sitename', '-S', default='', help='sitename required') tableau_auth = TSC.TableauAuth(args.username, password,site_id=args.sitename) * Update publish_workbook.py Edits (as requested) to publish workbooks on Tableau Online which removes the Sign-in Error. * Update publish_workbook.py commit 273af7acdc52169ac0f07f4236d7b7272836291d Merge: 43e1b06 168489c Author: Chris Shin Date: Wed Sep 2 14:39:13 2020 -0700 Merge pull request #685 from tableau/development [v0.13] Merge development into master Added notes field to JobItem (#571) Added webpage_url field to WorkbookItem (#661) Added support for switching between sites (#655) Added support for querying favorites for a user (#656) Added support for Python 3.8 (#659) Added support for Data Alerts (#667) Added support for basic Extract operations - Create, Delete, en/re/decrypt for site (#672) Added support for creating and querying Active Directory groups (#674) Added support for asynchronously updating a group (#674) Improved handling of invalid dates (#529) Improved consistency of update_permission endpoints (#668) Documentation updates (#658, #669, #670, #673, #683) --- .github/workflows/run-tests.yml | 38 ++ .travis.yml | 19 - CHANGELOG.md | 21 + CONTRIBUTORS.md | 3 + MANIFEST.in | 19 + samples/publish_workbook.py | 7 +- setup.py | 10 +- tableauserverclient/models/site_item.py | 532 +++++++++++++++++- .../models/subscription_item.py | 92 ++- tableauserverclient/models/task_item.py | 15 +- tableauserverclient/models/user_item.py | 11 + .../server/endpoint/subscriptions_endpoint.py | 12 + .../server/endpoint/users_endpoint.py | 22 +- .../server/endpoint/workbooks_endpoint.py | 5 +- tableauserverclient/server/request_factory.py | 206 ++++++- test/assets/site_create.xml | 2 +- test/assets/site_get.xml | 4 +- test/assets/site_get_by_id.xml | 2 +- test/assets/site_get_by_name.xml | 3 +- test/assets/site_update.xml | 2 +- test/assets/subscription_get.xml | 8 +- .../tasks_no_workbook_or_datasource.xml | 6 +- .../tasks_with_dataacceleration_task.xml | 2 +- test/assets/tasks_with_datasource.xml | 2 +- test/assets/tasks_with_workbook.xml | 2 +- .../tasks_with_workbook_and_datasource.xml | 6 +- test/assets/user_populate_groups.xml | 15 + test/test_site.py | 54 +- test/test_subscription.py | 27 +- test/test_task.py | 2 + test/test_user.py | 27 + test/test_workbook.py | 22 + 32 files changed, 1119 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/run-tests.yml delete mode 100644 .travis.yml create mode 100644 test/assets/user_populate_groups.xml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 000000000..a0917c7b6 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,38 @@ +name: Python package + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + 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 + continue-on-error: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3a57d48aa..000000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -dist: xenial -language: python -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" - - "3.9" -# command to install dependencies -install: - - "pip install -e ." - - "pip install pycodestyle" - - "pip install mypy" -# command to run tests -script: - # Tests - - python setup.py test - - pycodestyle tableauserverclient test samples - - mypy tableauserverclient test samples diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e6be649b..45a44b251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 0.15.0 (16 Feb 2021) +* Added support for python version 3.9 (#744) +* Added support for 'Get View by ID' (#750) +* Added docs and test data to MANIFEST.in file (#780) +* Added owner_id property to ProjectItem (#784) +* Added support for skipping connection check while publishing workbook (#791) +* Added support for 'Update Subscription' (#794) +* Added support for 'Get Groups for a User' (#799) +* Improved debug logging by including put/post request contents (#743) +* Improved local and active-directory group creation (#770) +* Improved 'Update Group' to match server requests/responses (#772) +* Improved SiteItem with new properties and functions (#777) +* Improved SubscriptionItem with new properties (#794) +* Improved the 'type' property of TaskItem to convert server response to enum (#796) +* Improved repository to use Github Actions for running tests/linter (#798) +* Fixed data_acceleration field causing error in workbook update payload (#741) + +## 0.14.1 (9 Dec 2020) +* Fixed filter query issue for server version below 2020.1 (#745) +* Fixed large workbook/datasource publish issue (#757) + ## 0.14.0 (6 Nov 2020) * Added django-style filtering and sorting (#615) * Added encoding tag-name before deleting (#687) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 811f5c5bf..2a19b1317 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -41,6 +41,9 @@ The following people have contributed to this project to make it possible, and w * [Paul Vickers](https://github.com/paulvic) * [Madhura Selvarajan](https://github.com/maddy-at-leisure) * [Niklas Nevalainen](https://github.com/nnevalainen) +* [Terrence Jones](https://github.com/tjones-commits) +* [John Vandenberg](https://github.com/jayvdb) +* [Lee Boynton](https://github.com/lboynton) ## Core Team diff --git a/MANIFEST.in b/MANIFEST.in index ae0a2ec7d..b4b1425f3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,22 @@ include versioneer.py include tableauserverclient/_version.py include LICENSE include LICENSE.versioneer +include README.md +include CHANGELOG.md +recursive-include docs *.md +recursive-include samples *.py +recursive-include samples *.txt +recursive-include smoke *.py +recursive-include test *.csv +recursive-include test *.dict +recursive-include test *.hyper +recursive-include test *.json +recursive-include test *.pdf +recursive-include test *.png +recursive-include test *.py +recursive-include test *.tde +recursive-include test *.tds +recursive-include test *.tdsx +recursive-include test *.twb +recursive-include test *.twbx +recursive-include test *.xml diff --git a/samples/publish_workbook.py b/samples/publish_workbook.py index be2c9599f..ca366cf9e 100644 --- a/samples/publish_workbook.py +++ b/samples/publish_workbook.py @@ -31,6 +31,7 @@ def main(): parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', help='desired logging level (set to error by default)') parser.add_argument('--as-job', '-a', help='Publishing asynchronously', action='store_true') + parser.add_argument('--skip-connection-check', '-c', help='Skip live connection check', action='store_true') parser.add_argument('--site', '-S', default='', help='id (contentUrl) of site to sign into') args = parser.parse_args() @@ -71,11 +72,13 @@ def main(): new_workbook = TSC.WorkbookItem(default_project.id) if args.as_job: new_job = server.workbooks.publish(new_workbook, args.filepath, overwrite_true, - connections=all_connections, as_job=args.as_job) + connections=all_connections, as_job=args.as_job, + skip_connection_check=args.skip_connection_check) print("Workbook published. JOB ID: {0}".format(new_job.id)) else: new_workbook = server.workbooks.publish(new_workbook, args.filepath, overwrite_true, - connections=all_connections, as_job=args.as_job) + connections=all_connections, as_job=args.as_job, + skip_connection_check=args.skip_connection_check) print("Workbook published. ID: {0}".format(new_workbook.id)) else: error = "The default project could not be found." diff --git a/setup.py b/setup.py index 5586e4716..8b374f0ce 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ # This makes work easier for offline installs or low bandwidth machines needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) pytest_runner = ['pytest-runner'] if needs_pytest else [] +test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0'] setup( name='tableauserverclient', @@ -34,9 +35,8 @@ install_requires=[ 'requests>=2.11,<3.0', ], - tests_require=[ - 'requests-mock>=1.0,<2.0', - 'pytest', - 'mock' - ] + tests_require=test_requirements, + extras_require={ + 'test': test_requirements + } ) diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index 081f47bd5..54f47233c 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -17,7 +17,19 @@ class State: 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=None, cataloging_enabled=None): + 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 @@ -36,6 +48,40 @@ def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_ self.data_acceleration_mode = data_acceleration_mode self.cataloging_enabled = cataloging_enabled self.flows_enabled = flows_enabled + self.editing_flows_enabled = editing_flows_enabled + self.scheduling_flows_enabled = scheduling_flows_enabled + self.allow_subscription_attachments = allow_subscription_attachments + self.guest_access_enabled = guest_access_enabled + self.cache_warmup_enabled = cache_warmup_enabled + self.commenting_enabled = commenting_enabled + self.extract_encryption_mode = extract_encryption_mode + self.request_access_enabled = request_access_enabled + self.run_now_enabled = run_now_enabled + self.tier_explorer_capacity = tier_explorer_capacity + self.tier_creator_capacity = tier_creator_capacity + self.tier_viewer_capacity = tier_viewer_capacity + self.data_alerts_enabled = data_alerts_enabled + self.commenting_mentions_enabled = commenting_mentions_enabled + self.catalog_obfuscation_enabled = catalog_obfuscation_enabled + self.flow_auto_save_enabled = flow_auto_save_enabled + self.web_extraction_enabled = web_extraction_enabled + self.metrics_content_type_enabled = metrics_content_type_enabled + self.notify_site_admins_on_throttle = notify_site_admins_on_throttle + self.authoring_enabled = authoring_enabled + self.custom_subscription_footer_enabled = custom_subscription_footer_enabled + self.custom_subscription_email_enabled = custom_subscription_email_enabled + self.custom_subscription_email = custom_subscription_email + self.custom_subscription_footer = custom_subscription_footer + self.ask_data_mode = ask_data_mode + self.named_sharing_enabled = named_sharing_enabled + self.mobile_biometrics_enabled = mobile_biometrics_enabled + self.sheet_image_enabled = sheet_image_enabled + self.derived_permissions_enabled = derived_permissions_enabled + self.user_visibility_mode = user_visibility_mode + self.use_default_time_zone = use_default_time_zone + self.time_zone = time_zone + self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled + self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window @property def admin_mode(self): @@ -147,12 +193,307 @@ def flows_enabled(self): return self._flows_enabled @flows_enabled.setter + @property_is_boolean def flows_enabled(self, value): self._flows_enabled = value def is_default(self): return self.name.lower() == 'default' + @property + def editing_flows_enabled(self): + return self._editing_flows_enabled + + @editing_flows_enabled.setter + @property_is_boolean + def editing_flows_enabled(self, value): + self._editing_flows_enabled = value + + @property + def scheduling_flows_enabled(self): + return self._scheduling_flows_enabled + + @scheduling_flows_enabled.setter + @property_is_boolean + def scheduling_flows_enabled(self, value): + self._scheduling_flows_enabled = value + + @property + def allow_subscription_attachments(self): + return self._allow_subscription_attachments + + @allow_subscription_attachments.setter + @property_is_boolean + def allow_subscription_attachments(self, value): + self._allow_subscription_attachments = value + + @property + def guest_access_enabled(self): + return self._guest_access_enabled + + @guest_access_enabled.setter + @property_is_boolean + def guest_access_enabled(self, value): + self._guest_access_enabled = value + + @property + def cache_warmup_enabled(self): + return self._cache_warmup_enabled + + @cache_warmup_enabled.setter + @property_is_boolean + def cache_warmup_enabled(self, value): + self._cache_warmup_enabled = value + + @property + def commenting_enabled(self): + return self._commenting_enabled + + @commenting_enabled.setter + @property_is_boolean + def commenting_enabled(self, value): + self._commenting_enabled = value + + @property + def extract_encryption_mode(self): + return self._extract_encryption_mode + + @extract_encryption_mode.setter + def extract_encryption_mode(self, value): + self._extract_encryption_mode = value + + @property + def request_access_enabled(self): + return self._request_access_enabled + + @request_access_enabled.setter + @property_is_boolean + def request_access_enabled(self, value): + self._request_access_enabled = value + + @property + def run_now_enabled(self): + return self._run_now_enabled + + @run_now_enabled.setter + @property_is_boolean + def run_now_enabled(self, value): + self._run_now_enabled = value + + @property + def tier_explorer_capacity(self): + return self._tier_explorer_capacity + + @tier_explorer_capacity.setter + def tier_explorer_capacity(self, value): + self._tier_explorer_capacity = value + + @property + def tier_creator_capacity(self): + return self._tier_creator_capacity + + @tier_creator_capacity.setter + def tier_creator_capacity(self, value): + self._tier_creator_capacity = value + + @property + def tier_viewer_capacity(self): + return self._tier_viewer_capacity + + @tier_viewer_capacity.setter + def tier_viewer_capacity(self, value): + self._tier_viewer_capacity = value + + @property + def data_alerts_enabled(self): + return self._data_alerts_enabled + + @data_alerts_enabled.setter + @property_is_boolean + def data_alerts_enabled(self, value): + self._data_alerts_enabled = value + + @property + def commenting_mentions_enabled(self): + return self._commenting_mentions_enabled + + @commenting_mentions_enabled.setter + @property_is_boolean + def commenting_mentions_enabled(self, value): + self._commenting_mentions_enabled = value + + @property + def catalog_obfuscation_enabled(self): + return self._catalog_obfuscation_enabled + + @catalog_obfuscation_enabled.setter + @property_is_boolean + def catalog_obfuscation_enabled(self, value): + self._catalog_obfuscation_enabled = value + + @property + def flow_auto_save_enabled(self): + return self._flow_auto_save_enabled + + @flow_auto_save_enabled.setter + @property_is_boolean + def flow_auto_save_enabled(self, value): + self._flow_auto_save_enabled = value + + @property + def web_extraction_enabled(self): + return self._web_extraction_enabled + + @web_extraction_enabled.setter + @property_is_boolean + def web_extraction_enabled(self, value): + self._web_extraction_enabled = value + + @property + def metrics_content_type_enabled(self): + return self._metrics_content_type_enabled + + @metrics_content_type_enabled.setter + @property_is_boolean + def metrics_content_type_enabled(self, value): + self._metrics_content_type_enabled = value + + @property + def notify_site_admins_on_throttle(self): + return self._notify_site_admins_on_throttle + + @notify_site_admins_on_throttle.setter + @property_is_boolean + def notify_site_admins_on_throttle(self, value): + self._notify_site_admins_on_throttle = value + + @property + def authoring_enabled(self): + return self._authoring_enabled + + @authoring_enabled.setter + @property_is_boolean + def authoring_enabled(self, value): + self._authoring_enabled = value + + @property + def custom_subscription_email_enabled(self): + return self._custom_subscription_email_enabled + + @custom_subscription_email_enabled.setter + @property_is_boolean + def custom_subscription_email_enabled(self, value): + self._custom_subscription_email_enabled = value + + @property + def custom_subscription_email(self): + return self._custom_subscription_email + + @custom_subscription_email.setter + def custom_subscription_email(self, value): + self._custom_subscription_email = value + + @property + def custom_subscription_footer_enabled(self): + return self._custom_subscription_footer_enabled + + @custom_subscription_footer_enabled.setter + @property_is_boolean + def custom_subscription_footer_enabled(self, value): + self._custom_subscription_footer_enabled = value + + @property + def custom_subscription_footer(self): + return self._custom_subscription_footer + + @custom_subscription_footer.setter + def custom_subscription_footer(self, value): + self._custom_subscription_footer = value + + @property + def ask_data_mode(self): + return self._ask_data_mode + + @ask_data_mode.setter + def ask_data_mode(self, value): + self._ask_data_mode = value + + @property + def named_sharing_enabled(self): + return self._named_sharing_enabled + + @named_sharing_enabled.setter + @property_is_boolean + def named_sharing_enabled(self, value): + self._named_sharing_enabled = value + + @property + def mobile_biometrics_enabled(self): + return self._mobile_biometrics_enabled + + @mobile_biometrics_enabled.setter + @property_is_boolean + def mobile_biometrics_enabled(self, value): + self._mobile_biometrics_enabled = value + + @property + def sheet_image_enabled(self): + return self._sheet_image_enabled + + @sheet_image_enabled.setter + @property_is_boolean + def sheet_image_enabled(self, value): + self._sheet_image_enabled = value + + @property + def derived_permissions_enabled(self): + return self._derived_permissions_enabled + + @derived_permissions_enabled.setter + @property_is_boolean + def derived_permissions_enabled(self, value): + self._derived_permissions_enabled = value + + @property + def user_visibility_mode(self): + return self._user_visibility_mode + + @user_visibility_mode.setter + def user_visibility_mode(self, value): + self._user_visibility_mode = value + + @property + def use_default_time_zone(self): + return self._use_default_time_zone + + @use_default_time_zone.setter + def use_default_time_zone(self, value): + self._use_default_time_zone = value + + @property + def time_zone(self): + return self._time_zone + + @time_zone.setter + def time_zone(self, value): + self._time_zone = value + + @property + def auto_suspend_refresh_inactivity_window(self): + return self._auto_suspend_refresh_inactivity_window + + @auto_suspend_refresh_inactivity_window.setter + def auto_suspend_refresh_inactivity_window(self, value): + self._auto_suspend_refresh_inactivity_window = value + + @property + def auto_suspend_refresh_enabled(self): + return self._auto_suspend_refresh_enabled + + @auto_suspend_refresh_enabled.setter + def auto_suspend_refresh_enabled(self, value): + self._auto_suspend_refresh_enabled = 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) @@ -160,18 +501,46 @@ def _parse_common_tags(self, site_xml, ns): (_, 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, cataloging_enabled, flows_enabled) = self._parse_element(site_xml, ns) + 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, cataloging_enabled, - flows_enabled) + 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): + 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: @@ -206,6 +575,74 @@ def _set_values(self, id, name, content_url, status_reason, admin_mode, state, self.flows_enabled = flows_enabled if cataloging_enabled is not None: self.cataloging_enabled = cataloging_enabled + if editing_flows_enabled is not None: + self.editing_flows_enabled = editing_flows_enabled + if scheduling_flows_enabled is not None: + self.scheduling_flows_enabled = scheduling_flows_enabled + if allow_subscription_attachments is not None: + self.allow_subscription_attachments = allow_subscription_attachments + if guest_access_enabled is not None: + self.guest_access_enabled = guest_access_enabled + if cache_warmup_enabled is not None: + self.cache_warmup_enabled = cache_warmup_enabled + if commenting_enabled is not None: + self.commenting_enabled = commenting_enabled + if extract_encryption_mode is not None: + self.extract_encryption_mode = extract_encryption_mode + if request_access_enabled is not None: + self.request_access_enabled = request_access_enabled + if run_now_enabled is not None: + self.run_now_enabled = run_now_enabled + if tier_explorer_capacity: + self.tier_explorer_capacity = tier_explorer_capacity + if tier_creator_capacity: + self.tier_creator_capacity = tier_creator_capacity + if tier_viewer_capacity: + self.tier_viewer_capacity = tier_viewer_capacity + if data_alerts_enabled is not None: + self.data_alerts_enabled = data_alerts_enabled + if commenting_mentions_enabled is not None: + self.commenting_mentions_enabled = commenting_mentions_enabled + if catalog_obfuscation_enabled is not None: + self.catalog_obfuscation_enabled = catalog_obfuscation_enabled + if flow_auto_save_enabled is not None: + self.flow_auto_save_enabled = flow_auto_save_enabled + if web_extraction_enabled is not None: + self.web_extraction_enabled = web_extraction_enabled + if metrics_content_type_enabled is not None: + self.metrics_content_type_enabled = metrics_content_type_enabled + if notify_site_admins_on_throttle is not None: + self.notify_site_admins_on_throttle = notify_site_admins_on_throttle + if authoring_enabled is not None: + self.authoring_enabled = authoring_enabled + if custom_subscription_email_enabled is not None: + self.custom_subscription_email_enabled = custom_subscription_email_enabled + if custom_subscription_email is not None: + self.custom_subscription_email = custom_subscription_email + if custom_subscription_footer_enabled is not None: + self.custom_subscription_footer_enabled = custom_subscription_footer_enabled + if custom_subscription_footer is not None: + self.custom_subscription_footer = custom_subscription_footer + if ask_data_mode is not None: + self.ask_data_mode = ask_data_mode + if named_sharing_enabled is not None: + self.named_sharing_enabled = named_sharing_enabled + if mobile_biometrics_enabled is not None: + self.mobile_biometrics_enabled = mobile_biometrics_enabled + if sheet_image_enabled is not None: + self.sheet_image_enabled = sheet_image_enabled + if derived_permissions_enabled is not None: + self.derived_permissions_enabled = derived_permissions_enabled + if user_visibility_mode is not None: + self.user_visibility_mode = user_visibility_mode + if use_default_time_zone is not None: + self.use_default_time_zone = use_default_time_zone + if time_zone is not None: + self.time_zone = time_zone + if auto_suspend_refresh_enabled is not None: + self.auto_suspend_refresh_enabled = auto_suspend_refresh_enabled + if auto_suspend_refresh_inactivity_window is not None: + self.auto_suspend_refresh_inactivity_window = auto_suspend_refresh_inactivity_window @classmethod def from_response(cls, resp, ns): @@ -215,14 +652,34 @@ def from_response(cls, resp, 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) = cls._parse_element(site_xml, ns) + 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) + 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 @@ -237,6 +694,48 @@ def _parse_element(site_xml, ns): 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) + if tier_creator_capacity: + tier_creator_capacity = int(tier_creator_capacity) + 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) + if auto_suspend_refresh_inactivity_window: + auto_suspend_refresh_inactivity_window = int(auto_suspend_refresh_inactivity_window) user_quota = site_xml.get('userQuota', None) if user_quota: @@ -264,7 +763,16 @@ def _parse_element(site_xml, ns): 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 + 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 diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index 1a93c60d2..cdcc468a1 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -1,15 +1,23 @@ import xml.etree.ElementTree as ET from .target import Target +from .property_decorators import property_is_boolean class SubscriptionItem(object): def __init__(self, subject, schedule_id, user_id, target): - self.id = None - self.subject = subject + self._id = None + self.attach_image = True + self.attach_pdf = False + self.message = None + self.page_orientation = None + self.page_size_option = None self.schedule_id = schedule_id - self.user_id = user_id + self.send_if_view_empty = True + self.subject = subject + self.suspended = False self.target = target + self.user_id = user_id def __repr__(self): if self.id is not None: @@ -19,8 +27,45 @@ def __repr__(self): return "= FILESIZE_LIMIT: logger.info('Publishing {0} to server with chunking method (workbook over 64MB)'.format(workbook_item.name)) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 14c755bbd..e34220188 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -472,7 +472,7 @@ def update_req(self, site_item): 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) - if site_item.subscribe_others_enabled is not None: + if site_item.revision_history_enabled is not None: 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() @@ -480,6 +480,78 @@ def update_req(self, site_item): 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() + if site_item.editing_flows_enabled is not None: + 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() + if site_item.allow_subscription_attachments is not None: + 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() + if site_item.cache_warmup_enabled is not None: + 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() + if site_item.extract_encryption_mode is not None: + 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() + if site_item.run_now_enabled is not None: + 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() + if site_item.tier_explorer_capacity is not None: + 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() + if site_item.data_alerts_enabled is not None: + 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() + if site_item.catalog_obfuscation_enabled is not None: + 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() + if site_item.web_extraction_enabled is not None: + 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() + if site_item.notify_site_admins_on_throttle is not None: + 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() + if site_item.custom_subscription_email_enabled is not None: + 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() + if site_item.custom_subscription_footer_enabled is not None: + 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() + if site_item.ask_data_mode is not None: + 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() + if site_item.mobile_biometrics_enabled is not None: + 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() + if site_item.derived_permissions_enabled is not None: + 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) + if site_item.use_default_time_zone is not None: + 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) + if site_item.auto_suspend_refresh_enabled is not None: + 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) + return ET.tostring(xml_request) def create_req(self, site_item): @@ -495,10 +567,90 @@ def create_req(self, site_item): 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() + if site_item.subscribe_others_enabled is not None: + 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) + if site_item.data_acceleration_mode is not None: + 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() + if site_item.editing_flows_enabled is not None: + 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() + if site_item.allow_subscription_attachments is not None: + 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() + if site_item.cache_warmup_enabled is not None: + 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() + if site_item.revision_history_enabled is not None: + 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() + if site_item.request_access_enabled is not None: + 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() + if site_item.tier_creator_capacity is not None: + 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() + if site_item.tier_viewer_capacity is not None: + 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() + if site_item.commenting_mentions_enabled is not None: + 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() + if site_item.flow_auto_save_enabled is not None: + 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() + if site_item.metrics_content_type_enabled is not None: + 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() + if site_item.authoring_enabled is not None: + 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() + if site_item.custom_subscription_email is not None: + 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() + if site_item.custom_subscription_footer is not None: + 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) + if site_item.named_sharing_enabled is not None: + 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() + if site_item.sheet_image_enabled is not None: + 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() + if site_item.derived_permissions_enabled is not None: + 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) + if site_item.use_default_time_zone is not None: + 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) + if site_item.auto_suspend_refresh_enabled is not None: + 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) + return ET.tostring(xml_request) @@ -675,22 +827,68 @@ def run_req(self, xml_request, task_item): class SubscriptionRequest(object): - def create_req(self, subscription_item): - xml_request = ET.Element('tsRequest') + @_tsrequest_wrapped + def create_req(self, xml_request, subscription_item): subscription_element = ET.SubElement(xml_request, 'subscription') - subscription_element.attrib['subject'] = subscription_item.subject + # Main attributes + 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() + if subscription_item.attach_pdf is not None: + subscription_element.attrib['attachPdf'] = str(subscription_item.attach_pdf).lower() + if subscription_item.message is not None: + subscription_element.attrib['message'] = subscription_item.message + if subscription_item.page_orientation is not None: + 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 + + # 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 + if subscription_item.send_if_view_empty is not None: + 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 + # User element 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') + + # Main attributes + if subscription_item.subject is not None: + subscription.attrib['subject'] = subscription_item.subject + if subscription_item.attach_image is not None: + 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() + if subscription_item.page_orientation is not None: + subscription.attrib['pageOrientation'] = subscription_item.page_orientation + if subscription_item.page_size_option is not None: + subscription.attrib['pageSizeOption'] = subscription_item.page_size_option + if subscription_item.suspended is not None: + subscription.attrib['suspended'] = str(subscription_item.suspended).lower() + + # Schedule element + schedule = ET.SubElement(subscription, 'schedule') + if subscription_item.schedule_id is not None: + schedule.attrib['id'] = subscription_item.schedule_id + + # Content element + 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() + return ET.tostring(xml_request) + class EmptyRequest(object): @_tsrequest_wrapped diff --git a/test/assets/site_create.xml b/test/assets/site_create.xml index 9fafb5f02..9d9c4a009 100644 --- a/test/assets/site_create.xml +++ b/test/assets/site_create.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/test/assets/site_get.xml b/test/assets/site_get.xml index e3c7a781c..7ffa91eb7 100644 --- a/test/assets/site_get.xml +++ b/test/assets/site_get.xml @@ -2,7 +2,7 @@ - - + + \ No newline at end of file diff --git a/test/assets/site_get_by_id.xml b/test/assets/site_get_by_id.xml index 98bc3e4e6..a47703fb6 100644 --- a/test/assets/site_get_by_id.xml +++ b/test/assets/site_get_by_id.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/test/assets/site_get_by_name.xml b/test/assets/site_get_by_name.xml index 5b3042e61..852f9594f 100644 --- a/test/assets/site_get_by_name.xml +++ b/test/assets/site_get_by_name.xml @@ -1,5 +1,4 @@ - + \ No newline at end of file diff --git a/test/assets/site_update.xml b/test/assets/site_update.xml index 30e434373..dbb166de1 100644 --- a/test/assets/site_update.xml +++ b/test/assets/site_update.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/test/assets/subscription_get.xml b/test/assets/subscription_get.xml index d038c8419..b66ffc927 100644 --- a/test/assets/subscription_get.xml +++ b/test/assets/subscription_get.xml @@ -4,13 +4,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd"> - - + + - - + + diff --git a/test/assets/tasks_no_workbook_or_datasource.xml b/test/assets/tasks_no_workbook_or_datasource.xml index 7ddbcae62..da84194bf 100644 --- a/test/assets/tasks_no_workbook_or_datasource.xml +++ b/test/assets/tasks_no_workbook_or_datasource.xml @@ -4,17 +4,17 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd"> - + - + - + diff --git a/test/assets/tasks_with_dataacceleration_task.xml b/test/assets/tasks_with_dataacceleration_task.xml index cbe837405..beb5d59eb 100644 --- a/test/assets/tasks_with_dataacceleration_task.xml +++ b/test/assets/tasks_with_dataacceleration_task.xml @@ -2,7 +2,7 @@ - + diff --git a/test/assets/tasks_with_datasource.xml b/test/assets/tasks_with_datasource.xml index 68e23a417..097161bf7 100644 --- a/test/assets/tasks_with_datasource.xml +++ b/test/assets/tasks_with_datasource.xml @@ -4,7 +4,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd"> - + diff --git a/test/assets/tasks_with_workbook.xml b/test/assets/tasks_with_workbook.xml index 1565abf74..81e974e78 100644 --- a/test/assets/tasks_with_workbook.xml +++ b/test/assets/tasks_with_workbook.xml @@ -4,7 +4,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd"> - + diff --git a/test/assets/tasks_with_workbook_and_datasource.xml b/test/assets/tasks_with_workbook_and_datasource.xml index 4389fa06c..81777bb46 100644 --- a/test/assets/tasks_with_workbook_and_datasource.xml +++ b/test/assets/tasks_with_workbook_and_datasource.xml @@ -4,19 +4,19 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.6.xsd"> - + - + - + diff --git a/test/assets/user_populate_groups.xml b/test/assets/user_populate_groups.xml new file mode 100644 index 000000000..567f1dbf8 --- /dev/null +++ b/test/assets/user_populate_groups.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/test/test_site.py b/test/test_site.py index a06876e2a..8fbb4eda3 100644 --- a/test/test_site.py +++ b/test/test_site.py @@ -36,13 +36,30 @@ def test_get(self): self.assertEqual('ContentOnly', all_sites[0].admin_mode) self.assertEqual(False, all_sites[0].revision_history_enabled) self.assertEqual(True, all_sites[0].subscribe_others_enabled) - + self.assertEqual(25, all_sites[0].revision_limit) + self.assertEqual(None, all_sites[0].num_users) + self.assertEqual(None, all_sites[0].storage) + self.assertEqual(True, all_sites[0].cataloging_enabled) + self.assertEqual(False, all_sites[0].editing_flows_enabled) + self.assertEqual(False, all_sites[0].scheduling_flows_enabled) + self.assertEqual(True, all_sites[0].allow_subscription_attachments) self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', all_sites[1].id) self.assertEqual('Active', all_sites[1].state) self.assertEqual('Samples', all_sites[1].name) self.assertEqual('ContentOnly', all_sites[1].admin_mode) self.assertEqual(False, all_sites[1].revision_history_enabled) self.assertEqual(True, all_sites[1].subscribe_others_enabled) + self.assertEqual(False, all_sites[1].guest_access_enabled) + self.assertEqual(True, all_sites[1].cache_warmup_enabled) + self.assertEqual(True, all_sites[1].commenting_enabled) + self.assertEqual(True, all_sites[1].cache_warmup_enabled) + self.assertEqual(False, all_sites[1].request_access_enabled) + self.assertEqual(True, all_sites[1].run_now_enabled) + self.assertEqual(1, all_sites[1].tier_explorer_capacity) + self.assertEqual(2, all_sites[1].tier_creator_capacity) + self.assertEqual(1, all_sites[1].tier_viewer_capacity) + self.assertEqual(False, all_sites[1].flows_enabled) + self.assertEqual(None, all_sites[1].data_acceleration_mode) def test_get_before_signin(self): self.server._auth_token = None @@ -62,6 +79,9 @@ def test_get_by_id(self): self.assertEqual(False, single_site.revision_history_enabled) self.assertEqual(True, single_site.subscribe_others_enabled) self.assertEqual(False, single_site.disable_subscriptions) + self.assertEqual(False, single_site.data_alerts_enabled) + self.assertEqual(False, single_site.commenting_mentions_enabled) + self.assertEqual(True, single_site.catalog_obfuscation_enabled) def test_get_by_id_missing_id(self): self.assertRaises(ValueError, self.server.sites.get_by_id, '') @@ -93,7 +113,18 @@ def test_update(self): admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers, user_quota=15, storage_quota=1000, disable_subscriptions=True, revision_history_enabled=False, - data_acceleration_mode='disable') + data_acceleration_mode='disable', flow_auto_save_enabled=True, + web_extraction_enabled=False, metrics_content_type_enabled=True, + notify_site_admins_on_throttle=False, authoring_enabled=True, + custom_subscription_email_enabled=True, + custom_subscription_email='test@test.com', + custom_subscription_footer_enabled=True, + custom_subscription_footer='example_footer', ask_data_mode='EnabledByDefault', + named_sharing_enabled=False, mobile_biometrics_enabled=True, + sheet_image_enabled=False, derived_permissions_enabled=True, + user_visibility_mode='FULL', use_default_time_zone=False, + time_zone='America/Los_Angeles', auto_suspend_refresh_enabled=True, + auto_suspend_refresh_inactivity_window=55) single_site._id = '6b7179ba-b82b-4f0f-91ed-812074ac5da6' single_site = self.server.sites.update(single_site) @@ -109,6 +140,25 @@ def test_update(self): self.assertEqual('disable', single_site.data_acceleration_mode) self.assertEqual(True, single_site.flows_enabled) self.assertEqual(True, single_site.cataloging_enabled) + self.assertEqual(True, single_site.flow_auto_save_enabled) + self.assertEqual(False, single_site.web_extraction_enabled) + self.assertEqual(True, single_site.metrics_content_type_enabled) + self.assertEqual(False, single_site.notify_site_admins_on_throttle) + self.assertEqual(True, single_site.authoring_enabled) + self.assertEqual(True, single_site.custom_subscription_email_enabled) + self.assertEqual('test@test.com', single_site.custom_subscription_email) + self.assertEqual(True, single_site.custom_subscription_footer_enabled) + self.assertEqual('example_footer', single_site.custom_subscription_footer) + self.assertEqual('EnabledByDefault', single_site.ask_data_mode) + self.assertEqual(False, single_site.named_sharing_enabled) + self.assertEqual(True, single_site.mobile_biometrics_enabled) + self.assertEqual(False, single_site.sheet_image_enabled) + self.assertEqual(True, single_site.derived_permissions_enabled) + self.assertEqual('FULL', single_site.user_visibility_mode) + self.assertEqual(False, single_site.use_default_time_zone) + self.assertEqual('America/Los_Angeles', single_site.time_zone) + self.assertEqual(True, single_site.auto_suspend_refresh_enabled) + self.assertEqual(55, single_site.auto_suspend_refresh_inactivity_window) def test_update_missing_id(self): single_site = TSC.SiteItem('test', 'test') diff --git a/test/test_subscription.py b/test/test_subscription.py index 2e4b1eadf..15b845e56 100644 --- a/test/test_subscription.py +++ b/test/test_subscription.py @@ -28,14 +28,37 @@ def test_get_subscriptions(self): m.get(self.baseurl, text=response_xml) all_subscriptions, pagination_item = self.server.subscriptions.get() + self.assertEqual(2, pagination_item.total_available) subscription = all_subscriptions[0] self.assertEqual('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', subscription.id) - self.assertEqual('View', subscription.target.type) + self.assertEqual('NOT FOUND!', subscription.message) + self.assertTrue(subscription.attach_image) + self.assertFalse(subscription.attach_pdf) + self.assertFalse(subscription.suspended) + self.assertFalse(subscription.send_if_view_empty) + self.assertIsNone(subscription.page_orientation) + self.assertIsNone(subscription.page_size_option) + self.assertEqual('Not Found Alert', subscription.subject) self.assertEqual('cdd716ca-5818-470e-8bec-086885dbadee', subscription.target.id) + self.assertEqual('View', subscription.target.type) self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id) - self.assertEqual('Not Found Alert', subscription.subject) self.assertEqual('7617c389-cdca-4940-a66e-69956fcebf3e', subscription.schedule_id) + subscription = all_subscriptions[1] + self.assertEqual('23cb7630-afc8-4c8e-b6cd-83ae0322ec66', subscription.id) + self.assertEqual('overview', subscription.message) + self.assertFalse(subscription.attach_image) + self.assertTrue(subscription.attach_pdf) + self.assertTrue(subscription.suspended) + self.assertTrue(subscription.send_if_view_empty) + self.assertEqual('PORTRAIT', subscription.page_orientation) + self.assertEqual('A5', subscription.page_size_option) + self.assertEqual('Last 7 Days', subscription.subject) + self.assertEqual('2e6b4e8f-22dd-4061-8f75-bf33703da7e5', subscription.target.id) + self.assertEqual('Workbook', subscription.target.type) + self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id) + self.assertEqual('3407cd38-7b39-4983-86a6-67a1506a5e3f', subscription.schedule_id) + def test_get_subscription_by_id(self): with open(GET_XML_BY_ID, "rb") as f: response_xml = f.read().decode("utf-8") diff --git a/test/test_task.py b/test/test_task.py index 789f97187..566167d4a 100644 --- a/test/test_task.py +++ b/test/test_task.py @@ -104,6 +104,7 @@ def test_get_materializeviews_tasks(self): self.assertEqual('b22190b4-6ac2-4eed-9563-4afc03444413', task.schedule_id) self.assertEqual(parse_datetime('2019-12-09T22:30:00Z'), task.schedule_item.next_run_at) self.assertEqual(parse_datetime('2019-12-09T20:45:04Z'), task.last_run_at) + self.assertEqual(TSC.TaskItem.Type.DataAcceleration, task.task_type) def test_delete_data_acceleration(self): with requests_mock.mock() as m: @@ -124,6 +125,7 @@ def test_get_by_id(self): self.assertEqual('c7a9327e-1cda-4504-b026-ddb43b976d1d', task.target.id) self.assertEqual('workbook', task.target.type) self.assertEqual('b60b4efd-a6f7-4599-beb3-cb677e7abac1', task.schedule_id) + self.assertEqual(TSC.TaskItem.Type.ExtractRefresh, task.task_type) def test_run_now(self): task_id = 'f84901ac-72ad-4f9b-a87e-7a3500402ad6' diff --git a/test/test_user.py b/test/test_user.py index db0f829f7..e4d1d6717 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -13,6 +13,7 @@ ADD_XML = os.path.join(TEST_ASSET_DIR, 'user_add.xml') POPULATE_WORKBOOKS_XML = os.path.join(TEST_ASSET_DIR, 'user_populate_workbooks.xml') GET_FAVORITES_XML = os.path.join(TEST_ASSET_DIR, 'favorites_get.xml') +POPULATE_GROUPS_XML = os.path.join(TEST_ASSET_DIR, 'user_populate_groups.xml') class UserTests(unittest.TestCase): @@ -175,3 +176,29 @@ def test_populate_favorites(self): self.assertEqual(view.id, 'd79634e1-6063-4ec9-95ff-50acbf609ff5') self.assertEqual(datasource.id, 'e76a1461-3b1d-4588-bf1b-17551a879ad9') self.assertEqual(project.id, '1d0304cd-3796-429f-b815-7258370b9b74') + + def test_populate_groups(self): + self.server.version = '3.7' + with open(POPULATE_GROUPS_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.get(self.server.users.baseurl + '/dd2239f6-ddf1-4107-981a-4cf94e415794/groups', + text=response_xml) + single_user = TSC.UserItem('test', 'Interactor') + single_user._id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' + self.server.users.populate_groups(single_user) + + group_list = list(single_user.groups) + + self.assertEqual(3, len(group_list)) + self.assertEqual('ef8b19c0-43b6-11e6-af50-63f5805dbe3c', group_list[0].id) + self.assertEqual('All Users', group_list[0].name) + self.assertEqual('local', group_list[0].domain_name) + + self.assertEqual('e7833b48-c6f7-47b5-a2a7-36e7dd232758', group_list[1].id) + self.assertEqual('Another group', group_list[1].name) + self.assertEqual('local', group_list[1].domain_name) + + self.assertEqual('86a66d40-f289-472a-83d0-927b0f954dc8', group_list[2].id) + self.assertEqual('TableauExample', group_list[2].name) + self.assertEqual('local', group_list[2].domain_name) diff --git a/test/test_workbook.py b/test/test_workbook.py index f14e4d96f..fc1344b9e 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -544,6 +544,28 @@ def test_publish_with_hidden_view(self): self.assertTrue(re.search(rb'<\/views>', request_body)) self.assertTrue(re.search(rb'<\/views>', request_body)) + def test_publish_with_query_params(self): + with open(PUBLISH_ASYNC_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.post(self.baseurl, text=response_xml) + + new_workbook = TSC.WorkbookItem(name='Sample', + show_tabs=False, + project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + + sample_workbook = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx') + publish_mode = self.server.PublishMode.CreateNew + + self.server.workbooks.publish(new_workbook, sample_workbook, publish_mode, + as_job=True, skip_connection_check=True) + + request_query_params = m._adapter.request_history[0].qs + self.assertTrue('asjob' in request_query_params) + self.assertTrue(request_query_params['asjob']) + self.assertTrue('skipconnectioncheck' in request_query_params) + self.assertTrue(request_query_params['skipconnectioncheck']) + def test_publish_async(self): self.server.version = '3.0' baseurl = self.server.workbooks.baseurl From 0bf6fd972c4bb6bd9ee4377cd2737cd720759fb7 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 17 Feb 2021 22:50:23 -0600 Subject: [PATCH 20/69] Ignore property decorator warnings --- tableauserverclient/models/site_item.py | 46 +++++++++---------- .../models/subscription_item.py | 8 ++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index 54f47233c..c3af6c7ed 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -192,7 +192,7 @@ def cataloging_enabled(self, value): def flows_enabled(self): return self._flows_enabled - @flows_enabled.setter + @flows_enabled.setter # type: ignore @property_is_boolean def flows_enabled(self, value): self._flows_enabled = value @@ -204,7 +204,7 @@ def is_default(self): def editing_flows_enabled(self): return self._editing_flows_enabled - @editing_flows_enabled.setter + @editing_flows_enabled.setter # type: ignore @property_is_boolean def editing_flows_enabled(self, value): self._editing_flows_enabled = value @@ -213,7 +213,7 @@ def editing_flows_enabled(self, value): def scheduling_flows_enabled(self): return self._scheduling_flows_enabled - @scheduling_flows_enabled.setter + @scheduling_flows_enabled.setter # type: ignore @property_is_boolean def scheduling_flows_enabled(self, value): self._scheduling_flows_enabled = value @@ -222,7 +222,7 @@ def scheduling_flows_enabled(self, value): def allow_subscription_attachments(self): return self._allow_subscription_attachments - @allow_subscription_attachments.setter + @allow_subscription_attachments.setter # type: ignore @property_is_boolean def allow_subscription_attachments(self, value): self._allow_subscription_attachments = value @@ -231,7 +231,7 @@ def allow_subscription_attachments(self, value): def guest_access_enabled(self): return self._guest_access_enabled - @guest_access_enabled.setter + @guest_access_enabled.setter # type: ignore @property_is_boolean def guest_access_enabled(self, value): self._guest_access_enabled = value @@ -240,7 +240,7 @@ def guest_access_enabled(self, value): def cache_warmup_enabled(self): return self._cache_warmup_enabled - @cache_warmup_enabled.setter + @cache_warmup_enabled.setter # type: ignore @property_is_boolean def cache_warmup_enabled(self, value): self._cache_warmup_enabled = value @@ -249,7 +249,7 @@ def cache_warmup_enabled(self, value): def commenting_enabled(self): return self._commenting_enabled - @commenting_enabled.setter + @commenting_enabled.setter # type: ignore @property_is_boolean def commenting_enabled(self, value): self._commenting_enabled = value @@ -266,7 +266,7 @@ def extract_encryption_mode(self, value): def request_access_enabled(self): return self._request_access_enabled - @request_access_enabled.setter + @request_access_enabled.setter # type: ignore @property_is_boolean def request_access_enabled(self, value): self._request_access_enabled = value @@ -275,7 +275,7 @@ def request_access_enabled(self, value): def run_now_enabled(self): return self._run_now_enabled - @run_now_enabled.setter + @run_now_enabled.setter # type: ignore @property_is_boolean def run_now_enabled(self, value): self._run_now_enabled = value @@ -308,7 +308,7 @@ def tier_viewer_capacity(self, value): def data_alerts_enabled(self): return self._data_alerts_enabled - @data_alerts_enabled.setter + @data_alerts_enabled.setter # type: ignore @property_is_boolean def data_alerts_enabled(self, value): self._data_alerts_enabled = value @@ -317,7 +317,7 @@ def data_alerts_enabled(self, value): def commenting_mentions_enabled(self): return self._commenting_mentions_enabled - @commenting_mentions_enabled.setter + @commenting_mentions_enabled.setter # type: ignore @property_is_boolean def commenting_mentions_enabled(self, value): self._commenting_mentions_enabled = value @@ -326,7 +326,7 @@ def commenting_mentions_enabled(self, value): def catalog_obfuscation_enabled(self): return self._catalog_obfuscation_enabled - @catalog_obfuscation_enabled.setter + @catalog_obfuscation_enabled.setter # type: ignore @property_is_boolean def catalog_obfuscation_enabled(self, value): self._catalog_obfuscation_enabled = value @@ -335,7 +335,7 @@ def catalog_obfuscation_enabled(self, value): def flow_auto_save_enabled(self): return self._flow_auto_save_enabled - @flow_auto_save_enabled.setter + @flow_auto_save_enabled.setter # type: ignore @property_is_boolean def flow_auto_save_enabled(self, value): self._flow_auto_save_enabled = value @@ -344,7 +344,7 @@ def flow_auto_save_enabled(self, value): def web_extraction_enabled(self): return self._web_extraction_enabled - @web_extraction_enabled.setter + @web_extraction_enabled.setter # type: ignore @property_is_boolean def web_extraction_enabled(self, value): self._web_extraction_enabled = value @@ -353,7 +353,7 @@ def web_extraction_enabled(self, value): def metrics_content_type_enabled(self): return self._metrics_content_type_enabled - @metrics_content_type_enabled.setter + @metrics_content_type_enabled.setter # type: ignore @property_is_boolean def metrics_content_type_enabled(self, value): self._metrics_content_type_enabled = value @@ -362,7 +362,7 @@ def metrics_content_type_enabled(self, value): def notify_site_admins_on_throttle(self): return self._notify_site_admins_on_throttle - @notify_site_admins_on_throttle.setter + @notify_site_admins_on_throttle.setter # type: ignore @property_is_boolean def notify_site_admins_on_throttle(self, value): self._notify_site_admins_on_throttle = value @@ -371,7 +371,7 @@ def notify_site_admins_on_throttle(self, value): def authoring_enabled(self): return self._authoring_enabled - @authoring_enabled.setter + @authoring_enabled.setter # type: ignore @property_is_boolean def authoring_enabled(self, value): self._authoring_enabled = value @@ -380,7 +380,7 @@ def authoring_enabled(self, value): def custom_subscription_email_enabled(self): return self._custom_subscription_email_enabled - @custom_subscription_email_enabled.setter + @custom_subscription_email_enabled.setter # type: ignore @property_is_boolean def custom_subscription_email_enabled(self, value): self._custom_subscription_email_enabled = value @@ -397,7 +397,7 @@ def custom_subscription_email(self, value): def custom_subscription_footer_enabled(self): return self._custom_subscription_footer_enabled - @custom_subscription_footer_enabled.setter + @custom_subscription_footer_enabled.setter # type: ignore @property_is_boolean def custom_subscription_footer_enabled(self, value): self._custom_subscription_footer_enabled = value @@ -422,7 +422,7 @@ def ask_data_mode(self, value): def named_sharing_enabled(self): return self._named_sharing_enabled - @named_sharing_enabled.setter + @named_sharing_enabled.setter # type: ignore @property_is_boolean def named_sharing_enabled(self, value): self._named_sharing_enabled = value @@ -431,7 +431,7 @@ def named_sharing_enabled(self, value): def mobile_biometrics_enabled(self): return self._mobile_biometrics_enabled - @mobile_biometrics_enabled.setter + @mobile_biometrics_enabled.setter # type: ignore @property_is_boolean def mobile_biometrics_enabled(self, value): self._mobile_biometrics_enabled = value @@ -440,7 +440,7 @@ def mobile_biometrics_enabled(self, value): def sheet_image_enabled(self): return self._sheet_image_enabled - @sheet_image_enabled.setter + @sheet_image_enabled.setter # type: ignore @property_is_boolean def sheet_image_enabled(self, value): self._sheet_image_enabled = value @@ -449,7 +449,7 @@ def sheet_image_enabled(self, value): def derived_permissions_enabled(self): return self._derived_permissions_enabled - @derived_permissions_enabled.setter + @derived_permissions_enabled.setter # type: ignore @property_is_boolean def derived_permissions_enabled(self, value): self._derived_permissions_enabled = value diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index cdcc468a1..60886b1fd 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -35,7 +35,7 @@ def id(self): def attach_image(self): return self._attach_image - @attach_image.setter + @attach_image.setter # type: ignore @property_is_boolean def attach_image(self, value): self._attach_image = value @@ -44,7 +44,7 @@ def attach_image(self, value): def attach_pdf(self): return self._attach_pdf - @attach_pdf.setter + @attach_pdf.setter # type: ignore @property_is_boolean def attach_pdf(self, value): self._attach_pdf = value @@ -53,7 +53,7 @@ def attach_pdf(self, value): def send_if_view_empty(self): return self._send_if_view_empty - @send_if_view_empty.setter + @send_if_view_empty.setter # type: ignore @property_is_boolean def send_if_view_empty(self, value): self._send_if_view_empty = value @@ -62,7 +62,7 @@ def send_if_view_empty(self, value): def suspended(self): return self._suspended - @suspended.setter + @suspended.setter # type: ignore @property_is_boolean def suspended(self, value): self._suspended = value From a8b42e1a7f24f080038d1f22ba2b666551ab774d Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 17 Feb 2021 23:00:05 -0600 Subject: [PATCH 21/69] Continue annotations on wb endpoint --- .../server/endpoint/workbooks_endpoint.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 3427ba24b..306cbb72e 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: from ..server import Server from ..request_options import RequestOptions + from .. import DatasourceItem # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -73,7 +74,8 @@ def refresh(self, workbook_id: str) -> JobItem: # create one or more extracts on 1 workbook, optionally encrypted @api(version='3.5') - def create_extract(self, workbook_item, encrypt=False, includeAll=True, datasources=None): + def create_extract(self, workbook_item: WorkbookItem, encrypt: bool = False, includeAll: bool = True, + datasources: Optional[List['DatasourceItem']] = None) -> JobItem: id_ = getattr(workbook_item, 'id', workbook_item) url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt) @@ -84,7 +86,7 @@ def create_extract(self, workbook_item, encrypt=False, includeAll=True, datasour # delete all the extracts on 1 workbook @api(version='3.5') - def delete_extract(self, workbook_item): + def delete_extract(self, workbook_item: WorkbookItem) -> None: id_ = getattr(workbook_item, 'id', workbook_item) url = "{0}/{1}/deleteExtract".format(self.baseurl, id_) empty_req = RequestFactory.Empty.empty_req() @@ -92,7 +94,7 @@ def delete_extract(self, workbook_item): # Delete 1 workbook by id @api(version="2.0") - def delete(self, workbook_id): + def delete(self, workbook_id: str) -> None: if not workbook_id: error = "Workbook ID undefined." raise ValueError(error) @@ -102,7 +104,7 @@ def delete(self, workbook_id): # Update workbook @api(version="2.0") - def update(self, workbook_item): + def update(self, workbook_item: WorkbookItem) -> WorkbookItem: if not workbook_item.id: error = "Workbook item missing ID. Workbook must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -125,7 +127,7 @@ def update_conn(self, *args, **kwargs): # Update workbook_connection @api(version="2.3") - def update_connection(self, workbook_item, connection_item): + def update_connection(self, workbook_item: WorkbookItem, connection_item: ConnectionItem) -> ConnectionItem: url = "{0}/{1}/connections/{2}".format(self.baseurl, workbook_item.id, connection_item.id) update_req = RequestFactory.Connection.update_req(connection_item) server_response = self.put_request(url, update_req) From 973237d9983430a8e05e9986431ea230740cbb67 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 17 Feb 2021 23:13:36 -0600 Subject: [PATCH 22/69] More workbook endpoint --- .../server/endpoint/workbooks_endpoint.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 306cbb72e..3c3a7ff99 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -19,6 +19,7 @@ from ..server import Server from ..request_options import RequestOptions from .. import DatasourceItem + from pathlib import Path # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -141,7 +142,8 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec @api(version="2.0") @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): + def download(self, workbook_id: str, filepath: Optional[Union[str, 'Path']] = None, + include_extract: bool = True, no_extract: Optional[bool] = None) -> str: if not workbook_id: error = "Workbook ID undefined." raise ValueError(error) @@ -169,7 +171,7 @@ def download(self, workbook_id, filepath=None, include_extract=True, no_extract= # Get all views of workbook @api(version="2.0") - def populate_views(self, workbook_item, usage=False): + def populate_views(self, workbook_item: WorkbookItem, usage: bool = False) -> None: if not workbook_item.id: error = "Workbook item missing ID. Workbook must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -180,7 +182,7 @@ def view_fetcher(): workbook_item._set_views(view_fetcher) logger.info('Populated views for workbook (ID: {0})'.format(workbook_item.id)) - def _get_views_for_workbook(self, workbook_item, usage): + def _get_views_for_workbook(self, workbook_item: WorkbookItem, usage: bool) -> List[ViewItem]: url = "{0}/{1}/views".format(self.baseurl, workbook_item.id) if usage: url += "?includeUsageStatistics=true" @@ -192,7 +194,7 @@ def _get_views_for_workbook(self, workbook_item, usage): # Get all connections of workbook @api(version="2.0") - def populate_connections(self, workbook_item): + def populate_connections(self, workbook_item: WorkbookItem) -> None: if not workbook_item.id: error = "Workbook item missing ID. Workbook must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -203,7 +205,8 @@ def connection_fetcher(): workbook_item._set_connections(connection_fetcher) logger.info('Populated connections for workbook (ID: {0})'.format(workbook_item.id)) - def _get_workbook_connections(self, workbook_item, req_options=None): + def _get_workbook_connections(self, workbook_item: WorkbookItem, + req_options: 'RequestOptions' = None) -> List[ConnectionItem]: url = "{0}/{1}/connections".format(self.baseurl, workbook_item.id) server_response = self.get_request(url, req_options) connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace) @@ -211,7 +214,7 @@ def _get_workbook_connections(self, workbook_item, req_options=None): # Get the pdf of the entire workbook if its tabs are enabled, pdf of the default view if its tabs are disabled @api(version="3.4") - def populate_pdf(self, workbook_item, req_options=None): + def populate_pdf(self, workbook_item: WorkbookItem, req_options: 'RequestOptions' = None) -> None: if not workbook_item.id: error = "Workbook item missing ID." raise MissingRequiredFieldError(error) @@ -230,7 +233,7 @@ def _get_wb_pdf(self, workbook_item, req_options): # Get preview image of workbook @api(version="2.0") - def populate_preview_image(self, workbook_item): + def populate_preview_image(self, workbook_item: WorkbookItem) -> None: if not workbook_item.id: error = "Workbook item missing ID. Workbook must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -248,7 +251,7 @@ def _get_wb_preview_image(self, workbook_item): return preview_image @api(version='2.0') - def populate_permissions(self, item): + def populate_permissions(self, item: WorkbookItem) -> None: self._permissions.populate(item) @api(version='2.0') From 9a8150650620318cdfc5cfea7a062a343536859a Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 17 Feb 2021 23:39:14 -0600 Subject: [PATCH 23/69] Type hinting. Hinting error in publish --- .../server/endpoint/workbooks_endpoint.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 3c3a7ff99..145078650 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -13,13 +13,13 @@ import cgi from contextlib import closing -from typing import Dict, List, Mapping, Optional, Tuple, TYPE_CHECKING, Union +from typing import Any, Dict, List, IO, Mapping, Optional, Sequence, Tuple, TYPE_CHECKING, Union if TYPE_CHECKING: from ..server import Server from ..request_options import RequestOptions from .. import DatasourceItem - from pathlib import Path + from ...models.connection_credentials import ConnectionCredentials # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -142,7 +142,7 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec @api(version="2.0") @parameter_added_in(no_extract='2.5') @parameter_added_in(include_extract='2.5') - def download(self, workbook_id: str, filepath: Optional[Union[str, 'Path']] = None, + def download(self, workbook_id: str, filepath: Optional[Union['os.PathLike[Any]', IO[Any]]] = None, include_extract: bool = True, no_extract: Optional[bool] = None) -> str: if not workbook_id: error = "Workbook ID undefined." @@ -262,15 +262,17 @@ def update_permissions(self, resource, rules): def delete_permission(self, item, capability_item): return self._permissions.delete(item, capability_item) + #TODO: Fix file type hint # 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') 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: WorkbookItem, file: Union['os.PathLike[Any]', IO[Any], str], mode: str, + connection_credentials: Optional['ConnectionCredentials'] = None, + connections: Optional[Sequence[ConnectionItem]] = None, as_job: bool = False, + hidden_views: Optional[Sequence[str]] = None, skip_connection_check: bool = False + ) -> Union[JobItem, WorkbookItem]: if connection_credentials is not None: import warnings From cd3342d8bec7045fb4c7f749521b63da9b9c7276 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Thu, 18 Feb 2021 08:43:55 -0600 Subject: [PATCH 24/69] Formatting --- tableauserverclient/server/endpoint/workbooks_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 145078650..0d2ba987a 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -262,7 +262,7 @@ def update_permissions(self, resource, rules): def delete_permission(self, item, capability_item): return self._permissions.delete(item, capability_item) - #TODO: Fix file type hint + # TODO: Fix file type hint # Publishes workbook. Chunking method if file over 64MB @api(version="2.0") @parameter_added_in(as_job='3.0') From cc5e9dcafbdea006adb770dbfb856f3e1faddcff Mon Sep 17 00:00:00 2001 From: jorwoods Date: Thu, 18 Feb 2021 08:45:41 -0600 Subject: [PATCH 25/69] Stop testing 3.5 --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a0917c7b6..52e53129d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 From 8870a07b1004db1ba077f075679ae00460ea2f3c Mon Sep 17 00:00:00 2001 From: jorwoods Date: Sun, 21 Feb 2021 17:32:17 -0600 Subject: [PATCH 26/69] Remove failing typecheck --- .pre-commit-config.yaml | 1 - tableauserverclient/server/endpoint/workbooks_endpoint.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7ce52e210..297301d5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - - id: check-yaml - id: end-of-file-fixer files: '.*\.py' - id: trailing-whitespace diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 0d2ba987a..02b4d3035 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -268,7 +268,7 @@ def delete_permission(self, item, capability_item): @parameter_added_in(as_job='3.0') @parameter_added_in(connections='2.8') def publish( - self, workbook_item: WorkbookItem, file: Union['os.PathLike[Any]', IO[Any], str], mode: str, + self, workbook_item: WorkbookItem, file, mode: str, connection_credentials: Optional['ConnectionCredentials'] = None, connections: Optional[Sequence[ConnectionItem]] = None, as_job: bool = False, hidden_views: Optional[Sequence[str]] = None, skip_connection_check: bool = False From a8e182fcc1d6738e978230946417f47c550a6b13 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Sun, 21 Feb 2021 17:46:46 -0600 Subject: [PATCH 27/69] Python 3.5 is deprecated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e2c30704a..c485e71d7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Use the Tableau Server Client (TSC) library to increase your productivity as you * Create users and groups. * Query projects, sites, and more. -This repository contains Python source code and sample files. Python versions 3.5 and up are supported. +This repository contains Python source code and sample files. Python versions 3.6 and up are supported. For more information on installing and using TSC, see the documentation: From 7ff1ad4fc76748fecbbda2ddcdd99f75100b5321 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 24 Feb 2021 08:34:14 -0600 Subject: [PATCH 28/69] Remove misc check from pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 297301d5c..f234adf66 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: stages: [commit] - id: mypy name: mypy - entry: mypy tableauserverclient samples test + entry: mypy --show-error-codes --disable-error-code misc tableauserverclient language: python 'types': [python] pass_filenames: false From 505f0e2d00d38d78f00f3314b5b9e153e8e23a9e Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 24 Feb 2021 18:56:15 -0600 Subject: [PATCH 29/69] Remove ignore comments --- tableauserverclient/_version.py | 4 +- tableauserverclient/models/column_item.py | 2 +- .../models/connection_credentials.py | 4 +- tableauserverclient/models/data_alert_item.py | 6 +- tableauserverclient/models/database_item.py | 8 +-- tableauserverclient/models/datasource_item.py | 14 ++--- tableauserverclient/models/flow_item.py | 2 +- tableauserverclient/models/group_item.py | 6 +- tableauserverclient/models/interval_item.py | 10 +-- tableauserverclient/models/project_item.py | 4 +- tableauserverclient/models/schedule_item.py | 10 +-- tableauserverclient/models/site_item.py | 62 +++++++++---------- .../models/subscription_item.py | 8 +-- tableauserverclient/models/table_item.py | 4 +- tableauserverclient/models/user_item.py | 6 +- tableauserverclient/models/workbook_item.py | 12 ++-- tableauserverclient/server/request_options.py | 6 +- test/test_regression_tests.py | 2 +- 18 files changed, 85 insertions(+), 85 deletions(-) diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 2afee59f0..5efcb7782 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -1,4 +1,4 @@ - +# type: ignore # 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 @@ -52,7 +52,7 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} # type: ignore +LONG_VERSION_PY = {} HANDLERS = {} diff --git a/tableauserverclient/models/column_item.py b/tableauserverclient/models/column_item.py index d5edc8245..9bf198220 100644 --- a/tableauserverclient/models/column_item.py +++ b/tableauserverclient/models/column_item.py @@ -17,7 +17,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index 47787a6df..c883a515a 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -19,7 +19,7 @@ def __init__(self, name, password, embed=True, oauth=False): def embed(self): return self._embed - @embed.setter # type: ignore + @embed.setter @property_is_boolean def embed(self, value): self._embed = value @@ -28,7 +28,7 @@ def embed(self, value): def oauth(self): return self._oauth - @oauth.setter # type: ignore + @oauth.setter @property_is_boolean def oauth(self, value): self._oauth = value diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index c1ba48d6d..559050b4b 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -43,7 +43,7 @@ def id(self): def subject(self): return self._subject - @subject.setter # type: ignore + @subject.setter @property_not_empty def subject(self, value): self._subject = value @@ -52,7 +52,7 @@ def subject(self, value): def frequency(self): return self._frequency - @frequency.setter # type: ignore + @frequency.setter @property_is_enum(Frequency) def frequency(self, value): self._frequency = value @@ -61,7 +61,7 @@ def frequency(self, value): def public(self): return self._public - @public.setter # type: ignore + @public.setter @property_is_boolean def public(self, value): self._public = value diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index 9467406f3..8f9ad091d 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -40,7 +40,7 @@ def __init__(self, name, description=None, content_permissions=None): def content_permissions(self): return self._content_permissions - @content_permissions.setter # type: ignore + @content_permissions.setter @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -67,7 +67,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value @@ -76,7 +76,7 @@ def name(self, value): def description(self): return self._description - @description.setter # type: ignore + @description.setter def description(self, value): self._description = value @@ -88,7 +88,7 @@ def embedded(self): def certified(self): return self._certified - @certified.setter # type: ignore + @certified.setter @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 0da3f45b2..429681ce5 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -38,7 +38,7 @@ def __init__(self, project_id: str, name: str = None) -> None: self.description = None self.name = name self.owner_id = None - self.project_id = project_id # type: ignore + self.project_id = project_id self.tags: Set = set() self._permissions = None @@ -49,7 +49,7 @@ def __init__(self, project_id: str, name: str = None) -> None: def ask_data_enablement(self) -> Optional['DatasourceItem.AskDataEnablement']: return self._ask_data_enablement - @ask_data_enablement.setter # type: ignore + @ask_data_enablement.setter @property_is_enum(AskDataEnablement) def ask_data_enablement(self, value: Optional['DatasourceItem.AskDataEnablement']): self._ask_data_enablement = value @@ -80,7 +80,7 @@ def created_at(self) -> Optional['datetime.datetime']: def certified(self) -> Optional[bool]: return self._certified - @certified.setter # type: ignore + @certified.setter @property_not_nullable @property_is_boolean def certified(self, value: Optional[bool]): @@ -90,7 +90,7 @@ def certified(self, value: Optional[bool]): def certification_note(self) -> Optional[str]: return self._certification_note - @certification_note.setter # type: ignore + @certification_note.setter def certification_note(self, value: Optional[str]): self._certification_note = value @@ -98,7 +98,7 @@ def certification_note(self, value: Optional[str]): def encrypt_extracts(self): return self._encrypt_extracts - @encrypt_extracts.setter # type: ignore + @encrypt_extracts.setter @property_is_boolean def encrypt_extracts(self, value: Optional[bool]): self._encrypt_extracts = value @@ -115,7 +115,7 @@ def id(self) -> Optional[str]: def project_id(self) -> str: return self._project_id - @project_id.setter # type: ignore + @project_id.setter @property_not_nullable def project_id(self, value: str): self._project_id = value @@ -136,7 +136,7 @@ def updated_at(self) -> Optional['datetime.datetime']: def use_remote_query_agent(self) -> Optional[bool]: return self._use_remote_query_agent - @use_remote_query_agent.setter # type: ignore + @use_remote_query_agent.setter @property_is_boolean def use_remote_query_agent(self, value: bool): self._use_remote_query_agent = value diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index 8660b32c4..c978d8175 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -53,7 +53,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter # type: ignore + @project_id.setter @property_not_nullable def project_id(self, value): self._project_id = value diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index 5bab2b5db..af9465dfb 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -37,7 +37,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value @@ -46,7 +46,7 @@ def name(self, value): def license_mode(self): return self._license_mode - @license_mode.setter # type: ignore + @license_mode.setter @property_is_enum(LicenseMode) def license_mode(self, value): self._license_mode = value @@ -55,7 +55,7 @@ def license_mode(self, value): def minimum_site_role(self): return self._minimum_site_role - @minimum_site_role.setter # type: ignore + @minimum_site_role.setter @property_is_enum(UserItem.Roles) def minimum_site_role(self, value): self._minimum_site_role = value diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index e5e841a46..cbc148e88 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -40,7 +40,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -50,7 +50,7 @@ def start_time(self, value): def end_time(self): return self._end_time - @end_time.setter # type: ignore + @end_time.setter @property_is_valid_time @property_not_nullable def end_time(self, value): @@ -95,7 +95,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -115,7 +115,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -149,7 +149,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index ddfc20766..f7226f054 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -28,7 +28,7 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N def content_permissions(self): return self._content_permissions - @content_permissions.setter # type: ignore + @content_permissions.setter @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -69,7 +69,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index 74b9ef199..c93ffe922 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -49,7 +49,7 @@ def end_schedule_at(self): def execution_order(self): return self._execution_order - @execution_order.setter # type: ignore + @execution_order.setter @property_is_enum(ExecutionOrder) def execution_order(self, value): self._execution_order = value @@ -62,7 +62,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_nullable def name(self, value): self._name = value @@ -75,7 +75,7 @@ def next_run_at(self): def priority(self): return self._priority - @priority.setter # type: ignore + @priority.setter @property_is_int(range=(1, 100)) def priority(self, value): self._priority = value @@ -84,7 +84,7 @@ def priority(self, value): def schedule_type(self): return self._schedule_type - @schedule_type.setter # type: ignore + @schedule_type.setter @property_is_enum(Type) @property_not_nullable def schedule_type(self, value): @@ -94,7 +94,7 @@ def schedule_type(self, value): def state(self): return self._state - @state.setter # type: ignore + @state.setter @property_is_enum(State) def state(self, value): self._state = value diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index c3af6c7ed..f562289ce 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -87,7 +87,7 @@ def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_ def admin_mode(self): return self._admin_mode - @admin_mode.setter # type: ignore + @admin_mode.setter @property_is_enum(AdminMode) def admin_mode(self, value): self._admin_mode = value @@ -96,7 +96,7 @@ def admin_mode(self, value): def content_url(self): return self._content_url - @content_url.setter # type: ignore + @content_url.setter @property_not_nullable @property_matches(VALID_CONTENT_URL_RE, "content_url can contain only letters, numbers, dashes, and underscores") def content_url(self, value): @@ -106,7 +106,7 @@ def content_url(self, value): def disable_subscriptions(self): return self._disable_subscriptions - @disable_subscriptions.setter # type: ignore + @disable_subscriptions.setter @property_is_boolean def disable_subscriptions(self, value): self._disable_subscriptions = value @@ -119,7 +119,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value @@ -132,7 +132,7 @@ def num_users(self): def revision_history_enabled(self): return self._revision_history_enabled - @revision_history_enabled.setter # type: ignore + @revision_history_enabled.setter @property_is_boolean def revision_history_enabled(self, value): self._revision_history_enabled = value @@ -141,7 +141,7 @@ def revision_history_enabled(self, value): def revision_limit(self): return self._revision_limit - @revision_limit.setter # type: ignore + @revision_limit.setter @property_is_int((2, 10000), allowed=[-1]) def revision_limit(self, value): self._revision_limit = value @@ -150,7 +150,7 @@ def revision_limit(self, value): def state(self): return self._state - @state.setter # type: ignore + @state.setter @property_is_enum(State) def state(self, value): self._state = value @@ -167,7 +167,7 @@ def storage(self): def subscribe_others_enabled(self): return self._subscribe_others_enabled - @subscribe_others_enabled.setter # type: ignore + @subscribe_others_enabled.setter @property_is_boolean def subscribe_others_enabled(self, value): self._subscribe_others_enabled = value @@ -192,7 +192,7 @@ def cataloging_enabled(self, value): def flows_enabled(self): return self._flows_enabled - @flows_enabled.setter # type: ignore + @flows_enabled.setter @property_is_boolean def flows_enabled(self, value): self._flows_enabled = value @@ -204,7 +204,7 @@ def is_default(self): def editing_flows_enabled(self): return self._editing_flows_enabled - @editing_flows_enabled.setter # type: ignore + @editing_flows_enabled.setter @property_is_boolean def editing_flows_enabled(self, value): self._editing_flows_enabled = value @@ -213,7 +213,7 @@ def editing_flows_enabled(self, value): def scheduling_flows_enabled(self): return self._scheduling_flows_enabled - @scheduling_flows_enabled.setter # type: ignore + @scheduling_flows_enabled.setter @property_is_boolean def scheduling_flows_enabled(self, value): self._scheduling_flows_enabled = value @@ -222,7 +222,7 @@ def scheduling_flows_enabled(self, value): def allow_subscription_attachments(self): return self._allow_subscription_attachments - @allow_subscription_attachments.setter # type: ignore + @allow_subscription_attachments.setter @property_is_boolean def allow_subscription_attachments(self, value): self._allow_subscription_attachments = value @@ -231,7 +231,7 @@ def allow_subscription_attachments(self, value): def guest_access_enabled(self): return self._guest_access_enabled - @guest_access_enabled.setter # type: ignore + @guest_access_enabled.setter @property_is_boolean def guest_access_enabled(self, value): self._guest_access_enabled = value @@ -240,7 +240,7 @@ def guest_access_enabled(self, value): def cache_warmup_enabled(self): return self._cache_warmup_enabled - @cache_warmup_enabled.setter # type: ignore + @cache_warmup_enabled.setter @property_is_boolean def cache_warmup_enabled(self, value): self._cache_warmup_enabled = value @@ -249,7 +249,7 @@ def cache_warmup_enabled(self, value): def commenting_enabled(self): return self._commenting_enabled - @commenting_enabled.setter # type: ignore + @commenting_enabled.setter @property_is_boolean def commenting_enabled(self, value): self._commenting_enabled = value @@ -266,7 +266,7 @@ def extract_encryption_mode(self, value): def request_access_enabled(self): return self._request_access_enabled - @request_access_enabled.setter # type: ignore + @request_access_enabled.setter @property_is_boolean def request_access_enabled(self, value): self._request_access_enabled = value @@ -275,7 +275,7 @@ def request_access_enabled(self, value): def run_now_enabled(self): return self._run_now_enabled - @run_now_enabled.setter # type: ignore + @run_now_enabled.setter @property_is_boolean def run_now_enabled(self, value): self._run_now_enabled = value @@ -308,7 +308,7 @@ def tier_viewer_capacity(self, value): def data_alerts_enabled(self): return self._data_alerts_enabled - @data_alerts_enabled.setter # type: ignore + @data_alerts_enabled.setter @property_is_boolean def data_alerts_enabled(self, value): self._data_alerts_enabled = value @@ -317,7 +317,7 @@ def data_alerts_enabled(self, value): def commenting_mentions_enabled(self): return self._commenting_mentions_enabled - @commenting_mentions_enabled.setter # type: ignore + @commenting_mentions_enabled.setter @property_is_boolean def commenting_mentions_enabled(self, value): self._commenting_mentions_enabled = value @@ -326,7 +326,7 @@ def commenting_mentions_enabled(self, value): def catalog_obfuscation_enabled(self): return self._catalog_obfuscation_enabled - @catalog_obfuscation_enabled.setter # type: ignore + @catalog_obfuscation_enabled.setter @property_is_boolean def catalog_obfuscation_enabled(self, value): self._catalog_obfuscation_enabled = value @@ -335,7 +335,7 @@ def catalog_obfuscation_enabled(self, value): def flow_auto_save_enabled(self): return self._flow_auto_save_enabled - @flow_auto_save_enabled.setter # type: ignore + @flow_auto_save_enabled.setter @property_is_boolean def flow_auto_save_enabled(self, value): self._flow_auto_save_enabled = value @@ -344,7 +344,7 @@ def flow_auto_save_enabled(self, value): def web_extraction_enabled(self): return self._web_extraction_enabled - @web_extraction_enabled.setter # type: ignore + @web_extraction_enabled.setter @property_is_boolean def web_extraction_enabled(self, value): self._web_extraction_enabled = value @@ -353,7 +353,7 @@ def web_extraction_enabled(self, value): def metrics_content_type_enabled(self): return self._metrics_content_type_enabled - @metrics_content_type_enabled.setter # type: ignore + @metrics_content_type_enabled.setter @property_is_boolean def metrics_content_type_enabled(self, value): self._metrics_content_type_enabled = value @@ -362,7 +362,7 @@ def metrics_content_type_enabled(self, value): def notify_site_admins_on_throttle(self): return self._notify_site_admins_on_throttle - @notify_site_admins_on_throttle.setter # type: ignore + @notify_site_admins_on_throttle.setter @property_is_boolean def notify_site_admins_on_throttle(self, value): self._notify_site_admins_on_throttle = value @@ -371,7 +371,7 @@ def notify_site_admins_on_throttle(self, value): def authoring_enabled(self): return self._authoring_enabled - @authoring_enabled.setter # type: ignore + @authoring_enabled.setter @property_is_boolean def authoring_enabled(self, value): self._authoring_enabled = value @@ -380,7 +380,7 @@ def authoring_enabled(self, value): def custom_subscription_email_enabled(self): return self._custom_subscription_email_enabled - @custom_subscription_email_enabled.setter # type: ignore + @custom_subscription_email_enabled.setter @property_is_boolean def custom_subscription_email_enabled(self, value): self._custom_subscription_email_enabled = value @@ -397,7 +397,7 @@ def custom_subscription_email(self, value): def custom_subscription_footer_enabled(self): return self._custom_subscription_footer_enabled - @custom_subscription_footer_enabled.setter # type: ignore + @custom_subscription_footer_enabled.setter @property_is_boolean def custom_subscription_footer_enabled(self, value): self._custom_subscription_footer_enabled = value @@ -422,7 +422,7 @@ def ask_data_mode(self, value): def named_sharing_enabled(self): return self._named_sharing_enabled - @named_sharing_enabled.setter # type: ignore + @named_sharing_enabled.setter @property_is_boolean def named_sharing_enabled(self, value): self._named_sharing_enabled = value @@ -431,7 +431,7 @@ def named_sharing_enabled(self, value): def mobile_biometrics_enabled(self): return self._mobile_biometrics_enabled - @mobile_biometrics_enabled.setter # type: ignore + @mobile_biometrics_enabled.setter @property_is_boolean def mobile_biometrics_enabled(self, value): self._mobile_biometrics_enabled = value @@ -440,7 +440,7 @@ def mobile_biometrics_enabled(self, value): def sheet_image_enabled(self): return self._sheet_image_enabled - @sheet_image_enabled.setter # type: ignore + @sheet_image_enabled.setter @property_is_boolean def sheet_image_enabled(self, value): self._sheet_image_enabled = value @@ -449,7 +449,7 @@ def sheet_image_enabled(self, value): def derived_permissions_enabled(self): return self._derived_permissions_enabled - @derived_permissions_enabled.setter # type: ignore + @derived_permissions_enabled.setter @property_is_boolean def derived_permissions_enabled(self, value): self._derived_permissions_enabled = value diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index 60886b1fd..cdcc468a1 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -35,7 +35,7 @@ def id(self): def attach_image(self): return self._attach_image - @attach_image.setter # type: ignore + @attach_image.setter @property_is_boolean def attach_image(self, value): self._attach_image = value @@ -44,7 +44,7 @@ def attach_image(self, value): def attach_pdf(self): return self._attach_pdf - @attach_pdf.setter # type: ignore + @attach_pdf.setter @property_is_boolean def attach_pdf(self, value): self._attach_pdf = value @@ -53,7 +53,7 @@ def attach_pdf(self, value): def send_if_view_empty(self): return self._send_if_view_empty - @send_if_view_empty.setter # type: ignore + @send_if_view_empty.setter @property_is_boolean def send_if_view_empty(self, value): self._send_if_view_empty = value @@ -62,7 +62,7 @@ def send_if_view_empty(self, value): def suspended(self): return self._suspended - @suspended.setter # type: ignore + @suspended.setter @property_is_boolean def suspended(self, value): self._suspended = value diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index eb79483ce..2f00ef2b7 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -33,7 +33,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value @@ -50,7 +50,7 @@ def description(self, value): def certified(self): return self._certified - @certified.setter # type: ignore + @certified.setter @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index 332c18cfc..b5a05b0d1 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -54,7 +54,7 @@ def __init__(self, name=None, site_role=None, auth_setting=None): def auth_setting(self): return self._auth_setting - @auth_setting.setter # type: ignore + @auth_setting.setter @property_is_enum(Auth) def auth_setting(self, value): self._auth_setting = value @@ -79,7 +79,7 @@ def last_login(self): def name(self): return self._name - @name.setter # type: ignore + @name.setter @property_not_empty def name(self, value): self._name = value @@ -88,7 +88,7 @@ def name(self, value): def site_role(self): return self._site_role - @site_role.setter # type: ignore + @site_role.setter @property_not_nullable @property_is_enum(Roles) def site_role(self, value): diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 9df4af344..4190ab16c 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -32,10 +32,10 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self.name = name self._description = None self.owner_id = None - self.project_id = project_id # type: ignore - self.show_tabs = show_tabs # type: ignore + self.project_id = project_id + self.show_tabs = show_tabs self.tags: set = set() - self.data_acceleration_config = {'acceleration_enabled': None, # type: ignore + self.data_acceleration_config = {'acceleration_enabled': None, 'accelerate_now': None, 'last_updated_at': None, 'acceleration_status': None} @@ -95,7 +95,7 @@ def preview_image(self): def project_id(self) -> Optional[str]: return self._project_id - @project_id.setter # type: ignore + @project_id.setter @property_not_nullable def project_id(self, value: str): self._project_id = value @@ -108,7 +108,7 @@ def project_name(self) -> Optional[str]: def show_tabs(self) -> bool: return self._show_tabs - @show_tabs.setter # type: ignore + @show_tabs.setter @property_is_boolean def show_tabs(self, value: bool): self._show_tabs = value @@ -142,7 +142,7 @@ def views(self) -> List[ViewItem]: def data_acceleration_config(self): return self._data_acceleration_config - @data_acceleration_config.setter # type: ignore + @data_acceleration_config.setter @property_is_data_acceleration_config def data_acceleration_config(self, value): self._data_acceleration_config = value diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 74bb7987a..22d0a4ef0 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -124,7 +124,7 @@ def __init__(self, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore + @max_age.setter @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -152,7 +152,7 @@ def __init__(self, imageresolution=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore + @max_age.setter @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -197,7 +197,7 @@ def __init__(self, page_type=None, orientation=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore + @max_age.setter @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index 68826ccf6..281f3fbca 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -3,7 +3,7 @@ try: from unittest import mock except ImportError: - import mock # type: ignore + import mock import tableauserverclient.server.request_factory as factory from tableauserverclient.server.endpoint import Endpoint From 24574cb6b4e61699c2167fde3b0e75d4c9b014e8 Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 24 Feb 2021 19:02:45 -0600 Subject: [PATCH 30/69] Clean up function signatures --- tableauserverclient/models/workbook_item.py | 2 +- .../server/endpoint/datasources_endpoint.py | 37 +++++++++++++------ .../server/endpoint/workbooks_endpoint.py | 28 ++++++++++---- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 4190ab16c..81c0837f8 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -212,7 +212,7 @@ def _set_values(self, id, name, content_url, webpage_url, created_at, descriptio self.data_acceleration_config = data_acceleration_config @classmethod - def from_response(cls, resp: str, ns: Optional[Dict[Union[str, str], Union[str, str]]]) -> List['WorkbookItem']: + def from_response(cls, resp: str, ns: Dict[str, str]) -> List['WorkbookItem']: all_workbook_items = list() parsed_response = ET.fromstring(resp) all_workbook_xml = parsed_response.findall('.//t:workbook', namespaces=ns) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index a69d683d1..3e318146b 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -16,7 +16,9 @@ from contextlib import closing from pathlib import Path -from typing import List, Dict, Sequence, Mapping, Optional, Tuple, Union, TYPE_CHECKING, Any, BinaryIO +from typing import List, Dict, Sequence, Mapping, Optional, Tuple, Union, TYPE_CHECKING, Any, TextIO, TypeVar + +PathOrFile = TypeVar('PathOrFile', os.PathLike[Any], str, TextIO) # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -36,7 +38,7 @@ def __init__(self, parent_srv: 'Server') -> None: self._resource_tagger = _ResourceTagger(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) - return + return None @property def baseurl(self) -> str: @@ -96,8 +98,13 @@ def delete(self, datasource_id: str) -> None: @api(version="2.0") @parameter_added_in(no_extract='2.5') @parameter_added_in(include_extract='2.5') - def download(self, datasource_id: str, filepath: str = None, include_extract: bool = True, - no_extract: Optional[bool] = None) -> str: + def download( + self, + datasource_id: str, + filepath: str = None, + include_extract: bool = True, + no_extract: Optional[bool] = None + ) -> str: if not datasource_id: error = "Datasource ID undefined." raise ValueError(error) @@ -184,19 +191,25 @@ def delete_extract(self, datasource_item: DatasourceItem) -> None: @api(version="2.0") @parameter_added_in(connections="2.8") @parameter_added_in(as_job='3.0') - def publish(self, datasource_item: DatasourceItem, file: Union[str, Path, BinaryIO], mode: str, - connection_credentials: ConnectionCredentials = None, connections: Sequence[ConnectionItem] = None, - as_job: bool = False) -> Union[DatasourceItem, JobItem]: + def publish( + self, + datasource_item: DatasourceItem, + file: PathOrFile, + mode: str, + connection_credentials: ConnectionCredentials = None, + connections: Sequence[ConnectionItem] = None, + as_job: bool = False + ) -> Union[DatasourceItem, JobItem]: try: - if not os.path.isfile(file): # type: ignore + if not os.path.isfile(file): error = "File path does not lead to an existing file." raise IOError(error) - filename = os.path.basename(file) # type: ignore + filename = os.path.basename(file) file_extension = os.path.splitext(filename)[1][1:] - file_size = os.path.getsize(file) # type: ignore + file_size = os.path.getsize(file) # If name is not defined, grab the name from the file to publish if not datasource_item.name: @@ -247,10 +260,10 @@ def publish(self, datasource_item: DatasourceItem, file: Union[str, Path, Binary logger.info('Publishing {0} to server'.format(filename)) try: - with open(file, 'rb') as f: # type: ignore + with open(file, 'rb') as f: file_contents = f.read() except TypeError: - file_contents = file.read() # type: ignore + file_contents = file.read() xml_request, content_type = RequestFactory.Datasource.publish_req(datasource_item, filename, diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 02b4d3035..acf02e9e7 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -75,8 +75,13 @@ def refresh(self, workbook_id: str) -> JobItem: # create one or more extracts on 1 workbook, optionally encrypted @api(version='3.5') - def create_extract(self, workbook_item: WorkbookItem, encrypt: bool = False, includeAll: bool = True, - datasources: Optional[List['DatasourceItem']] = None) -> JobItem: + def create_extract( + self, + workbook_item: WorkbookItem, + encrypt: bool = False, + includeAll: bool = True, + datasources: Optional[List['DatasourceItem']] = None + ) -> JobItem: id_ = getattr(workbook_item, 'id', workbook_item) url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt) @@ -142,8 +147,13 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec @api(version="2.0") @parameter_added_in(no_extract='2.5') @parameter_added_in(include_extract='2.5') - def download(self, workbook_id: str, filepath: Optional[Union['os.PathLike[Any]', IO[Any]]] = None, - include_extract: bool = True, no_extract: Optional[bool] = None) -> str: + def download( + self, + workbook_id: str, + filepath: Optional[Union['os.PathLike[Any]', IO[Any]]] = None, + include_extract: bool = True, + no_extract: Optional[bool] = None + ) -> str: if not workbook_id: error = "Workbook ID undefined." raise ValueError(error) @@ -268,10 +278,14 @@ def delete_permission(self, item, capability_item): @parameter_added_in(as_job='3.0') @parameter_added_in(connections='2.8') def publish( - self, workbook_item: WorkbookItem, file, mode: str, + self, + workbook_item: WorkbookItem, + file, mode: str, connection_credentials: Optional['ConnectionCredentials'] = None, - connections: Optional[Sequence[ConnectionItem]] = None, as_job: bool = False, - hidden_views: Optional[Sequence[str]] = None, skip_connection_check: bool = False + connections: Optional[Sequence[ConnectionItem]] = None, + as_job: bool = False, + hidden_views: Optional[Sequence[str]] = None, + skip_connection_check: bool = False ) -> Union[JobItem, WorkbookItem]: if connection_credentials is not None: From 4fa4c54421dab0753dcf01f06a2fe9bf5f3ea52b Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 24 Feb 2021 19:05:31 -0600 Subject: [PATCH 31/69] Fix PathLike call --- tableauserverclient/server/endpoint/datasources_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 3e318146b..28be3e9c2 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -18,7 +18,7 @@ from pathlib import Path from typing import List, Dict, Sequence, Mapping, Optional, Tuple, Union, TYPE_CHECKING, Any, TextIO, TypeVar -PathOrFile = TypeVar('PathOrFile', os.PathLike[Any], str, TextIO) +PathOrFile = TypeVar('PathOrFile', os.PathLike, str, TextIO) # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB From 662145bbc3032131b9c92185f440ee6468fdd21a Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 24 Feb 2021 19:07:07 -0600 Subject: [PATCH 32/69] Explicitly return None --- tableauserverclient/models/datasource_item.py | 2 +- tableauserverclient/models/workbook_item.py | 2 +- tableauserverclient/server/endpoint/workbooks_endpoint.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 429681ce5..ad7c13cbf 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -43,7 +43,7 @@ def __init__(self, project_id: str, name: str = None) -> None: self._permissions = None - return + return None @property def ask_data_enablement(self) -> Optional['DatasourceItem.AskDataEnablement']: diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 81c0837f8..cbd9085ae 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -41,7 +41,7 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - 'acceleration_status': None} self._permissions = None - return + return None @property def connections(self) -> List['ConnectionItem']: diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index acf02e9e7..e3c3324c0 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -35,7 +35,7 @@ def __init__(self, parent_srv: 'Server') -> None: self._resource_tagger = _ResourceTagger(parent_srv) self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl) - return + return None @property def baseurl(self) -> str: From f2e5a7212ce9ca4f18feffd64e1d67b2c15c011b Mon Sep 17 00:00:00 2001 From: jorwoods Date: Wed, 24 Feb 2021 19:12:18 -0600 Subject: [PATCH 33/69] Move mode to separate line --- tableauserverclient/server/endpoint/workbooks_endpoint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index e3c3324c0..97a1ed8fc 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -280,7 +280,8 @@ def delete_permission(self, item, capability_item): def publish( self, workbook_item: WorkbookItem, - file, mode: str, + file, + mode: str, connection_credentials: Optional['ConnectionCredentials'] = None, connections: Optional[Sequence[ConnectionItem]] = None, as_job: bool = False, From b146fdd6c857b2f048cdb4b8c5a902cc0923846c Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 9 Mar 2021 22:28:02 -0600 Subject: [PATCH 34/69] Remove bad TypeVar --- tableauserverclient/_version.py | 3 +-- tableauserverclient/server/endpoint/datasources_endpoint.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 5efcb7782..85370e301 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -1,4 +1,3 @@ -# type: ignore # 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 @@ -52,7 +51,7 @@ class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" -LONG_VERSION_PY = {} +LONG_VERSION_PY = {} # type: ignore HANDLERS = {} diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 28be3e9c2..faf7d30a5 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -18,7 +18,6 @@ from pathlib import Path from typing import List, Dict, Sequence, Mapping, Optional, Tuple, Union, TYPE_CHECKING, Any, TextIO, TypeVar -PathOrFile = TypeVar('PathOrFile', os.PathLike, str, TextIO) # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -194,7 +193,7 @@ def delete_extract(self, datasource_item: DatasourceItem) -> None: def publish( self, datasource_item: DatasourceItem, - file: PathOrFile, + file, mode: str, connection_credentials: ConnectionCredentials = None, connections: Sequence[ConnectionItem] = None, From 6041ce1cf7533d369f9d9e9e959724b96a27b2c3 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Fri, 30 Apr 2021 08:38:48 -0500 Subject: [PATCH 35/69] Run black formatter --- .pre-commit-config.yaml | 6 +-- tableauserverclient/_version.py | 23 ++------- tableauserverclient/models/database_item.py | 4 +- tableauserverclient/models/flow_item.py | 47 ++----------------- tableauserverclient/models/job_item.py | 35 ++------------ tableauserverclient/models/project_item.py | 21 ++------- .../models/property_decorators.py | 7 +-- tableauserverclient/models/site_item.py | 3 +- tableauserverclient/models/tableau_auth.py | 6 +-- tableauserverclient/models/task_item.py | 9 +--- tableauserverclient/models/user_item.py | 33 ++----------- .../server/endpoint/datasources_endpoint.py | 17 ++----- .../server/endpoint/endpoint.py | 13 +---- .../server/endpoint/groups_endpoint.py | 5 +- .../server/endpoint/permissions_endpoint.py | 7 +-- .../server/endpoint/schedules_endpoint.py | 13 +---- .../server/endpoint/tables_endpoint.py | 5 +- .../server/endpoint/tasks_endpoint.py | 10 +--- .../server/endpoint/users_endpoint.py | 5 +- .../server/endpoint/workbooks_endpoint.py | 26 ++++------ tableauserverclient/server/request_factory.py | 27 ++--------- 21 files changed, 53 insertions(+), 269 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f234adf66..4ecfb5bbd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,9 +21,9 @@ repos: - requests-mock - pytest stages: [commit] - - id: pycodestyle - name: pycodestyle - entry: pycodestyle tableauserverclient samples test + - id: black + name: black + entry: black --line-length 120 tableauserverclient language: python 'types': [python] pass_filenames: false diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index d47374097..e083a46e5 100644 --- a/tableauserverclient/_version.py +++ b/tableauserverclient/_version.py @@ -77,11 +77,7 @@ 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: @@ -247,17 +243,7 @@ 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: @@ -299,10 +285,7 @@ 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/database_item.py b/tableauserverclient/models/database_item.py index 514bf92bc..425842082 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -243,9 +243,7 @@ def _set_tables(self, tables): def _set_default_permissions(self, permissions, content_type): setattr( - self, - "_default_{content}_permissions".format(content=content_type), - permissions, + self, "_default_{content}_permissions".format(content=content_type), permissions, ) def _set_data_quality_warnings(self, dqw): diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index d1387f368..c4546933e 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -99,44 +99,14 @@ 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) + (_, _, _, _, _, 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, + 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 @@ -181,16 +151,7 @@ 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 diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index 7a3a50861..084a45f08 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -92,17 +92,7 @@ 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): @@ -114,16 +104,7 @@ 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 @@ -195,14 +176,4 @@ 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/project_item.py b/tableauserverclient/models/project_item.py index 101a898e3..f699f2eba 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -90,13 +90,7 @@ 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 @@ -119,9 +113,7 @@ def _set_permissions(self, permissions): def _set_default_permissions(self, permissions, content_type): setattr( - self, - "_default_{content}_permissions".format(content=content_type), - permissions, + self, "_default_{content}_permissions".format(content=content_type), permissions, ) @classmethod @@ -131,14 +123,7 @@ 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 b3466dea7..6782eec6e 100644 --- a/tableauserverclient/models/property_decorators.py +++ b/tableauserverclient/models/property_decorators.py @@ -152,12 +152,7 @@ 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/site_item.py b/tableauserverclient/models/site_item.py index 7fb1d116e..15e207860 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -139,8 +139,7 @@ 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", + 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/tableau_auth.py b/tableauserverclient/models/tableau_auth.py index 01787de4e..c731e0514 100644 --- a/tableauserverclient/models/tableau_auth.py +++ b/tableauserverclient/models/tableau_auth.py @@ -19,8 +19,7 @@ def site(self): import warnings warnings.warn( - "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", - DeprecationWarning, + "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", DeprecationWarning, ) return self.site_id @@ -29,8 +28,7 @@ def site(self, value): import warnings warnings.warn( - "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", - DeprecationWarning, + "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", DeprecationWarning, ) self.site_id = value diff --git a/tableauserverclient/models/task_item.py b/tableauserverclient/models/task_item.py index 65709d5c9..bbd30dec7 100644 --- a/tableauserverclient/models/task_item.py +++ b/tableauserverclient/models/task_item.py @@ -81,14 +81,7 @@ 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 65abf4cb6..b42c981e9 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -132,31 +132,12 @@ 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 @@ -196,15 +177,7 @@ 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 diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 180b52dea..6686c07e8 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -105,11 +105,7 @@ def delete(self, datasource_id: str) -> None: @parameter_added_in(no_extract="2.5") @parameter_added_in(include_extract="2.5") def download( - self, - datasource_id: str, - filepath: str = None, - include_extract: bool = True, - no_extract: Optional[bool] = None + self, datasource_id: str, filepath: str = None, include_extract: bool = True, no_extract: Optional[bool] = None ) -> str: if not datasource_id: error = "Datasource ID undefined." @@ -120,8 +116,7 @@ def download( import warnings warnings.warn( - "no_extract is deprecated, use include_extract instead.", - DeprecationWarning, + "no_extract is deprecated, use include_extract instead.", DeprecationWarning, ) include_extract = not no_extract @@ -208,7 +203,7 @@ def publish( mode: str, connection_credentials: ConnectionCredentials = None, connections: Sequence[ConnectionItem] = None, - as_job: bool = False + as_job: bool = False, ) -> Union[DatasourceItem, JobItem]: try: @@ -276,11 +271,7 @@ def publish( 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 diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 6672a0336..6a11e4fbf 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -42,13 +42,7 @@ def _safe_to_log(server_response): return server_response.content def _make_request( - self, - method, - url, - content=None, - auth_token=None, - content_type=None, - parameters=None, + self, method, url, content=None, auth_token=None, content_type=None, parameters=None, ): parameters = parameters or {} parameters.update(self.parent_srv.http_options) @@ -105,10 +99,7 @@ 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/groups_endpoint.py b/tableauserverclient/server/endpoint/groups_endpoint.py index b771e56d8..35f8ca5a1 100644 --- a/tableauserverclient/server/endpoint/groups_endpoint.py +++ b/tableauserverclient/server/endpoint/groups_endpoint.py @@ -33,10 +33,7 @@ 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 7035837f4..d5b6dbbb9 100644 --- a/tableauserverclient/server/endpoint/permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/permissions_endpoint.py @@ -46,12 +46,7 @@ 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 d582dca26..e6e38aaf8 100644 --- a/tableauserverclient/server/endpoint/schedules_endpoint.py +++ b/tableauserverclient/server/endpoint/schedules_endpoint.py @@ -66,11 +66,7 @@ def create(self, schedule_item): @api(version="2.8") def add_to_schedule( - self, - schedule_id, - workbook=None, - datasource=None, - task_type=TaskItem.Type.ExtractRefresh, + self, schedule_id, workbook=None, datasource=None, task_type=TaskItem.Type.ExtractRefresh, ): def add_to(resource, type_, req_factory): id_ = resource.id @@ -85,12 +81,7 @@ 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/tables_endpoint.py b/tableauserverclient/server/endpoint/tables_endpoint.py index ac53484db..a85260df8 100644 --- a/tableauserverclient/server/endpoint/tables_endpoint.py +++ b/tableauserverclient/server/endpoint/tables_endpoint.py @@ -72,10 +72,7 @@ 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)) diff --git a/tableauserverclient/server/endpoint/tasks_endpoint.py b/tableauserverclient/server/endpoint/tasks_endpoint.py index aaa5069c3..98f573f82 100644 --- a/tableauserverclient/server/endpoint/tasks_endpoint.py +++ b/tableauserverclient/server/endpoint/tasks_endpoint.py @@ -42,11 +42,7 @@ 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] @@ -57,9 +53,7 @@ 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 6adbf92fb..275072fda 100644 --- a/tableauserverclient/server/endpoint/users_endpoint.py +++ b/tableauserverclient/server/endpoint/users_endpoint.py @@ -112,10 +112,7 @@ 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 baffd50d9..90bff1c2b 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -83,7 +83,7 @@ def create_extract( workbook_item: WorkbookItem, encrypt: bool = False, includeAll: bool = True, - datasources: Optional[List["DatasourceItem"]] = None + datasources: Optional[List["DatasourceItem"]] = None, ) -> JobItem: id_ = getattr(workbook_item, "id", workbook_item) url = "{0}/{1}/createExtract?encrypt={2}".format(self.baseurl, id_, encrypt) @@ -157,7 +157,7 @@ def download( workbook_id: str, filepath: Optional[Union["os.PathLike[Any]", IO[Any]]] = None, include_extract: bool = True, - no_extract: Optional[bool] = None + no_extract: Optional[bool] = None, ) -> str: if not workbook_id: error = "Workbook ID undefined." @@ -168,8 +168,7 @@ def download( import warnings warnings.warn( - "no_extract is deprecated, use include_extract instead.", - DeprecationWarning, + "no_extract is deprecated, use include_extract instead.", DeprecationWarning, ) include_extract = not no_extract @@ -207,9 +206,7 @@ def _get_views_for_workbook(self, workbook_item: WorkbookItem, usage: bool) -> L 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, + server_response.content, self.parent_srv.namespace, workbook_id=workbook_item.id, ) return views @@ -226,8 +223,9 @@ def connection_fetcher(): workbook_item._set_connections(connection_fetcher) logger.info("Populated connections for workbook (ID: {0})".format(workbook_item.id)) - def _get_workbook_connections(self, workbook_item: WorkbookItem, - req_options: "RequestOptions" = None) -> List[ConnectionItem]: + def _get_workbook_connections( + self, workbook_item: WorkbookItem, req_options: "RequestOptions" = None + ) -> List[ConnectionItem]: url = "{0}/{1}/connections".format(self.baseurl, workbook_item.id) server_response = self.get_request(url, req_options) connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace) @@ -297,15 +295,14 @@ def publish( connections: Optional[Sequence[ConnectionItem]] = None, as_job: bool = False, hidden_views: Optional[Sequence[str]] = None, - skip_connection_check: bool = False + skip_connection_check: bool = False, ) -> Union[JobItem, WorkbookItem]: if connection_credentials is not None: import warnings warnings.warn( - "connection_credentials is being deprecated. Use connections instead", - DeprecationWarning, + "connection_credentials is being deprecated. Use connections instead", DeprecationWarning, ) try: @@ -372,10 +369,7 @@ 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 c03a4fadc..70f4e6f33 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -178,12 +178,7 @@ 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, + self, datasource_item, filename, file_contents, connection_credentials=None, connections=None, ): xml_request = self._generate_xml(datasource_item, connection_credentials, connections) @@ -795,11 +790,7 @@ def add_req(self, user_item): class WorkbookRequest(object): def _generate_xml( - self, - workbook_item, - connection_credentials=None, - connections=None, - hidden_views=None, + self, workbook_item, connection_credentials=None, connections=None, hidden_views=None, ): xml_request = ET.Element("tsRequest") workbook_element = ET.SubElement(xml_request, "workbook") @@ -854,13 +845,7 @@ 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, @@ -876,11 +861,7 @@ def publish_req( return _add_multipart(parts) def publish_req_chunked( - self, - workbook_item, - connection_credentials=None, - connections=None, - hidden_views=None, + self, workbook_item, connection_credentials=None, connections=None, hidden_views=None, ): xml_request = self._generate_xml( workbook_item, From e54f7fea66741614058a17b2babec9212a2ed7f9 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Fri, 30 Apr 2021 08:42:20 -0500 Subject: [PATCH 36/69] Fix mypy errors --- tableauserverclient/models/dqw_item.py | 8 ----- tableauserverclient/models/workbook_item.py | 12 ++++++-- tableauserverclient/server/request_factory.py | 30 ------------------- 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/tableauserverclient/models/dqw_item.py b/tableauserverclient/models/dqw_item.py index a7f8ec9cb..3285e3022 100644 --- a/tableauserverclient/models/dqw_item.py +++ b/tableauserverclient/models/dqw_item.py @@ -80,14 +80,6 @@ def severe(self): 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 diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 1dedad6eb..d79a92bbc 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -11,7 +11,15 @@ from ..datetime_helpers import parse_datetime import copy -from typing import Dict, List, Mapping, Optional, TYPE_CHECKING, Union +from typing import ( + Dict, + List, + Mapping, + Optional, + Set, + TYPE_CHECKING, + Union, +) if TYPE_CHECKING: from .connection_item import ConnectionItem @@ -38,7 +46,7 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self.owner_id = None self.project_id = project_id self.show_tabs = show_tabs - self.tags = set() + self.tags: Set[str] = set() self.data_acceleration_config = { "acceleration_enabled": None, "accelerate_now": None, diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 70f4e6f33..ca4221734 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -225,36 +225,6 @@ def update_req(self, database_item): 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): """ From 059af6dd124cf5f1b80313a778ceabba66baccd5 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Fri, 30 Apr 2021 08:49:43 -0500 Subject: [PATCH 37/69] Add mypy into test install --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8b374f0ce..d641df12e 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ # This makes work easier for offline installs or low bandwidth machines needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) pytest_runner = ['pytest-runner'] if needs_pytest else [] -test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0'] +test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy'] setup( name='tableauserverclient', From dcd1fb69f6343284be846ac06fb9907c61f61b0f Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Fri, 30 Apr 2021 08:54:04 -0500 Subject: [PATCH 38/69] Remove redundant mypy install --- .github/workflows/run-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3fc33341f..d5468b053 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -23,7 +23,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -e .[test] - pip install mypy - name: Test with pytest run: | From eb9649bab39bd39b93ad28534247ba92db72930b Mon Sep 17 00:00:00 2001 From: jorwoods Date: Fri, 30 Apr 2021 15:36:01 -0500 Subject: [PATCH 39/69] Type hint publish file --- tableauserverclient/_version.py | 23 +++++++- tableauserverclient/models/database_item.py | 4 +- tableauserverclient/models/flow_item.py | 47 +++++++++++++-- tableauserverclient/models/job_item.py | 35 +++++++++++- tableauserverclient/models/project_item.py | 21 ++++++- .../models/property_decorators.py | 7 ++- tableauserverclient/models/site_item.py | 3 +- tableauserverclient/models/tableau_auth.py | 6 +- tableauserverclient/models/task_item.py | 9 ++- tableauserverclient/models/user_item.py | 33 ++++++++++- .../server/endpoint/datasources_endpoint.py | 9 ++- .../server/endpoint/endpoint.py | 13 ++++- .../server/endpoint/groups_endpoint.py | 5 +- .../server/endpoint/permissions_endpoint.py | 9 ++- .../server/endpoint/schedules_endpoint.py | 13 ++++- .../server/endpoint/server_info_endpoint.py | 2 +- .../server/endpoint/tables_endpoint.py | 5 +- .../server/endpoint/tasks_endpoint.py | 10 +++- .../server/endpoint/users_endpoint.py | 5 +- .../server/endpoint/workbooks_endpoint.py | 57 +++++++++++++++---- tableauserverclient/server/request_factory.py | 27 +++++++-- tableauserverclient/server/request_options.py | 2 +- 22 files changed, 294 insertions(+), 51 deletions(-) diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index e083a46e5..d47374097 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/database_item.py b/tableauserverclient/models/database_item.py index 425842082..514bf92bc 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -243,7 +243,9 @@ def _set_tables(self, tables): def _set_default_permissions(self, permissions, content_type): setattr( - self, "_default_{content}_permissions".format(content=content_type), permissions, + self, + "_default_{content}_permissions".format(content=content_type), + permissions, ) def _set_data_quality_warnings(self, dqw): diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index c4546933e..d1387f368 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -99,14 +99,44 @@ 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) + ( + _, + _, + _, + _, + _, + 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, + 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 @@ -151,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 diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index 084a45f08..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/project_item.py b/tableauserverclient/models/project_item.py index f699f2eba..101a898e3 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 @@ -113,7 +119,9 @@ def _set_permissions(self, permissions): def _set_default_permissions(self, permissions, content_type): setattr( - self, "_default_{content}_permissions".format(content=content_type), permissions, + self, + "_default_{content}_permissions".format(content=content_type), + permissions, ) @classmethod @@ -123,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 6782eec6e..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/site_item.py b/tableauserverclient/models/site_item.py index 15e207860..7fb1d116e 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -139,7 +139,8 @@ 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", + 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/tableau_auth.py b/tableauserverclient/models/tableau_auth.py index c731e0514..01787de4e 100644 --- a/tableauserverclient/models/tableau_auth.py +++ b/tableauserverclient/models/tableau_auth.py @@ -19,7 +19,8 @@ def site(self): import warnings warnings.warn( - "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", DeprecationWarning, + "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", + DeprecationWarning, ) return self.site_id @@ -28,7 +29,8 @@ def site(self, value): import warnings warnings.warn( - "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", DeprecationWarning, + "TableauAuth.site is deprecated, use TableauAuth.site_id instead.", + DeprecationWarning, ) self.site_id = value diff --git a/tableauserverclient/models/task_item.py b/tableauserverclient/models/task_item.py index bbd30dec7..65709d5c9 100644 --- a/tableauserverclient/models/task_item.py +++ b/tableauserverclient/models/task_item.py @@ -81,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 b42c981e9..65abf4cb6 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -132,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 @@ -177,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 diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 6686c07e8..8176d77c1 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -116,7 +116,8 @@ def download( import warnings warnings.warn( - "no_extract is deprecated, use include_extract instead.", DeprecationWarning, + "no_extract is deprecated, use include_extract instead.", + DeprecationWarning, ) include_extract = not no_extract @@ -271,7 +272,11 @@ def publish( 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 diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 6a11e4fbf..6672a0336 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -42,7 +42,13 @@ def _safe_to_log(server_response): return server_response.content def _make_request( - self, method, url, content=None, auth_token=None, content_type=None, parameters=None, + self, + method, + url, + content=None, + auth_token=None, + content_type=None, + parameters=None, ): parameters = parameters or {} parameters.update(self.parent_srv.http_options) @@ -99,7 +105,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/groups_endpoint.py b/tableauserverclient/server/endpoint/groups_endpoint.py index 35f8ca5a1..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 d5b6dbbb9..5013a0bef 100644 --- a/tableauserverclient/server/endpoint/permissions_endpoint.py +++ b/tableauserverclient/server/endpoint/permissions_endpoint.py @@ -44,9 +44,14 @@ def delete(self, resource, rules): for rule in rules: for capability, mode in rule.capabilities.items(): - " /permissions/groups/group-id/capability-name/capability-mode" + "/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 e6e38aaf8..d582dca26 100644 --- a/tableauserverclient/server/endpoint/schedules_endpoint.py +++ b/tableauserverclient/server/endpoint/schedules_endpoint.py @@ -66,7 +66,11 @@ def create(self, schedule_item): @api(version="2.8") def add_to_schedule( - self, schedule_id, workbook=None, datasource=None, task_type=TaskItem.Type.ExtractRefresh, + self, + schedule_id, + workbook=None, + datasource=None, + task_type=TaskItem.Type.ExtractRefresh, ): def add_to(resource, type_, req_factory): id_ = resource.id @@ -81,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 8776477d3..ca3715fca 100644 --- a/tableauserverclient/server/endpoint/server_info_endpoint.py +++ b/tableauserverclient/server/endpoint/server_info_endpoint.py @@ -17,7 +17,7 @@ def baseurl(self): @api(version="2.4") def get(self): - """ Retrieve the server info for the server. This is an unauthenticated call """ + """Retrieve the server info for the server. This is an unauthenticated call""" try: server_response = self.get_unauthenticated_request(self.baseurl) except ServerResponseError as e: diff --git a/tableauserverclient/server/endpoint/tables_endpoint.py b/tableauserverclient/server/endpoint/tables_endpoint.py index a85260df8..ac53484db 100644 --- a/tableauserverclient/server/endpoint/tables_endpoint.py +++ b/tableauserverclient/server/endpoint/tables_endpoint.py @@ -72,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)) diff --git a/tableauserverclient/server/endpoint/tasks_endpoint.py b/tableauserverclient/server/endpoint/tasks_endpoint.py index 98f573f82..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 275072fda..6adbf92fb 100644 --- a/tableauserverclient/server/endpoint/users_endpoint.py +++ b/tableauserverclient/server/endpoint/users_endpoint.py @@ -112,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 90bff1c2b..a06aeb62a 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -13,12 +13,29 @@ ) import os +from pathlib import Path +import io import logging import copy import cgi from contextlib import closing -from typing import Any, Dict, List, IO, Mapping, Optional, Sequence, Tuple, TYPE_CHECKING, Union +from typing import ( + Any, + Dict, + List, + IO, + Mapping, + Optional, + runtime_checkable, + Sequence, + Tuple, + TYPE_CHECKING, + TypeVar, + Union, + Protocol, + Generic, +) if TYPE_CHECKING: from ..server import Server @@ -33,6 +50,16 @@ logger = logging.getLogger("tableau.endpoint.workbooks") +PathOrFile = Union[os.PathLike, IO[Any]] + +T = TypeVar("T") + + +@runtime_checkable +class Readable(Protocol, Generic[T]): + def read(self) -> T: + ... + class Workbooks(QuerysetEndpoint): def __init__(self, parent_srv: "Server") -> None: @@ -168,7 +195,8 @@ def download( import warnings warnings.warn( - "no_extract is deprecated, use include_extract instead.", DeprecationWarning, + "no_extract is deprecated, use include_extract instead.", + DeprecationWarning, ) include_extract = not no_extract @@ -206,7 +234,9 @@ def _get_views_for_workbook(self, workbook_item: WorkbookItem, usage: bool) -> L 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, + server_response.content, + self.parent_srv.namespace, + workbook_id=workbook_item.id, ) return views @@ -289,7 +319,7 @@ def delete_permission(self, item, capability_item): def publish( self, workbook_item: WorkbookItem, - file, + file: PathOrFile, mode: str, connection_credentials: Optional["ConnectionCredentials"] = None, connections: Optional[Sequence[ConnectionItem]] = None, @@ -302,10 +332,11 @@ def publish( import warnings warnings.warn( - "connection_credentials is being deprecated. Use connections instead", DeprecationWarning, + "connection_credentials is being deprecated. Use connections instead", + DeprecationWarning, ) - try: + if isinstance(file, (str, Path)): # Expect file to be a filepath if not os.path.isfile(file): error = "File path does not lead to an existing file." @@ -322,7 +353,7 @@ def publish( error = "Only {} files can be published as workbooks.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) - except TypeError: + else: # Expect file to be a file object file_size = get_file_object_size(file) @@ -369,18 +400,24 @@ 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)) - try: + if isinstance(file, (str, Path)): with open(file, "rb") as f: file_contents = f.read() - except TypeError: + elif isinstance(file, Readable): file_contents = file.read() + else: + raise TypeError() + conn_creds = connection_credentials xml_request, content_type = RequestFactory.Workbook.publish_req( workbook_item, diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index ca4221734..309c545ca 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -178,7 +178,12 @@ 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, + self, + datasource_item, + filename, + file_contents, + connection_credentials=None, + connections=None, ): xml_request = self._generate_xml(datasource_item, connection_credentials, connections) @@ -760,7 +765,11 @@ def add_req(self, user_item): class WorkbookRequest(object): def _generate_xml( - self, workbook_item, connection_credentials=None, connections=None, hidden_views=None, + self, + workbook_item, + connection_credentials=None, + connections=None, + hidden_views=None, ): xml_request = ET.Element("tsRequest") workbook_element = ET.SubElement(xml_request, "workbook") @@ -815,7 +824,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, @@ -831,7 +846,11 @@ def publish_req( return _add_multipart(parts) def publish_req_chunked( - self, workbook_item, connection_credentials=None, connections=None, hidden_views=None, + self, + workbook_item, + connection_credentials=None, + connections=None, + hidden_views=None, ): xml_request = self._generate_xml( workbook_item, diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 23d10b3d6..4ebf1e4d6 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -98,7 +98,7 @@ def get_query_params(self): class _FilterOptionsBase(RequestOptionsBase): - """ Provide a basic implementation of adding view filters to the url """ + """Provide a basic implementation of adding view filters to the url""" def __init__(self): self.view_filters = [] From 444137738d141610cccfb23bca69786e67804feb Mon Sep 17 00:00:00 2001 From: jorwoods Date: Fri, 30 Apr 2021 15:42:49 -0500 Subject: [PATCH 40/69] Type hint datasource publish --- .../server/endpoint/datasources_endpoint.py | 44 +++++++++++++++---- .../server/endpoint/workbooks_endpoint.py | 3 +- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 8176d77c1..1c1345058 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -22,8 +22,22 @@ from contextlib import closing from pathlib import Path -from typing import List, Dict, Sequence, Mapping, Optional, Tuple, Union, TYPE_CHECKING, Any, TextIO, TypeVar - +from typing import ( + Any, + Dict, + List, + IO, + Mapping, + Optional, + runtime_checkable, + Sequence, + Tuple, + TYPE_CHECKING, + TypeVar, + Union, + Protocol, + Generic, +) # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -36,6 +50,15 @@ from ..server import Server from ...models import PermissionsRule +PathOrFile = Union[os.PathLike, IO[Any]] +T = TypeVar("T") + + +@runtime_checkable +class Readable(Protocol, Generic[T]): + def read(self) -> T: + ... + class Datasources(QuerysetEndpoint): def __init__(self, parent_srv: "Server") -> None: @@ -105,7 +128,11 @@ def delete(self, datasource_id: str) -> None: @parameter_added_in(no_extract="2.5") @parameter_added_in(include_extract="2.5") def download( - self, datasource_id: str, filepath: str = None, include_extract: bool = True, no_extract: Optional[bool] = None + self, + datasource_id: str, + filepath: os.PathLike = None, + include_extract: bool = True, + no_extract: Optional[bool] = None, ) -> str: if not datasource_id: error = "Datasource ID undefined." @@ -200,15 +227,14 @@ def delete_extract(self, datasource_item: DatasourceItem) -> None: def publish( self, datasource_item: DatasourceItem, - file, + file: PathOrFile, mode: str, connection_credentials: ConnectionCredentials = None, connections: Sequence[ConnectionItem] = None, as_job: bool = False, ) -> Union[DatasourceItem, JobItem]: - try: - + if isinstance(file, (Path, str)): if not os.path.isfile(file): error = "File path does not lead to an existing file." raise IOError(error) @@ -224,7 +250,7 @@ def publish( error = "Only {} files can be published as datasources.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) - except TypeError: + else: if not datasource_item.name: error = "Datasource item must have a name when passing a file object" @@ -265,10 +291,10 @@ def publish( else: logger.info("Publishing {0} to server".format(filename)) - try: + if isinstance(file, (Path, str)): with open(file, "rb") as f: file_contents = f.read() - except TypeError: + elif isinstance(file, Readable): file_contents = file.read() xml_request, content_type = RequestFactory.Datasource.publish_req( diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index a06aeb62a..010128623 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -51,7 +51,6 @@ logger = logging.getLogger("tableau.endpoint.workbooks") PathOrFile = Union[os.PathLike, IO[Any]] - T = TypeVar("T") @@ -182,7 +181,7 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec def download( self, workbook_id: str, - filepath: Optional[Union["os.PathLike[Any]", IO[Any]]] = None, + filepath: os.PathLike = None, include_extract: bool = True, no_extract: Optional[bool] = None, ) -> str: From 0ce6636d595568e0b94647066a3fdf8cadd6c42c Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 16:34:09 -0400 Subject: [PATCH 41/69] Split Union return type into overloads --- .../server/endpoint/workbooks_endpoint.py | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 010128623..fb5480306 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -25,8 +25,10 @@ Dict, List, IO, + Literal, Mapping, Optional, + overload, runtime_checkable, Sequence, Tuple, @@ -312,6 +314,36 @@ def delete_permission(self, item, capability_item): # TODO: Fix file type hint # Publishes workbook. Chunking method if file over 64MB + @overload + def publish( + self, + workbook_item: WorkbookItem, + file: PathOrFile, + mode: str, + connection_credentials: Optional["ConnectionCredentials"], + connections: Optional[Sequence[ConnectionItem]], + as_job: Literal[True], + hidden_views: Optional[Sequence[str]], + skip_connection_check: bool, + ) -> JobItem: + ... + + + @overload + def publish( + self, + workbook_item: WorkbookItem, + file: PathOrFile, + mode: str, + connection_credentials: Optional["ConnectionCredentials"], + connections: Optional[Sequence[ConnectionItem]], + as_job: Literal[False], + hidden_views: Optional[Sequence[str]], + skip_connection_check: bool, + ) -> WorkbookItem: + ... + + @api(version="2.0") @parameter_added_in(as_job="3.0") @parameter_added_in(connections="2.8") @@ -325,7 +357,7 @@ def publish( as_job: bool = False, hidden_views: Optional[Sequence[str]] = None, skip_connection_check: bool = False, - ) -> Union[JobItem, WorkbookItem]: + ): if connection_credentials is not None: import warnings From 83327c429093395660ba97584d4aa3b02b1a6b1c Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 21:25:32 -0400 Subject: [PATCH 42/69] Type hinting on datasource item --- .../server/endpoint/datasources_endpoint.py | 53 ++++++++++++------- .../server/endpoint/workbooks_endpoint.py | 19 +------ 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 1c1345058..a51cc0f96 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -15,6 +15,7 @@ from ...models.job_item import JobItem from ...models import ConnectionCredentials +import io import os import logging import copy @@ -23,20 +24,14 @@ from pathlib import Path from typing import ( - Any, - Dict, List, - IO, - Mapping, + Literal, Optional, - runtime_checkable, + overload, Sequence, Tuple, TYPE_CHECKING, - TypeVar, Union, - Protocol, - Generic, ) # The maximum size of a file that can be published in a single request is 64MB @@ -50,14 +45,7 @@ from ..server import Server from ...models import PermissionsRule -PathOrFile = Union[os.PathLike, IO[Any]] -T = TypeVar("T") - - -@runtime_checkable -class Readable(Protocol, Generic[T]): - def read(self) -> T: - ... +PathOrFile = Union[os.PathLike, io.BytesIO] class Datasources(QuerysetEndpoint): @@ -220,6 +208,32 @@ def delete_extract(self, datasource_item: DatasourceItem) -> None: empty_req = RequestFactory.Empty.empty_req() self.post_request(url, empty_req) + + @overload + def publish( + self, + datasource_item: DatasourceItem, + file: PathOrFile, + mode: str, + connection_credentials: ConnectionCredentials = None, + connections: Sequence[ConnectionItem] = None, + as_job: Literal[False] = False, + ) -> DatasourceItem: + ... + + + @overload + def publish( + self, + datasource_item: DatasourceItem, + file: PathOrFile, + mode: str, + connection_credentials: ConnectionCredentials, + connections: Sequence[ConnectionItem], + as_job: Literal[True], + ) -> JobItem: + ... + # Publish datasource @api(version="2.0") @parameter_added_in(connections="2.8") @@ -250,7 +264,7 @@ def publish( error = "Only {} files can be published as datasources.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) - else: + elif isinstance(file, io.BytesIO): if not datasource_item.name: error = "Datasource item must have a name when passing a file object" @@ -268,6 +282,9 @@ def publish( filename = "{}.{}".format(datasource_item.name, file_extension) file_size = get_file_object_size(file) + else: + raise TypeError() + if not mode or not hasattr(self.parent_srv.PublishMode, mode): error = "Invalid mode defined." raise ValueError(error) @@ -294,7 +311,7 @@ def publish( if isinstance(file, (Path, str)): with open(file, "rb") as f: file_contents = f.read() - elif isinstance(file, Readable): + elif isinstance(file, io.BytesIO): file_contents = file.read() xml_request, content_type = RequestFactory.Datasource.publish_req( diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index fb5480306..d316ad3e2 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -21,22 +21,14 @@ from contextlib import closing from typing import ( - Any, - Dict, List, - IO, Literal, - Mapping, Optional, overload, - runtime_checkable, Sequence, Tuple, TYPE_CHECKING, - TypeVar, Union, - Protocol, - Generic, ) if TYPE_CHECKING: @@ -52,14 +44,7 @@ logger = logging.getLogger("tableau.endpoint.workbooks") -PathOrFile = Union[os.PathLike, IO[Any]] -T = TypeVar("T") - - -@runtime_checkable -class Readable(Protocol, Generic[T]): - def read(self) -> T: - ... +PathOrFile = Union[os.PathLike, io.BytesIO] class Workbooks(QuerysetEndpoint): @@ -443,7 +428,7 @@ def publish( with open(file, "rb") as f: file_contents = f.read() - elif isinstance(file, Readable): + elif isinstance(file, io.BytesIO): file_contents = file.read() else: From eade29d028efeb7ea414dbae75b066306bc3a836 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 21:55:03 -0400 Subject: [PATCH 43/69] Fix type checking errors --- .../server/endpoint/datasources_endpoint.py | 12 +++++++++--- .../server/endpoint/workbooks_endpoint.py | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index f44aa9c05..0954118b0 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -31,9 +31,13 @@ Sequence, Tuple, TYPE_CHECKING, + TypeVar, Union, ) +io_types = (io.BytesIO, io.BufferedReader, io.TextIOWrapper) +T = TypeVar('T', *io_types) + # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -45,7 +49,7 @@ from ..server import Server from ...models import PermissionsRule -PathOrFile = Union[os.PathLike, io.BytesIO] +PathOrFile = Union[os.PathLike, T] class Datasources(QuerysetEndpoint): @@ -264,7 +268,7 @@ def publish( error = "Only {} files can be published as datasources.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) - elif isinstance(file, io.BytesIO): + elif isinstance(file, io_types): if not datasource_item.name: error = "Datasource item must have a name when passing a file object" @@ -311,8 +315,10 @@ def publish( if isinstance(file, (Path, str)): with open(file, "rb") as f: file_contents = f.read() - elif isinstance(file, io.BytesIO): + elif isinstance(file, io_types): file_contents = file.read() + else: + raise TypeError() xml_request, content_type = RequestFactory.Datasource.publish_req( datasource_item, diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 55f9b0149..7a2e2854b 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -43,7 +43,7 @@ logger = logging.getLogger("tableau.endpoint.workbooks") -PathOrFile = Union[os.PathLike, io.BytesIO] +PathOrFile = Union[os.PathLike, io.BytesIO, io.BufferedReader] class Workbooks(QuerysetEndpoint): @@ -427,7 +427,7 @@ def publish( with open(file, "rb") as f: file_contents = f.read() - elif isinstance(file, io.BytesIO): + elif isinstance(file, (io.BytesIO, io.BufferedReader)): file_contents = file.read() else: From d70b27bbdfe57e230b7e6f03be5bb56c491a4a9f Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 22:10:18 -0400 Subject: [PATCH 44/69] Make code compatible with 3.6 and 3.7 --- .../server/endpoint/datasources_endpoint.py | 27 --------------- .../server/endpoint/workbooks_endpoint.py | 33 ------------------- 2 files changed, 60 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 0954118b0..81b746123 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -25,9 +25,7 @@ from pathlib import Path from typing import ( List, - Literal, Optional, - overload, Sequence, Tuple, TYPE_CHECKING, @@ -212,31 +210,6 @@ def delete_extract(self, datasource_item: DatasourceItem) -> None: empty_req = RequestFactory.Empty.empty_req() self.post_request(url, empty_req) - - @overload - def publish( - self, - datasource_item: DatasourceItem, - file: PathOrFile, - mode: str, - connection_credentials: ConnectionCredentials = None, - connections: Sequence[ConnectionItem] = None, - as_job: Literal[False] = False, - ) -> DatasourceItem: - ... - - - @overload - def publish( - self, - datasource_item: DatasourceItem, - file: PathOrFile, - mode: str, - connection_credentials: ConnectionCredentials, - connections: Sequence[ConnectionItem], - as_job: Literal[True], - ) -> JobItem: - ... # Publish datasource @api(version="2.0") diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 7a2e2854b..cd0032a70 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -21,9 +21,7 @@ from typing import ( List, - Literal, Optional, - overload, Sequence, Tuple, TYPE_CHECKING, @@ -296,37 +294,6 @@ def update_permissions(self, resource, rules): def delete_permission(self, item, capability_item): return self._permissions.delete(item, capability_item) - # TODO: Fix file type hint - # Publishes workbook. Chunking method if file over 64MB - @overload - def publish( - self, - workbook_item: WorkbookItem, - file: PathOrFile, - mode: str, - connection_credentials: Optional["ConnectionCredentials"], - connections: Optional[Sequence[ConnectionItem]], - as_job: Literal[True], - hidden_views: Optional[Sequence[str]], - skip_connection_check: bool, - ) -> JobItem: - ... - - - @overload - def publish( - self, - workbook_item: WorkbookItem, - file: PathOrFile, - mode: str, - connection_credentials: Optional["ConnectionCredentials"], - connections: Optional[Sequence[ConnectionItem]], - as_job: Literal[False], - hidden_views: Optional[Sequence[str]], - skip_connection_check: bool, - ) -> WorkbookItem: - ... - @api(version="2.0") @parameter_added_in(as_job="3.0") From f1fb183de994f6997e9d29430e095c919975b4eb Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 22:23:21 -0400 Subject: [PATCH 45/69] Fix mypy errors --- tableauserverclient/server/endpoint/datasources_endpoint.py | 5 ++--- test/test_datasource.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 81b746123..b5228e558 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -33,8 +33,7 @@ Union, ) -io_types = (io.BytesIO, io.BufferedReader, io.TextIOWrapper) -T = TypeVar('T', *io_types) +io_types = (io.BytesIO, io.BufferedReader) # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -47,7 +46,7 @@ from ..server import Server from ...models import PermissionsRule -PathOrFile = Union[os.PathLike, T] +PathOrFile = Union[os.PathLike, io.BufferedReader, io.BytesIO] class Datasources(QuerysetEndpoint): diff --git a/test/test_datasource.py b/test/test_datasource.py index 52a5eabe3..ae587e212 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -497,7 +497,7 @@ def test_publish_invalid_file_type(self): def test_publish_hyper_file_object_raises_exception(self): new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') - with open(asset('World Indicators.hyper')) as file_object: + with open(asset('World Indicators.hyper'), 'rb') as file_object: self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, file_object, self.server.PublishMode.Append) @@ -505,7 +505,7 @@ def test_publish_tde_file_object_raises_exception(self): 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: + with open(tds_asset, 'rb') as file_object: self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, file_object, self.server.PublishMode.Append) From 742b1c98d9e2d8623555c3274f09ebcd897f4866 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 22:42:34 -0400 Subject: [PATCH 46/69] Enable mypy --- .github/workflows/run-tests.yml | 3 +- tableauserverclient/models/column_item.py | 2 +- .../models/connection_credentials.py | 4 +- tableauserverclient/models/data_alert_item.py | 6 +- tableauserverclient/models/database_item.py | 6 +- tableauserverclient/models/datasource_item.py | 12 ++-- tableauserverclient/models/flow_item.py | 2 +- tableauserverclient/models/group_item.py | 6 +- tableauserverclient/models/interval_item.py | 10 +-- tableauserverclient/models/project_item.py | 4 +- tableauserverclient/models/schedule_item.py | 10 +-- tableauserverclient/models/site_item.py | 64 +++++++++---------- .../models/subscription_item.py | 8 +-- tableauserverclient/models/table_item.py | 4 +- tableauserverclient/models/user_item.py | 6 +- tableauserverclient/models/workbook_item.py | 12 ++-- tableauserverclient/server/request_factory.py | 4 +- tableauserverclient/server/request_options.py | 6 +- tableauserverclient/server/server.py | 2 +- 19 files changed, 85 insertions(+), 86 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6cb271661..abf16c0a4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -29,7 +29,6 @@ jobs: run: | pytest test - - name: Run Mypy but allow failures + - name: Run Mypy tests run: | mypy --show-error-codes --disable-error-code misc tableauserverclient - continue-on-error: true diff --git a/tableauserverclient/models/column_item.py b/tableauserverclient/models/column_item.py index a95d005ca..2116d9de0 100644 --- a/tableauserverclient/models/column_item.py +++ b/tableauserverclient/models/column_item.py @@ -17,7 +17,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index db65de0ad..010adf6c9 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -19,7 +19,7 @@ def __init__(self, name, password, embed=True, oauth=False): def embed(self): return self._embed - @embed.setter + @embed.setter # type: ignore[misc] @property_is_boolean def embed(self, value): self._embed = value @@ -28,7 +28,7 @@ def embed(self, value): def oauth(self): return self._oauth - @oauth.setter + @oauth.setter # type: ignore[misc] @property_is_boolean def oauth(self, value): self._oauth = value diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index a4d11ca5e..8e7955441 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -49,7 +49,7 @@ def id(self): def subject(self): return self._subject - @subject.setter + @subject.setter # type: ignore[misc] @property_not_empty def subject(self, value): self._subject = value @@ -58,7 +58,7 @@ def subject(self, value): def frequency(self): return self._frequency - @frequency.setter + @frequency.setter # type: ignore[misc] @property_is_enum(Frequency) def frequency(self, value): self._frequency = value @@ -67,7 +67,7 @@ def frequency(self, value): def public(self): return self._public - @public.setter + @public.setter # type: ignore[misc] @property_is_boolean def public(self, value): self._public = value diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index 514bf92bc..94d992584 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -53,7 +53,7 @@ def dqws(self): def content_permissions(self): return self._content_permissions - @content_permissions.setter + @content_permissions.setter # type: ignore[misc] @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -80,7 +80,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value @@ -101,7 +101,7 @@ def embedded(self): def certified(self): return self._certified - @certified.setter + @certified.setter # type: ignore[misc] @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 2d4f17e7f..9302910d3 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -43,7 +43,7 @@ def __init__(self, project_id: str, name: str = None) -> None: self.description = None self.name = name self.owner_id = None - self.project_id = project_id + self.project_id = project_id # type: ignore[misc] self.tags: Set = set() self._permissions = None @@ -55,7 +55,7 @@ def __init__(self, project_id: str, name: str = None) -> None: def ask_data_enablement(self) -> Optional["DatasourceItem.AskDataEnablement"]: return self._ask_data_enablement - @ask_data_enablement.setter + @ask_data_enablement.setter # type: ignore[misc] @property_is_enum(AskDataEnablement) def ask_data_enablement(self, value: Optional["DatasourceItem.AskDataEnablement"]): self._ask_data_enablement = value @@ -86,7 +86,7 @@ def created_at(self) -> Optional["datetime.datetime"]: def certified(self) -> Optional[bool]: return self._certified - @certified.setter + @certified.setter # type: ignore[misc] @property_not_nullable @property_is_boolean def certified(self, value: Optional[bool]): @@ -104,7 +104,7 @@ def certification_note(self, value: Optional[str]): def encrypt_extracts(self): return self._encrypt_extracts - @encrypt_extracts.setter + @encrypt_extracts.setter # type: ignore[misc] @property_is_boolean def encrypt_extracts(self, value: Optional[bool]): self._encrypt_extracts = value @@ -128,7 +128,7 @@ def id(self) -> Optional[str]: def project_id(self) -> str: return self._project_id - @project_id.setter + @project_id.setter # type: ignore[misc] @property_not_nullable def project_id(self, value: str): self._project_id = value @@ -157,7 +157,7 @@ def updated_at(self) -> Optional["datetime.datetime"]: def use_remote_query_agent(self) -> Optional[bool]: return self._use_remote_query_agent - @use_remote_query_agent.setter + @use_remote_query_agent.setter # type: ignore[misc] @property_is_boolean def use_remote_query_agent(self, value: bool): self._use_remote_query_agent = value diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index d1387f368..2e30610af 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -61,7 +61,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter + @project_id.setter # type: ignore[misc] @property_not_nullable def project_id(self, value): self._project_id = value diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index fdc06604b..73dea024d 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -37,7 +37,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value @@ -46,7 +46,7 @@ def name(self, value): def license_mode(self): return self._license_mode - @license_mode.setter + @license_mode.setter # type: ignore[misc] @property_is_enum(LicenseMode) def license_mode(self, value): self._license_mode = value @@ -55,7 +55,7 @@ def license_mode(self, value): def minimum_site_role(self): return self._minimum_site_role - @minimum_site_role.setter + @minimum_site_role.setter # type: ignore[misc] @property_is_enum(UserItem.Roles) def minimum_site_role(self, value): self._minimum_site_role = value diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index 320e01ef2..47cf8cea5 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -40,7 +40,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore[misc] @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -50,7 +50,7 @@ def start_time(self, value): def end_time(self): return self._end_time - @end_time.setter + @end_time.setter # type: ignore[misc] @property_is_valid_time @property_not_nullable def end_time(self, value): @@ -95,7 +95,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore[misc] @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -115,7 +115,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore[misc] @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -149,7 +149,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter + @start_time.setter # type: ignore[misc] @property_is_valid_time @property_not_nullable def start_time(self, value): diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index c6525a2ca..0781ccc7c 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -29,7 +29,7 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N def content_permissions(self): return self._content_permissions - @content_permissions.setter + @content_permissions.setter # type: ignore[misc] @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -70,7 +70,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index f8baf0749..a23bbc67f 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -59,7 +59,7 @@ def end_schedule_at(self): def execution_order(self): return self._execution_order - @execution_order.setter + @execution_order.setter # type: ignore[misc] @property_is_enum(ExecutionOrder) def execution_order(self, value): self._execution_order = value @@ -72,7 +72,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_nullable def name(self, value): self._name = value @@ -85,7 +85,7 @@ def next_run_at(self): def priority(self): return self._priority - @priority.setter + @priority.setter # type: ignore[misc] @property_is_int(range=(1, 100)) def priority(self, value): self._priority = value @@ -94,7 +94,7 @@ def priority(self, value): def schedule_type(self): return self._schedule_type - @schedule_type.setter + @schedule_type.setter # type: ignore[misc] @property_is_enum(Type) @property_not_nullable def schedule_type(self, value): @@ -104,7 +104,7 @@ def schedule_type(self, value): def state(self): return self._state - @state.setter + @state.setter # type: ignore[misc] @property_is_enum(State) def state(self, value): self._state = value diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index ab0211414..d40fbc5c7 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -127,7 +127,7 @@ def __init__( def admin_mode(self): return self._admin_mode - @admin_mode.setter + @admin_mode.setter # type: ignore[misc] @property_is_enum(AdminMode) def admin_mode(self, value): self._admin_mode = value @@ -136,7 +136,7 @@ def admin_mode(self, value): def content_url(self): return self._content_url - @content_url.setter + @content_url.setter # type: ignore[misc] @property_not_nullable @property_matches( VALID_CONTENT_URL_RE, @@ -149,7 +149,7 @@ def content_url(self, value): def disable_subscriptions(self): return self._disable_subscriptions - @disable_subscriptions.setter + @disable_subscriptions.setter # type: ignore[misc] @property_is_boolean def disable_subscriptions(self, value): self._disable_subscriptions = value @@ -162,7 +162,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value @@ -175,7 +175,7 @@ def num_users(self): def revision_history_enabled(self): return self._revision_history_enabled - @revision_history_enabled.setter + @revision_history_enabled.setter # type: ignore[misc] @property_is_boolean def revision_history_enabled(self, value): self._revision_history_enabled = value @@ -184,7 +184,7 @@ def revision_history_enabled(self, value): def revision_limit(self): return self._revision_limit - @revision_limit.setter + @revision_limit.setter # type: ignore[misc] @property_is_int((2, 10000), allowed=[-1]) def revision_limit(self, value): self._revision_limit = value @@ -193,7 +193,7 @@ def revision_limit(self, value): def state(self): return self._state - @state.setter + @state.setter # type: ignore[misc] @property_is_enum(State) def state(self, value): self._state = value @@ -210,7 +210,7 @@ def storage(self): def subscribe_others_enabled(self): return self._subscribe_others_enabled - @subscribe_others_enabled.setter + @subscribe_others_enabled.setter # type: ignore[misc] @property_is_boolean def subscribe_others_enabled(self, value): self._subscribe_others_enabled = value @@ -235,7 +235,7 @@ def cataloging_enabled(self, value): def flows_enabled(self): return self._flows_enabled - @flows_enabled.setter + @flows_enabled.setter # type: ignore[misc] @property_is_boolean def flows_enabled(self, value): self._flows_enabled = value @@ -247,7 +247,7 @@ def is_default(self): def editing_flows_enabled(self): return self._editing_flows_enabled - @editing_flows_enabled.setter + @editing_flows_enabled.setter # type: ignore[misc] @property_is_boolean def editing_flows_enabled(self, value): self._editing_flows_enabled = value @@ -256,7 +256,7 @@ def editing_flows_enabled(self, value): def scheduling_flows_enabled(self): return self._scheduling_flows_enabled - @scheduling_flows_enabled.setter + @scheduling_flows_enabled.setter # type: ignore[misc] @property_is_boolean def scheduling_flows_enabled(self, value): self._scheduling_flows_enabled = value @@ -265,7 +265,7 @@ def scheduling_flows_enabled(self, value): def allow_subscription_attachments(self): return self._allow_subscription_attachments - @allow_subscription_attachments.setter + @allow_subscription_attachments.setter # type: ignore[misc] @property_is_boolean def allow_subscription_attachments(self, value): self._allow_subscription_attachments = value @@ -274,7 +274,7 @@ def allow_subscription_attachments(self, value): def guest_access_enabled(self): return self._guest_access_enabled - @guest_access_enabled.setter + @guest_access_enabled.setter # type: ignore[misc] @property_is_boolean def guest_access_enabled(self, value): self._guest_access_enabled = value @@ -283,7 +283,7 @@ def guest_access_enabled(self, value): def cache_warmup_enabled(self): return self._cache_warmup_enabled - @cache_warmup_enabled.setter + @cache_warmup_enabled.setter # type: ignore[misc] @property_is_boolean def cache_warmup_enabled(self, value): self._cache_warmup_enabled = value @@ -292,7 +292,7 @@ def cache_warmup_enabled(self, value): def commenting_enabled(self): return self._commenting_enabled - @commenting_enabled.setter + @commenting_enabled.setter # type: ignore[misc] @property_is_boolean def commenting_enabled(self, value): self._commenting_enabled = value @@ -309,7 +309,7 @@ def extract_encryption_mode(self, value): def request_access_enabled(self): return self._request_access_enabled - @request_access_enabled.setter + @request_access_enabled.setter # type: ignore[misc] @property_is_boolean def request_access_enabled(self, value): self._request_access_enabled = value @@ -318,7 +318,7 @@ def request_access_enabled(self, value): def run_now_enabled(self): return self._run_now_enabled - @run_now_enabled.setter + @run_now_enabled.setter # type: ignore[misc] @property_is_boolean def run_now_enabled(self, value): self._run_now_enabled = value @@ -327,7 +327,7 @@ def run_now_enabled(self, value): def tier_explorer_capacity(self): return self._tier_explorer_capacity - @tier_explorer_capacity.setter + @tier_explorer_capacity.setter # type: ignore[misc] def tier_explorer_capacity(self, value): self._tier_explorer_capacity = value @@ -351,7 +351,7 @@ def tier_viewer_capacity(self, value): def data_alerts_enabled(self): return self._data_alerts_enabled - @data_alerts_enabled.setter + @data_alerts_enabled.setter # type: ignore[misc] @property_is_boolean def data_alerts_enabled(self, value): self._data_alerts_enabled = value @@ -360,7 +360,7 @@ def data_alerts_enabled(self, value): def commenting_mentions_enabled(self): return self._commenting_mentions_enabled - @commenting_mentions_enabled.setter + @commenting_mentions_enabled.setter # type: ignore[misc] @property_is_boolean def commenting_mentions_enabled(self, value): self._commenting_mentions_enabled = value @@ -369,7 +369,7 @@ def commenting_mentions_enabled(self, value): def catalog_obfuscation_enabled(self): return self._catalog_obfuscation_enabled - @catalog_obfuscation_enabled.setter + @catalog_obfuscation_enabled.setter # type: ignore[misc] @property_is_boolean def catalog_obfuscation_enabled(self, value): self._catalog_obfuscation_enabled = value @@ -378,7 +378,7 @@ def catalog_obfuscation_enabled(self, value): def flow_auto_save_enabled(self): return self._flow_auto_save_enabled - @flow_auto_save_enabled.setter + @flow_auto_save_enabled.setter # type: ignore[misc] @property_is_boolean def flow_auto_save_enabled(self, value): self._flow_auto_save_enabled = value @@ -387,7 +387,7 @@ def flow_auto_save_enabled(self, value): def web_extraction_enabled(self): return self._web_extraction_enabled - @web_extraction_enabled.setter + @web_extraction_enabled.setter # type: ignore[misc] @property_is_boolean def web_extraction_enabled(self, value): self._web_extraction_enabled = value @@ -396,7 +396,7 @@ def web_extraction_enabled(self, value): def metrics_content_type_enabled(self): return self._metrics_content_type_enabled - @metrics_content_type_enabled.setter + @metrics_content_type_enabled.setter # type: ignore[misc] @property_is_boolean def metrics_content_type_enabled(self, value): self._metrics_content_type_enabled = value @@ -405,7 +405,7 @@ def metrics_content_type_enabled(self, value): def notify_site_admins_on_throttle(self): return self._notify_site_admins_on_throttle - @notify_site_admins_on_throttle.setter + @notify_site_admins_on_throttle.setter # type: ignore[misc] @property_is_boolean def notify_site_admins_on_throttle(self, value): self._notify_site_admins_on_throttle = value @@ -414,7 +414,7 @@ def notify_site_admins_on_throttle(self, value): def authoring_enabled(self): return self._authoring_enabled - @authoring_enabled.setter + @authoring_enabled.setter # type: ignore[misc] @property_is_boolean def authoring_enabled(self, value): self._authoring_enabled = value @@ -423,7 +423,7 @@ def authoring_enabled(self, value): def custom_subscription_email_enabled(self): return self._custom_subscription_email_enabled - @custom_subscription_email_enabled.setter + @custom_subscription_email_enabled.setter # type: ignore[misc] @property_is_boolean def custom_subscription_email_enabled(self, value): self._custom_subscription_email_enabled = value @@ -440,7 +440,7 @@ def custom_subscription_email(self, value): def custom_subscription_footer_enabled(self): return self._custom_subscription_footer_enabled - @custom_subscription_footer_enabled.setter + @custom_subscription_footer_enabled.setter # type: ignore[misc] @property_is_boolean def custom_subscription_footer_enabled(self, value): self._custom_subscription_footer_enabled = value @@ -465,7 +465,7 @@ def ask_data_mode(self, value): def named_sharing_enabled(self): return self._named_sharing_enabled - @named_sharing_enabled.setter + @named_sharing_enabled.setter # type: ignore[misc] @property_is_boolean def named_sharing_enabled(self, value): self._named_sharing_enabled = value @@ -474,7 +474,7 @@ def named_sharing_enabled(self, value): def mobile_biometrics_enabled(self): return self._mobile_biometrics_enabled - @mobile_biometrics_enabled.setter + @mobile_biometrics_enabled.setter # type: ignore[misc] @property_is_boolean def mobile_biometrics_enabled(self, value): self._mobile_biometrics_enabled = value @@ -483,7 +483,7 @@ def mobile_biometrics_enabled(self, value): def sheet_image_enabled(self): return self._sheet_image_enabled - @sheet_image_enabled.setter + @sheet_image_enabled.setter # type: ignore[misc] @property_is_boolean def sheet_image_enabled(self, value): self._sheet_image_enabled = value @@ -492,7 +492,7 @@ def sheet_image_enabled(self, value): def derived_permissions_enabled(self): return self._derived_permissions_enabled - @derived_permissions_enabled.setter + @derived_permissions_enabled.setter # type: ignore[misc] @property_is_boolean def derived_permissions_enabled(self, value): self._derived_permissions_enabled = value diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index bc431ed77..3e5bd62cf 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -38,7 +38,7 @@ def id(self): def attach_image(self): return self._attach_image - @attach_image.setter + @attach_image.setter # type: ignore[misc] @property_is_boolean def attach_image(self, value): self._attach_image = value @@ -47,7 +47,7 @@ def attach_image(self, value): def attach_pdf(self): return self._attach_pdf - @attach_pdf.setter + @attach_pdf.setter # type: ignore[misc] @property_is_boolean def attach_pdf(self, value): self._attach_pdf = value @@ -56,7 +56,7 @@ def attach_pdf(self, value): def send_if_view_empty(self): return self._send_if_view_empty - @send_if_view_empty.setter + @send_if_view_empty.setter # type: ignore[misc] @property_is_boolean def send_if_view_empty(self, value): self._send_if_view_empty = value @@ -65,7 +65,7 @@ def send_if_view_empty(self, value): def suspended(self): return self._suspended - @suspended.setter + @suspended.setter # type: ignore[misc] @property_is_boolean def suspended(self, value): self._suspended = value diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index 2f47400f7..56d995e5e 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -41,7 +41,7 @@ def id(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value @@ -58,7 +58,7 @@ def description(self, value): def certified(self): return self._certified - @certified.setter + @certified.setter # type: ignore[misc] @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index 65abf4cb6..548db5d7d 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -58,7 +58,7 @@ def __init__(self, name=None, site_role=None, auth_setting=None): def auth_setting(self): return self._auth_setting - @auth_setting.setter + @auth_setting.setter # type: ignore[misc] @property_is_enum(Auth) def auth_setting(self, value): self._auth_setting = value @@ -83,7 +83,7 @@ def last_login(self): def name(self): return self._name - @name.setter + @name.setter # type: ignore[misc] @property_not_empty def name(self, value): self._name = value @@ -92,7 +92,7 @@ def name(self, value): def site_role(self): return self._site_role - @site_role.setter + @site_role.setter # type: ignore[misc] @property_not_nullable @property_is_enum(Roles) def site_role(self, value): diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 2a8f62c51..9a5b5450b 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -45,10 +45,10 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self.name = name self._description = None self.owner_id = None - self.project_id = project_id - self.show_tabs = show_tabs + self.project_id = project_id # type: ignore[misc] + self.show_tabs = show_tabs # type: ignore[misc] self.tags: Set[str] = set() - self.data_acceleration_config = { + self.data_acceleration_config = { # type: ignore[misc] "acceleration_enabled": None, "accelerate_now": None, "last_updated_at": None, @@ -110,7 +110,7 @@ def preview_image(self): def project_id(self) -> Optional[str]: return self._project_id - @project_id.setter + @project_id.setter # type: ignore[misc] @property_not_nullable def project_id(self, value: str): self._project_id = value @@ -123,7 +123,7 @@ def project_name(self) -> Optional[str]: def show_tabs(self) -> bool: return self._show_tabs - @show_tabs.setter + @show_tabs.setter # type: ignore[misc] @property_is_boolean def show_tabs(self, value: bool): self._show_tabs = value @@ -157,7 +157,7 @@ def views(self) -> List[ViewItem]: def data_acceleration_config(self): return self._data_acceleration_config - @data_acceleration_config.setter + @data_acceleration_config.setter # type: ignore[misc] @property_is_data_acceleration_config def data_acceleration_config(self, value): self._data_acceleration_config = value diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 21db9c484..5ca8ad517 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -1,7 +1,7 @@ import xml.etree.ElementTree as ET -from requests.packages.urllib3.fields import RequestField -from requests.packages.urllib3.filepost import encode_multipart_formdata +from requests.packages.urllib3.fields import RequestField # type: ignore[import] +from requests.packages.urllib3.filepost import encode_multipart_formdata # type: ignore[import] from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 3047691a9..c6dcd1b99 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -124,7 +124,7 @@ def __init__(self, maxage=-1): def max_age(self): return self._max_age - @max_age.setter + @max_age.setter # type: ignore[misc] @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -152,7 +152,7 @@ def __init__(self, imageresolution=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter + @max_age.setter # type: ignore[misc] @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -198,7 +198,7 @@ def __init__(self, page_type=None, orientation=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter + @max_age.setter # type: ignore[misc] @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index 4d289d5e5..0c3aa9319 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -31,7 +31,7 @@ ServerInfoEndpointNotFoundError, ) -import requests +import requests # type: ignore[import] from distutils.version import LooseVersion as Version From bc7d63e577cd935f804f9bf73cc221cdf463e02b Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 22:50:35 -0400 Subject: [PATCH 47/69] Ignore mypy import errors --- .github/workflows/run-tests.yml | 2 +- tableauserverclient/models/column_item.py | 2 +- .../models/connection_credentials.py | 4 +- tableauserverclient/models/data_alert_item.py | 6 +- tableauserverclient/models/database_item.py | 6 +- tableauserverclient/models/datasource_item.py | 12 ++-- tableauserverclient/models/flow_item.py | 2 +- tableauserverclient/models/group_item.py | 6 +- tableauserverclient/models/interval_item.py | 10 +-- tableauserverclient/models/project_item.py | 4 +- tableauserverclient/models/schedule_item.py | 10 +-- tableauserverclient/models/site_item.py | 64 +++++++++---------- .../models/subscription_item.py | 8 +-- tableauserverclient/models/table_item.py | 4 +- tableauserverclient/models/user_item.py | 6 +- tableauserverclient/models/workbook_item.py | 12 ++-- tableauserverclient/server/request_factory.py | 4 +- tableauserverclient/server/request_options.py | 6 +- tableauserverclient/server/server.py | 2 +- 19 files changed, 85 insertions(+), 85 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index abf16c0a4..f79869f64 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,4 +31,4 @@ jobs: - name: Run Mypy tests run: | - mypy --show-error-codes --disable-error-code misc tableauserverclient + mypy --show-error-codes --disable-error-code misc --disable-error-code import tableauserverclient diff --git a/tableauserverclient/models/column_item.py b/tableauserverclient/models/column_item.py index 2116d9de0..a95d005ca 100644 --- a/tableauserverclient/models/column_item.py +++ b/tableauserverclient/models/column_item.py @@ -17,7 +17,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/connection_credentials.py b/tableauserverclient/models/connection_credentials.py index 010adf6c9..db65de0ad 100644 --- a/tableauserverclient/models/connection_credentials.py +++ b/tableauserverclient/models/connection_credentials.py @@ -19,7 +19,7 @@ def __init__(self, name, password, embed=True, oauth=False): def embed(self): return self._embed - @embed.setter # type: ignore[misc] + @embed.setter @property_is_boolean def embed(self, value): self._embed = value @@ -28,7 +28,7 @@ def embed(self, value): def oauth(self): return self._oauth - @oauth.setter # type: ignore[misc] + @oauth.setter @property_is_boolean def oauth(self, value): self._oauth = value diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index 8e7955441..a4d11ca5e 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -49,7 +49,7 @@ def id(self): def subject(self): return self._subject - @subject.setter # type: ignore[misc] + @subject.setter @property_not_empty def subject(self, value): self._subject = value @@ -58,7 +58,7 @@ def subject(self, value): def frequency(self): return self._frequency - @frequency.setter # type: ignore[misc] + @frequency.setter @property_is_enum(Frequency) def frequency(self, value): self._frequency = value @@ -67,7 +67,7 @@ def frequency(self, value): def public(self): return self._public - @public.setter # type: ignore[misc] + @public.setter @property_is_boolean def public(self, value): self._public = value diff --git a/tableauserverclient/models/database_item.py b/tableauserverclient/models/database_item.py index 94d992584..514bf92bc 100644 --- a/tableauserverclient/models/database_item.py +++ b/tableauserverclient/models/database_item.py @@ -53,7 +53,7 @@ def dqws(self): def content_permissions(self): return self._content_permissions - @content_permissions.setter # type: ignore[misc] + @content_permissions.setter @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -80,7 +80,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value @@ -101,7 +101,7 @@ def embedded(self): def certified(self): return self._certified - @certified.setter # type: ignore[misc] + @certified.setter @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 9302910d3..2d4f17e7f 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -43,7 +43,7 @@ def __init__(self, project_id: str, name: str = None) -> None: self.description = None self.name = name self.owner_id = None - self.project_id = project_id # type: ignore[misc] + self.project_id = project_id self.tags: Set = set() self._permissions = None @@ -55,7 +55,7 @@ def __init__(self, project_id: str, name: str = None) -> None: def ask_data_enablement(self) -> Optional["DatasourceItem.AskDataEnablement"]: return self._ask_data_enablement - @ask_data_enablement.setter # type: ignore[misc] + @ask_data_enablement.setter @property_is_enum(AskDataEnablement) def ask_data_enablement(self, value: Optional["DatasourceItem.AskDataEnablement"]): self._ask_data_enablement = value @@ -86,7 +86,7 @@ def created_at(self) -> Optional["datetime.datetime"]: def certified(self) -> Optional[bool]: return self._certified - @certified.setter # type: ignore[misc] + @certified.setter @property_not_nullable @property_is_boolean def certified(self, value: Optional[bool]): @@ -104,7 +104,7 @@ def certification_note(self, value: Optional[str]): def encrypt_extracts(self): return self._encrypt_extracts - @encrypt_extracts.setter # type: ignore[misc] + @encrypt_extracts.setter @property_is_boolean def encrypt_extracts(self, value: Optional[bool]): self._encrypt_extracts = value @@ -128,7 +128,7 @@ def id(self) -> Optional[str]: def project_id(self) -> str: return self._project_id - @project_id.setter # type: ignore[misc] + @project_id.setter @property_not_nullable def project_id(self, value: str): self._project_id = value @@ -157,7 +157,7 @@ def updated_at(self) -> Optional["datetime.datetime"]: def use_remote_query_agent(self) -> Optional[bool]: return self._use_remote_query_agent - @use_remote_query_agent.setter # type: ignore[misc] + @use_remote_query_agent.setter @property_is_boolean def use_remote_query_agent(self, value: bool): self._use_remote_query_agent = value diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index 2e30610af..d1387f368 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -61,7 +61,7 @@ def id(self): def project_id(self): return self._project_id - @project_id.setter # type: ignore[misc] + @project_id.setter @property_not_nullable def project_id(self, value): self._project_id = value diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index 73dea024d..fdc06604b 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -37,7 +37,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value @@ -46,7 +46,7 @@ def name(self, value): def license_mode(self): return self._license_mode - @license_mode.setter # type: ignore[misc] + @license_mode.setter @property_is_enum(LicenseMode) def license_mode(self, value): self._license_mode = value @@ -55,7 +55,7 @@ def license_mode(self, value): def minimum_site_role(self): return self._minimum_site_role - @minimum_site_role.setter # type: ignore[misc] + @minimum_site_role.setter @property_is_enum(UserItem.Roles) def minimum_site_role(self, value): self._minimum_site_role = value diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index 47cf8cea5..320e01ef2 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -40,7 +40,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore[misc] + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -50,7 +50,7 @@ def start_time(self, value): def end_time(self): return self._end_time - @end_time.setter # type: ignore[misc] + @end_time.setter @property_is_valid_time @property_not_nullable def end_time(self, value): @@ -95,7 +95,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore[misc] + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -115,7 +115,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore[misc] + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): @@ -149,7 +149,7 @@ def _frequency(self): def start_time(self): return self._start_time - @start_time.setter # type: ignore[misc] + @start_time.setter @property_is_valid_time @property_not_nullable def start_time(self, value): diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index 0781ccc7c..c6525a2ca 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -29,7 +29,7 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N def content_permissions(self): return self._content_permissions - @content_permissions.setter # type: ignore[misc] + @content_permissions.setter @property_is_enum(ContentPermissions) def content_permissions(self, value): self._content_permissions = value @@ -70,7 +70,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index a23bbc67f..f8baf0749 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -59,7 +59,7 @@ def end_schedule_at(self): def execution_order(self): return self._execution_order - @execution_order.setter # type: ignore[misc] + @execution_order.setter @property_is_enum(ExecutionOrder) def execution_order(self, value): self._execution_order = value @@ -72,7 +72,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_nullable def name(self, value): self._name = value @@ -85,7 +85,7 @@ def next_run_at(self): def priority(self): return self._priority - @priority.setter # type: ignore[misc] + @priority.setter @property_is_int(range=(1, 100)) def priority(self, value): self._priority = value @@ -94,7 +94,7 @@ def priority(self, value): def schedule_type(self): return self._schedule_type - @schedule_type.setter # type: ignore[misc] + @schedule_type.setter @property_is_enum(Type) @property_not_nullable def schedule_type(self, value): @@ -104,7 +104,7 @@ def schedule_type(self, value): def state(self): return self._state - @state.setter # type: ignore[misc] + @state.setter @property_is_enum(State) def state(self, value): self._state = value diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index d40fbc5c7..ab0211414 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -127,7 +127,7 @@ def __init__( def admin_mode(self): return self._admin_mode - @admin_mode.setter # type: ignore[misc] + @admin_mode.setter @property_is_enum(AdminMode) def admin_mode(self, value): self._admin_mode = value @@ -136,7 +136,7 @@ def admin_mode(self, value): def content_url(self): return self._content_url - @content_url.setter # type: ignore[misc] + @content_url.setter @property_not_nullable @property_matches( VALID_CONTENT_URL_RE, @@ -149,7 +149,7 @@ def content_url(self, value): def disable_subscriptions(self): return self._disable_subscriptions - @disable_subscriptions.setter # type: ignore[misc] + @disable_subscriptions.setter @property_is_boolean def disable_subscriptions(self, value): self._disable_subscriptions = value @@ -162,7 +162,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value @@ -175,7 +175,7 @@ def num_users(self): def revision_history_enabled(self): return self._revision_history_enabled - @revision_history_enabled.setter # type: ignore[misc] + @revision_history_enabled.setter @property_is_boolean def revision_history_enabled(self, value): self._revision_history_enabled = value @@ -184,7 +184,7 @@ def revision_history_enabled(self, value): def revision_limit(self): return self._revision_limit - @revision_limit.setter # type: ignore[misc] + @revision_limit.setter @property_is_int((2, 10000), allowed=[-1]) def revision_limit(self, value): self._revision_limit = value @@ -193,7 +193,7 @@ def revision_limit(self, value): def state(self): return self._state - @state.setter # type: ignore[misc] + @state.setter @property_is_enum(State) def state(self, value): self._state = value @@ -210,7 +210,7 @@ def storage(self): def subscribe_others_enabled(self): return self._subscribe_others_enabled - @subscribe_others_enabled.setter # type: ignore[misc] + @subscribe_others_enabled.setter @property_is_boolean def subscribe_others_enabled(self, value): self._subscribe_others_enabled = value @@ -235,7 +235,7 @@ def cataloging_enabled(self, value): def flows_enabled(self): return self._flows_enabled - @flows_enabled.setter # type: ignore[misc] + @flows_enabled.setter @property_is_boolean def flows_enabled(self, value): self._flows_enabled = value @@ -247,7 +247,7 @@ def is_default(self): def editing_flows_enabled(self): return self._editing_flows_enabled - @editing_flows_enabled.setter # type: ignore[misc] + @editing_flows_enabled.setter @property_is_boolean def editing_flows_enabled(self, value): self._editing_flows_enabled = value @@ -256,7 +256,7 @@ def editing_flows_enabled(self, value): def scheduling_flows_enabled(self): return self._scheduling_flows_enabled - @scheduling_flows_enabled.setter # type: ignore[misc] + @scheduling_flows_enabled.setter @property_is_boolean def scheduling_flows_enabled(self, value): self._scheduling_flows_enabled = value @@ -265,7 +265,7 @@ def scheduling_flows_enabled(self, value): def allow_subscription_attachments(self): return self._allow_subscription_attachments - @allow_subscription_attachments.setter # type: ignore[misc] + @allow_subscription_attachments.setter @property_is_boolean def allow_subscription_attachments(self, value): self._allow_subscription_attachments = value @@ -274,7 +274,7 @@ def allow_subscription_attachments(self, value): def guest_access_enabled(self): return self._guest_access_enabled - @guest_access_enabled.setter # type: ignore[misc] + @guest_access_enabled.setter @property_is_boolean def guest_access_enabled(self, value): self._guest_access_enabled = value @@ -283,7 +283,7 @@ def guest_access_enabled(self, value): def cache_warmup_enabled(self): return self._cache_warmup_enabled - @cache_warmup_enabled.setter # type: ignore[misc] + @cache_warmup_enabled.setter @property_is_boolean def cache_warmup_enabled(self, value): self._cache_warmup_enabled = value @@ -292,7 +292,7 @@ def cache_warmup_enabled(self, value): def commenting_enabled(self): return self._commenting_enabled - @commenting_enabled.setter # type: ignore[misc] + @commenting_enabled.setter @property_is_boolean def commenting_enabled(self, value): self._commenting_enabled = value @@ -309,7 +309,7 @@ def extract_encryption_mode(self, value): def request_access_enabled(self): return self._request_access_enabled - @request_access_enabled.setter # type: ignore[misc] + @request_access_enabled.setter @property_is_boolean def request_access_enabled(self, value): self._request_access_enabled = value @@ -318,7 +318,7 @@ def request_access_enabled(self, value): def run_now_enabled(self): return self._run_now_enabled - @run_now_enabled.setter # type: ignore[misc] + @run_now_enabled.setter @property_is_boolean def run_now_enabled(self, value): self._run_now_enabled = value @@ -327,7 +327,7 @@ def run_now_enabled(self, value): def tier_explorer_capacity(self): return self._tier_explorer_capacity - @tier_explorer_capacity.setter # type: ignore[misc] + @tier_explorer_capacity.setter def tier_explorer_capacity(self, value): self._tier_explorer_capacity = value @@ -351,7 +351,7 @@ def tier_viewer_capacity(self, value): def data_alerts_enabled(self): return self._data_alerts_enabled - @data_alerts_enabled.setter # type: ignore[misc] + @data_alerts_enabled.setter @property_is_boolean def data_alerts_enabled(self, value): self._data_alerts_enabled = value @@ -360,7 +360,7 @@ def data_alerts_enabled(self, value): def commenting_mentions_enabled(self): return self._commenting_mentions_enabled - @commenting_mentions_enabled.setter # type: ignore[misc] + @commenting_mentions_enabled.setter @property_is_boolean def commenting_mentions_enabled(self, value): self._commenting_mentions_enabled = value @@ -369,7 +369,7 @@ def commenting_mentions_enabled(self, value): def catalog_obfuscation_enabled(self): return self._catalog_obfuscation_enabled - @catalog_obfuscation_enabled.setter # type: ignore[misc] + @catalog_obfuscation_enabled.setter @property_is_boolean def catalog_obfuscation_enabled(self, value): self._catalog_obfuscation_enabled = value @@ -378,7 +378,7 @@ def catalog_obfuscation_enabled(self, value): def flow_auto_save_enabled(self): return self._flow_auto_save_enabled - @flow_auto_save_enabled.setter # type: ignore[misc] + @flow_auto_save_enabled.setter @property_is_boolean def flow_auto_save_enabled(self, value): self._flow_auto_save_enabled = value @@ -387,7 +387,7 @@ def flow_auto_save_enabled(self, value): def web_extraction_enabled(self): return self._web_extraction_enabled - @web_extraction_enabled.setter # type: ignore[misc] + @web_extraction_enabled.setter @property_is_boolean def web_extraction_enabled(self, value): self._web_extraction_enabled = value @@ -396,7 +396,7 @@ def web_extraction_enabled(self, value): def metrics_content_type_enabled(self): return self._metrics_content_type_enabled - @metrics_content_type_enabled.setter # type: ignore[misc] + @metrics_content_type_enabled.setter @property_is_boolean def metrics_content_type_enabled(self, value): self._metrics_content_type_enabled = value @@ -405,7 +405,7 @@ def metrics_content_type_enabled(self, value): def notify_site_admins_on_throttle(self): return self._notify_site_admins_on_throttle - @notify_site_admins_on_throttle.setter # type: ignore[misc] + @notify_site_admins_on_throttle.setter @property_is_boolean def notify_site_admins_on_throttle(self, value): self._notify_site_admins_on_throttle = value @@ -414,7 +414,7 @@ def notify_site_admins_on_throttle(self, value): def authoring_enabled(self): return self._authoring_enabled - @authoring_enabled.setter # type: ignore[misc] + @authoring_enabled.setter @property_is_boolean def authoring_enabled(self, value): self._authoring_enabled = value @@ -423,7 +423,7 @@ def authoring_enabled(self, value): def custom_subscription_email_enabled(self): return self._custom_subscription_email_enabled - @custom_subscription_email_enabled.setter # type: ignore[misc] + @custom_subscription_email_enabled.setter @property_is_boolean def custom_subscription_email_enabled(self, value): self._custom_subscription_email_enabled = value @@ -440,7 +440,7 @@ def custom_subscription_email(self, value): def custom_subscription_footer_enabled(self): return self._custom_subscription_footer_enabled - @custom_subscription_footer_enabled.setter # type: ignore[misc] + @custom_subscription_footer_enabled.setter @property_is_boolean def custom_subscription_footer_enabled(self, value): self._custom_subscription_footer_enabled = value @@ -465,7 +465,7 @@ def ask_data_mode(self, value): def named_sharing_enabled(self): return self._named_sharing_enabled - @named_sharing_enabled.setter # type: ignore[misc] + @named_sharing_enabled.setter @property_is_boolean def named_sharing_enabled(self, value): self._named_sharing_enabled = value @@ -474,7 +474,7 @@ def named_sharing_enabled(self, value): def mobile_biometrics_enabled(self): return self._mobile_biometrics_enabled - @mobile_biometrics_enabled.setter # type: ignore[misc] + @mobile_biometrics_enabled.setter @property_is_boolean def mobile_biometrics_enabled(self, value): self._mobile_biometrics_enabled = value @@ -483,7 +483,7 @@ def mobile_biometrics_enabled(self, value): def sheet_image_enabled(self): return self._sheet_image_enabled - @sheet_image_enabled.setter # type: ignore[misc] + @sheet_image_enabled.setter @property_is_boolean def sheet_image_enabled(self, value): self._sheet_image_enabled = value @@ -492,7 +492,7 @@ def sheet_image_enabled(self, value): def derived_permissions_enabled(self): return self._derived_permissions_enabled - @derived_permissions_enabled.setter # type: ignore[misc] + @derived_permissions_enabled.setter @property_is_boolean def derived_permissions_enabled(self, value): self._derived_permissions_enabled = value diff --git a/tableauserverclient/models/subscription_item.py b/tableauserverclient/models/subscription_item.py index 3e5bd62cf..bc431ed77 100644 --- a/tableauserverclient/models/subscription_item.py +++ b/tableauserverclient/models/subscription_item.py @@ -38,7 +38,7 @@ def id(self): def attach_image(self): return self._attach_image - @attach_image.setter # type: ignore[misc] + @attach_image.setter @property_is_boolean def attach_image(self, value): self._attach_image = value @@ -47,7 +47,7 @@ def attach_image(self, value): def attach_pdf(self): return self._attach_pdf - @attach_pdf.setter # type: ignore[misc] + @attach_pdf.setter @property_is_boolean def attach_pdf(self, value): self._attach_pdf = value @@ -56,7 +56,7 @@ def attach_pdf(self, value): def send_if_view_empty(self): return self._send_if_view_empty - @send_if_view_empty.setter # type: ignore[misc] + @send_if_view_empty.setter @property_is_boolean def send_if_view_empty(self, value): self._send_if_view_empty = value @@ -65,7 +65,7 @@ def send_if_view_empty(self, value): def suspended(self): return self._suspended - @suspended.setter # type: ignore[misc] + @suspended.setter @property_is_boolean def suspended(self, value): self._suspended = value diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index 56d995e5e..2f47400f7 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -41,7 +41,7 @@ def id(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value @@ -58,7 +58,7 @@ def description(self, value): def certified(self): return self._certified - @certified.setter # type: ignore[misc] + @certified.setter @property_is_boolean def certified(self, value): self._certified = value diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index 548db5d7d..65abf4cb6 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -58,7 +58,7 @@ def __init__(self, name=None, site_role=None, auth_setting=None): def auth_setting(self): return self._auth_setting - @auth_setting.setter # type: ignore[misc] + @auth_setting.setter @property_is_enum(Auth) def auth_setting(self, value): self._auth_setting = value @@ -83,7 +83,7 @@ def last_login(self): def name(self): return self._name - @name.setter # type: ignore[misc] + @name.setter @property_not_empty def name(self, value): self._name = value @@ -92,7 +92,7 @@ def name(self, value): def site_role(self): return self._site_role - @site_role.setter # type: ignore[misc] + @site_role.setter @property_not_nullable @property_is_enum(Roles) def site_role(self, value): diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 9a5b5450b..2a8f62c51 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -45,10 +45,10 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self.name = name self._description = None self.owner_id = None - self.project_id = project_id # type: ignore[misc] - self.show_tabs = show_tabs # type: ignore[misc] + self.project_id = project_id + self.show_tabs = show_tabs self.tags: Set[str] = set() - self.data_acceleration_config = { # type: ignore[misc] + self.data_acceleration_config = { "acceleration_enabled": None, "accelerate_now": None, "last_updated_at": None, @@ -110,7 +110,7 @@ def preview_image(self): def project_id(self) -> Optional[str]: return self._project_id - @project_id.setter # type: ignore[misc] + @project_id.setter @property_not_nullable def project_id(self, value: str): self._project_id = value @@ -123,7 +123,7 @@ def project_name(self) -> Optional[str]: def show_tabs(self) -> bool: return self._show_tabs - @show_tabs.setter # type: ignore[misc] + @show_tabs.setter @property_is_boolean def show_tabs(self, value: bool): self._show_tabs = value @@ -157,7 +157,7 @@ def views(self) -> List[ViewItem]: def data_acceleration_config(self): return self._data_acceleration_config - @data_acceleration_config.setter # type: ignore[misc] + @data_acceleration_config.setter @property_is_data_acceleration_config def data_acceleration_config(self, value): self._data_acceleration_config = value diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 5ca8ad517..21db9c484 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -1,7 +1,7 @@ import xml.etree.ElementTree as ET -from requests.packages.urllib3.fields import RequestField # type: ignore[import] -from requests.packages.urllib3.filepost import encode_multipart_formdata # type: ignore[import] +from requests.packages.urllib3.fields import RequestField +from requests.packages.urllib3.filepost import encode_multipart_formdata from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index c6dcd1b99..3047691a9 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -124,7 +124,7 @@ def __init__(self, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore[misc] + @max_age.setter @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -152,7 +152,7 @@ def __init__(self, imageresolution=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore[misc] + @max_age.setter @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value @@ -198,7 +198,7 @@ def __init__(self, page_type=None, orientation=None, maxage=-1): def max_age(self): return self._max_age - @max_age.setter # type: ignore[misc] + @max_age.setter @property_is_int(range=(0, 240), allowed=[-1]) def max_age(self, value): self._max_age = value diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index 0c3aa9319..4d289d5e5 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -31,7 +31,7 @@ ServerInfoEndpointNotFoundError, ) -import requests # type: ignore[import] +import requests from distutils.version import LooseVersion as Version From 0d4dc2c8b5a274679a98e8022d01d2e8b309c43f Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 22:58:25 -0400 Subject: [PATCH 48/69] Remove pre-commit --- .pre-commit-config.yaml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 4ecfb5bbd..000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,37 +0,0 @@ -repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 - hooks: - - id: end-of-file-fixer - files: '.*\.py' - - id: trailing-whitespace - files: '.*\.py' - -- repo: local - hooks: - - id: unittest - name: unittest - entry: python -m unittest discover - language: python - 'types': [python] - pass_filenames: false - additional_dependencies: - - requests - - mock - - requests-mock - - pytest - stages: [commit] - - id: black - name: black - entry: black --line-length 120 tableauserverclient - language: python - 'types': [python] - pass_filenames: false - stages: [commit] - - id: mypy - name: mypy - entry: mypy --show-error-codes --disable-error-code misc tableauserverclient - language: python - 'types': [python] - pass_filenames: false - stages: [commit] From a6ab0b5be3bc07bbe7b4b414d740d2c958f7ed80 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 18 Oct 2021 23:01:33 -0400 Subject: [PATCH 49/69] Formatting --- tableauserverclient/exponential_backoff.py | 12 ++++++------ tableauserverclient/models/datasource_item.py | 18 +++++++++--------- tableauserverclient/models/job_item.py | 2 +- .../server/endpoint/datasources_endpoint.py | 5 ++--- .../server/endpoint/exceptions.py | 2 +- .../server/endpoint/jobs_endpoint.py | 1 + .../server/endpoint/workbooks_endpoint.py | 1 - 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/tableauserverclient/exponential_backoff.py b/tableauserverclient/exponential_backoff.py index 2b3ded109..69ffdc96b 100644 --- a/tableauserverclient/exponential_backoff.py +++ b/tableauserverclient/exponential_backoff.py @@ -1,12 +1,12 @@ import time # Polling for server-side events (such as job completion) uses exponential backoff for the sleep intervals between polls -ASYNC_POLL_MIN_INTERVAL=0.5 -ASYNC_POLL_MAX_INTERVAL=30 -ASYNC_POLL_BACKOFF_FACTOR=1.4 +ASYNC_POLL_MIN_INTERVAL = 0.5 +ASYNC_POLL_MAX_INTERVAL = 30 +ASYNC_POLL_BACKOFF_FACTOR = 1.4 -class ExponentialBackoffTimer(): +class ExponentialBackoffTimer: def __init__(self, *, timeout=None): self.start_time = time.time() self.timeout = timeout @@ -15,7 +15,7 @@ def __init__(self, *, timeout=None): def sleep(self): max_sleep_time = ASYNC_POLL_MAX_INTERVAL if self.timeout is not None: - elapsed = (time.time() - self.start_time) + elapsed = time.time() - self.start_time if elapsed >= self.timeout: raise TimeoutError(f"Timeout after {elapsed} seconds waiting for asynchronous event") remaining_time = self.timeout - elapsed @@ -27,4 +27,4 @@ def sleep(self): max_sleep_time = max(max_sleep_time, ASYNC_POLL_MIN_INTERVAL) time.sleep(min(self.current_sleep_interval, max_sleep_time)) - self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR \ No newline at end of file + self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 2d4f17e7f..e17d27ce3 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -332,15 +332,15 @@ def from_response(cls, resp: str, ns: Dict) -> List["DatasourceItem"]: @staticmethod def _parse_element(datasource_xml: ET.Element, ns: Dict) -> Tuple: - id_ = datasource_xml.get('id', None) - name = datasource_xml.get('name', None) - datasource_type = datasource_xml.get('type', None) - description = datasource_xml.get('description', None) - content_url = datasource_xml.get('contentUrl', None) - created_at = parse_datetime(datasource_xml.get('createdAt', None)) - updated_at = parse_datetime(datasource_xml.get('updatedAt', None)) - certification_note = datasource_xml.get('certificationNote', None) - certified = str(datasource_xml.get('isCertified', None)).lower() == 'true' + id_ = datasource_xml.get("id", None) + name = datasource_xml.get("name", None) + datasource_type = datasource_xml.get("type", None) + description = datasource_xml.get("description", None) + content_url = datasource_xml.get("contentUrl", None) + created_at = parse_datetime(datasource_xml.get("createdAt", None)) + updated_at = parse_datetime(datasource_xml.get("updatedAt", None)) + certification_note = datasource_xml.get("certificationNote", None) + certified = str(datasource_xml.get("isCertified", None)).lower() == "true" certification_note = datasource_xml.get("certificationNote", None) certified = str(datasource_xml.get("isCertified", None)).lower() == "true" content_url = datasource_xml.get("contentUrl", None) diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index 2a8b6b509..f8c00b555 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -8,11 +8,11 @@ class FinishCode: Status codes as documented on https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#query_job """ + Success = 0 Failed = 1 Cancelled = 2 - def __init__( self, id_, diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index b5228e558..d6efa9d41 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -209,7 +209,6 @@ def delete_extract(self, datasource_item: DatasourceItem) -> None: empty_req = RequestFactory.Empty.empty_req() self.post_request(url, empty_req) - # Publish datasource @api(version="2.0") @parameter_added_in(connections="2.8") @@ -260,7 +259,7 @@ def publish( else: raise TypeError() - + if not mode or not hasattr(self.parent_srv.PublishMode, mode): error = "Invalid mode defined." raise ValueError(error) @@ -318,7 +317,7 @@ def publish( return new_datasource @api(version="3.13") - def update_hyper_data(self, datasource_or_connection_item, *, request_id, actions, payload = None): + def update_hyper_data(self, datasource_or_connection_item, *, request_id, actions, payload=None): if isinstance(datasource_or_connection_item, DatasourceItem): datasource_id = datasource_or_connection_item.id url = "{0}/{1}/data".format(self.baseurl, datasource_id) diff --git a/tableauserverclient/server/endpoint/exceptions.py b/tableauserverclient/server/endpoint/exceptions.py index 693817ddc..3d39e7102 100644 --- a/tableauserverclient/server/endpoint/exceptions.py +++ b/tableauserverclient/server/endpoint/exceptions.py @@ -70,7 +70,7 @@ class JobFailedException(Exception): def __init__(self, job): self.notes = job.notes self.job = job - + def __str__(self): return f"Job {self.job.id} failed with notes {self.notes}" diff --git a/tableauserverclient/server/endpoint/jobs_endpoint.py b/tableauserverclient/server/endpoint/jobs_endpoint.py index 4c975c523..ab0002ac0 100644 --- a/tableauserverclient/server/endpoint/jobs_endpoint.py +++ b/tableauserverclient/server/endpoint/jobs_endpoint.py @@ -8,6 +8,7 @@ logger = logging.getLogger("tableau.endpoint.jobs") + class Jobs(Endpoint): @property def baseurl(self): diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index cd0032a70..abd744d22 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -294,7 +294,6 @@ def update_permissions(self, resource, rules): def delete_permission(self, item, capability_item): return self._permissions.delete(item, capability_item) - @api(version="2.0") @parameter_added_in(as_job="3.0") @parameter_added_in(connections="2.8") From 1f01be26418da2bb26993afbdf4e5cd58b47b94b Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 04:42:44 -0400 Subject: [PATCH 50/69] Fix black formatting oddities --- tableauserverclient/models/workbook_item.py | 2 +- tableauserverclient/server/endpoint/datasources_endpoint.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 2a8f62c51..9bb561b80 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -147,7 +147,7 @@ def views(self) -> List[ViewItem]: error = "Workbook item must be populated with views first." raise UnpopulatedPropertyError(error) elif callable(self._views): - # We"ve called `populate_views` on this model + # We've called `populate_views` on this model return self._views() else: # We had views included in a WorkbookItem response diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index d6efa9d41..b93575d8d 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -83,7 +83,7 @@ def get_by_id(self, datasource_id: str) -> DatasourceItem: server_response = self.get_request(url) return DatasourceItem.from_response(server_response.content, self.parent_srv.namespace)[0] - # Populate datasource item"s connections + # Populate datasource item's connections @api(version="2.0") def populate_connections(self, datasource_item: DatasourceItem) -> None: if not datasource_item.id: From 511f676538d5bd259bf7026e25aadfb59a6a3229 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 04:53:28 -0400 Subject: [PATCH 51/69] Add context to TypeErrors --- .../server/endpoint/datasources_endpoint.py | 2 +- tableauserverclient/server/endpoint/workbooks_endpoint.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index b93575d8d..e633bbf0d 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -258,7 +258,7 @@ def publish( file_size = get_file_object_size(file) else: - raise TypeError() + raise TypeError('file should be a filepath or file object.') if not mode or not hasattr(self.parent_srv.PublishMode, mode): error = "Invalid mode defined." diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index abd744d22..5166a9a6a 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -334,7 +334,7 @@ def publish( error = "Only {} files can be published as workbooks.".format(", ".join(ALLOWED_FILE_EXTENSIONS)) raise ValueError(error) - else: + elif isinstance(file, (io.BytesIO, io.BufferedReader)): # Expect file to be a file object file_size = get_file_object_size(file) @@ -356,6 +356,9 @@ def publish( # This is needed when publishing the workbook in a single request filename = "{}.{}".format(workbook_item.name, file_extension) + else: + raise TypeError('file should be a filepath or file object.') + if not hasattr(self.parent_srv.PublishMode, mode): error = "Invalid mode defined." raise ValueError(error) @@ -397,7 +400,8 @@ def publish( file_contents = file.read() else: - raise TypeError() + raise TypeError('file should be a filepath or file object.') + conn_creds = connection_credentials xml_request, content_type = RequestFactory.Workbook.publish_req( From 077c273476b043c3c5e226967e1fe20ab690ac76 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 04:59:41 -0400 Subject: [PATCH 52/69] Add workbook test for TypeError --- test/test_workbook.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/test_workbook.py b/test/test_workbook.py index 459b1f905..8ef4e24d4 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -632,12 +632,21 @@ def test_publish_invalid_file_type(self): def test_publish_unnamed_file_object(self): new_workbook = TSC.WorkbookItem('test') - with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx')) as f: + with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx'), 'rb') as f: self.assertRaises(ValueError, self.server.workbooks.publish, new_workbook, f, self.server.PublishMode.CreateNew ) + def test_publish_non_bytes_file_object(self): + new_workbook = TSC.WorkbookItem('test') + + with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx')) as f: + + self.assertRaises(TypeError, self.server.workbooks.publish, + new_workbook, f, self.server.PublishMode.CreateNew + ) + def test_publish_file_object_of_unknown_type_raises_exception(self): new_workbook = TSC.WorkbookItem('test') with BytesIO() as file_object: From 4eeab5d3500891fb48f6078035867663360b59c7 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 05:17:02 -0400 Subject: [PATCH 53/69] Type hint update_hyper_data --- .../server/endpoint/datasources_endpoint.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index e633bbf0d..a09e40773 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -317,7 +317,13 @@ def publish( return new_datasource @api(version="3.13") - def update_hyper_data(self, datasource_or_connection_item, *, request_id, actions, payload=None): + def update_hyper_data(self, + datasource_or_connection_item: Union[DatasourceItem, ConnectionItem], + *, + request_id: str, + actions: List[str], + payload: Optional[os.PathLike] = None + ) -> JobItem: if isinstance(datasource_or_connection_item, DatasourceItem): datasource_id = datasource_or_connection_item.id url = "{0}/{1}/data".format(self.baseurl, datasource_id) From 27b4c61ab16cfbdf1000d8a6b7a3468dd5783820 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 05:19:34 -0400 Subject: [PATCH 54/69] Formatting --- .../server/endpoint/datasources_endpoint.py | 17 +++++++++-------- .../server/endpoint/workbooks_endpoint.py | 5 ++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index a09e40773..50ec1b424 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -258,7 +258,7 @@ def publish( file_size = get_file_object_size(file) else: - raise TypeError('file should be a filepath or file object.') + raise TypeError("file should be a filepath or file object.") if not mode or not hasattr(self.parent_srv.PublishMode, mode): error = "Invalid mode defined." @@ -317,13 +317,14 @@ def publish( return new_datasource @api(version="3.13") - def update_hyper_data(self, - datasource_or_connection_item: Union[DatasourceItem, ConnectionItem], - *, - request_id: str, - actions: List[str], - payload: Optional[os.PathLike] = None - ) -> JobItem: + def update_hyper_data( + self, + datasource_or_connection_item: Union[DatasourceItem, ConnectionItem], + *, + request_id: str, + actions: List[str], + payload: Optional[os.PathLike] = None + ) -> JobItem: if isinstance(datasource_or_connection_item, DatasourceItem): datasource_id = datasource_or_connection_item.id url = "{0}/{1}/data".format(self.baseurl, datasource_id) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 5166a9a6a..a19dc18fd 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -357,7 +357,7 @@ def publish( filename = "{}.{}".format(workbook_item.name, file_extension) else: - raise TypeError('file should be a filepath or file object.') + raise TypeError("file should be a filepath or file object.") if not hasattr(self.parent_srv.PublishMode, mode): error = "Invalid mode defined." @@ -400,8 +400,7 @@ def publish( file_contents = file.read() else: - raise TypeError('file should be a filepath or file object.') - + raise TypeError("file should be a filepath or file object.") conn_creds = connection_credentials xml_request, content_type = RequestFactory.Workbook.publish_req( From 1e41a91067de5a1b0b1d58af1e86e759716c22c9 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 05:25:24 -0400 Subject: [PATCH 55/69] Pin mypy version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d641df12e..429e7c09d 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ # This makes work easier for offline installs or low bandwidth machines needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) pytest_runner = ['pytest-runner'] if needs_pytest else [] -test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy'] +test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy==0.910'] setup( name='tableauserverclient', From 814430b0e5ace328b8ab209b9a9a384ccb568039 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 05:35:16 -0400 Subject: [PATCH 56/69] Better TypeError message --- tableauserverclient/server/endpoint/datasources_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 50ec1b424..60497f760 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -289,7 +289,7 @@ def publish( elif isinstance(file, io_types): file_contents = file.read() else: - raise TypeError() + raise TypeError("file should be a filepath or file object.") xml_request, content_type = RequestFactory.Datasource.publish_req( datasource_item, From bfccfb422f31d0e4f7a58845c07418f384b60568 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 05:55:25 -0400 Subject: [PATCH 57/69] Use os.PathLike abstract class in isinstance --- tableauserverclient/server/endpoint/datasources_endpoint.py | 2 +- tableauserverclient/server/endpoint/workbooks_endpoint.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 60497f760..252d96829 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -223,7 +223,7 @@ def publish( as_job: bool = False, ) -> Union[DatasourceItem, JobItem]: - if isinstance(file, (Path, str)): + if isinstance(file, (os.PathLike, str)): if not os.path.isfile(file): error = "File path does not lead to an existing file." raise IOError(error) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index a19dc18fd..61b2a2ec1 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -317,7 +317,7 @@ def publish( DeprecationWarning, ) - if isinstance(file, (str, Path)): + if isinstance(file, (str, os.PathLike)): # Expect file to be a filepath if not os.path.isfile(file): error = "File path does not lead to an existing file." From a5997ec21badc0f3d670146b9bbadf24b8fd76a0 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 05:56:48 -0400 Subject: [PATCH 58/69] Add test for publishing a Path object --- test/test_workbook.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/test_workbook.py b/test/test_workbook.py index 8ef4e24d4..919d92e21 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -5,7 +5,7 @@ import requests_mock import tableauserverclient as TSC import xml.etree.ElementTree as ET - +from pathlib import Path from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient.server.endpoint.exceptions import InternalServerError @@ -547,6 +547,37 @@ def test_publish_non_packeged_file_object(self): self.assertEqual('GDP per capita', new_workbook.views[0].name) self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) + def test_publish_path_object(self): + with open(PUBLISH_XML, 'rb') as f: + response_xml = f.read().decode('utf-8') + with requests_mock.mock() as m: + m.post(self.baseurl, text=response_xml) + + new_workbook = TSC.WorkbookItem(name='Sample', + show_tabs=False, + project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + + sample_workbook = Path(TEST_ASSET_DIR) / 'SampleWB.twbx' + publish_mode = self.server.PublishMode.CreateNew + + new_workbook = self.server.workbooks.publish(new_workbook, + sample_workbook, + publish_mode) + + self.assertEqual('a8076ca1-e9d8-495e-bae6-c684dbb55836', new_workbook.id) + self.assertEqual('RESTAPISample', new_workbook.name) + self.assertEqual('RESTAPISample_0', new_workbook.content_url) + self.assertEqual(False, new_workbook.show_tabs) + self.assertEqual(1, new_workbook.size) + self.assertEqual('2016-08-18T18:33:24Z', format_datetime(new_workbook.created_at)) + self.assertEqual('2016-08-18T20:31:34Z', format_datetime(new_workbook.updated_at)) + self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_workbook.project_id) + self.assertEqual('default', new_workbook.project_name) + self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_workbook.owner_id) + self.assertEqual('fe0b4e89-73f4-435e-952d-3a263fbfa56c', new_workbook.views[0].id) + self.assertEqual('GDP per capita', new_workbook.views[0].name) + self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) + def test_publish_with_hidden_view(self): with open(PUBLISH_XML, 'rb') as f: response_xml = f.read().decode('utf-8') From ee732154451fb531e126db3853e739573a9f2812 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 08:12:38 -0400 Subject: [PATCH 59/69] Add str into PathOrFile --- .../server/endpoint/datasources_endpoint.py | 8 +++++--- tableauserverclient/server/endpoint/workbooks_endpoint.py | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 252d96829..0df048cb6 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -46,7 +46,9 @@ from ..server import Server from ...models import PermissionsRule -PathOrFile = Union[os.PathLike, io.BufferedReader, io.BytesIO] +FilePath = Union[str, os.PathLike] +FileObject = Union[io.BufferedReader, io.BytesIO] +PathOrFile = Union[FilePath, FileObject] class Datasources(QuerysetEndpoint): @@ -119,7 +121,7 @@ def delete(self, datasource_id: str) -> None: def download( self, datasource_id: str, - filepath: os.PathLike = None, + filepath: FilePath = None, include_extract: bool = True, no_extract: Optional[bool] = None, ) -> str: @@ -323,7 +325,7 @@ def update_hyper_data( *, request_id: str, actions: List[str], - payload: Optional[os.PathLike] = None + payload: Optional[FilePath] = None ) -> JobItem: if isinstance(datasource_or_connection_item, DatasourceItem): datasource_id = datasource_or_connection_item.id diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 61b2a2ec1..a631ae170 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -40,8 +40,9 @@ ALLOWED_FILE_EXTENSIONS = ["twb", "twbx"] logger = logging.getLogger("tableau.endpoint.workbooks") - -PathOrFile = Union[os.PathLike, io.BytesIO, io.BufferedReader] +FilePath = Union[str, os.PathLike] +FileObject = Union[io.BufferedReader, io.BytesIO] +PathOrFile = Union[FilePath, FileObject] class Workbooks(QuerysetEndpoint): @@ -165,7 +166,7 @@ def update_connection(self, workbook_item: WorkbookItem, connection_item: Connec def download( self, workbook_id: str, - filepath: os.PathLike = None, + filepath: FilePath = None, include_extract: bool = True, no_extract: Optional[bool] = None, ) -> str: From 56e8a939582d4210b84b96641f5bcc155cecf641 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 08:24:13 -0400 Subject: [PATCH 60/69] Add type hints to tests --- .github/workflows/run-tests.yml | 2 +- tableauserverclient/models/datasource_item.py | 8 +- tableauserverclient/models/workbook_item.py | 4 +- test/test_datasource.py | 100 ++++++++--------- test/test_regression_tests.py | 2 +- test/test_workbook.py | 102 +++++++++--------- 6 files changed, 109 insertions(+), 109 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index f79869f64..126339854 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -31,4 +31,4 @@ jobs: - name: Run Mypy tests run: | - mypy --show-error-codes --disable-error-code misc --disable-error-code import tableauserverclient + mypy --show-error-codes --disable-error-code misc --disable-error-code import tableauserverclient test diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index e17d27ce3..32b632f8c 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -28,21 +28,21 @@ def __init__(self, project_id: str, name: str = None) -> None: self._certified = None self._certification_note = None self._connections = None - self._content_url = None + self._content_url: Optional[str] = None self._created_at = None self._datasource_type = None self._description = None self._encrypt_extracts = None self._has_extracts = None - self._id = None + self._id: Optional[str] = None self._initial_tags: Set = set() - self._project_name = None + self._project_name: Optional[str] = None self._updated_at = None self._use_remote_query_agent = None self._webpage_url = None self.description = None self.name = name - self.owner_id = None + self.owner_id: Optional[str] = None self.project_id = project_id self.tags: Set = set() diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 9bb561b80..1bbe9a6e8 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -34,7 +34,7 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self._content_url = None self._webpage_url = None self._created_at = None - self._id = None + self._id: Optional[str] = None self._initial_tags: set = set() self._pdf = None self._preview_image = None @@ -44,7 +44,7 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self._views = None self.name = name self._description = None - self.owner_id = None + self.owner_id: Optional[str] = None self.project_id = project_id self.show_tabs = show_tabs self.tags: Set[str] = set() diff --git a/test/test_datasource.py b/test/test_datasource.py index ae587e212..d00c05080 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -28,7 +28,7 @@ class DatasourceTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.server = TSC.Server('http://test') # Fake signin @@ -37,7 +37,7 @@ def setUp(self): self.baseurl = self.server.datasources.baseurl - def test_get(self): + def test_get(self) -> None: response_xml = read_xml_asset(GET_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) @@ -75,11 +75,11 @@ def test_get(self): self.assertFalse(all_datasources[1].has_extracts) self.assertTrue(all_datasources[1].use_remote_query_agent) - def test_get_before_signin(self): + def test_get_before_signin(self) -> None: self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.datasources.get) - def test_get_empty(self): + def test_get_empty(self) -> None: response_xml = read_xml_asset(GET_EMPTY_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) @@ -88,7 +88,7 @@ def test_get_empty(self): self.assertEqual(0, pagination_item.total_available) self.assertEqual([], all_datasources) - def test_get_by_id(self): + def test_get_by_id(self) -> None: response_xml = read_xml_asset(GET_BY_ID_XML) with requests_mock.mock() as m: m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', text=response_xml) @@ -107,7 +107,7 @@ def test_get_by_id(self): self.assertEqual(set(['world', 'indicators', 'sample']), single_datasource.tags) self.assertEqual(TSC.DatasourceItem.AskDataEnablement.SiteDefault, single_datasource.ask_data_enablement) - def test_update(self): + def test_update(self) -> None: 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) @@ -127,7 +127,7 @@ def test_update(self): self.assertEqual(updated_datasource.certified, single_datasource.certified) self.assertEqual(updated_datasource.certification_note, single_datasource.certification_note) - def test_update_copy_fields(self): + def test_update_copy_fields(self) -> None: with open(asset(UPDATE_XML), 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -140,7 +140,7 @@ def test_update_copy_fields(self): self.assertEqual(single_datasource.tags, updated_datasource.tags) self.assertEqual(single_datasource._project_name, updated_datasource._project_name) - def test_update_tags(self): + def test_update_tags(self) -> None: add_tags_xml, update_xml = read_xml_assets(ADD_TAGS_XML, UPDATE_XML) with requests_mock.mock() as m: m.put(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags', text=add_tags_xml) @@ -156,7 +156,7 @@ def test_update_tags(self): self.assertEqual(single_datasource.tags, updated_datasource.tags) self.assertEqual(single_datasource._initial_tags, updated_datasource._initial_tags) - def test_populate_connections(self): + def test_populate_connections(self) -> None: 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) @@ -180,7 +180,7 @@ def test_populate_connections(self): self.assertEqual('heero', ds2.username) self.assertEqual(False, ds2.embed_password) - def test_update_connection(self): + def test_update_connection(self) -> None: populate_xml, response_xml = read_xml_assets(POPULATE_CONNECTIONS_XML, UPDATE_CONNECTION_XML) with requests_mock.mock() as m: @@ -193,7 +193,7 @@ def test_update_connection(self): single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' self.server.datasources.populate_connections(single_datasource) - connection = single_datasource.connections[0] + connection = single_datasource.connections[0] # type: ignore[index] connection.server_address = 'bar' connection.server_port = '9876' connection.username = 'foo' @@ -204,7 +204,7 @@ def test_update_connection(self): self.assertEqual('9876', new_connection.server_port) self.assertEqual('foo', new_connection.username) - def test_populate_permissions(self): + def test_populate_permissions(self) -> None: with open(asset(POPULATE_PERMISSIONS_XML), 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -215,22 +215,22 @@ def test_populate_permissions(self): self.server.datasources.populate_permissions(single_datasource) permissions = single_datasource.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') - self.assertDictEqual(permissions[0].capabilities, { + self.assertEqual(permissions[0].grantee.tag_name, 'group') # type: ignore[index] + self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') # type: ignore[index] + self.assertDictEqual(permissions[0].capabilities, { # type: ignore[index] TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny, TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny, TSC.Permission.Capability.Connect: TSC.Permission.Mode.Allow, TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, }) - self.assertEqual(permissions[1].grantee.tag_name, 'user') - self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') - self.assertDictEqual(permissions[1].capabilities, { + self.assertEqual(permissions[1].grantee.tag_name, 'user') # type: ignore[index] + self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') # type: ignore[index] + self.assertDictEqual(permissions[1].capabilities, { # type: ignore[index] TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, }) - def test_publish(self): + def test_publish(self) -> None: response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) @@ -251,7 +251,7 @@ def test_publish(self): self.assertEqual('default', new_datasource.project_name) self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) - def test_publish_a_non_packaged_file_object(self): + def test_publish_a_non_packaged_file_object(self) -> None: response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) @@ -273,7 +273,7 @@ def test_publish_a_non_packaged_file_object(self): self.assertEqual('default', new_datasource.project_name) self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) - def test_publish_a_packaged_file_object(self): + def test_publish_a_packaged_file_object(self) -> None: response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) @@ -301,7 +301,7 @@ def test_publish_a_packaged_file_object(self): self.assertEqual('default', new_datasource.project_name) self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) - def test_publish_async(self): + def test_publish_async(self) -> None: self.server.version = "3.0" baseurl = self.server.datasources.baseurl response_xml = read_xml_asset(PUBLISH_XML_ASYNC) @@ -321,7 +321,7 @@ def test_publish_async(self): self.assertEqual('2018-06-30T00:54:54Z', format_datetime(new_job.created_at)) self.assertEqual(1, new_job.finish_code) - def test_publish_unnamed_file_object(self): + def test_publish_unnamed_file_object(self) -> None: new_datasource = TSC.DatasourceItem('test') publish_mode = self.server.PublishMode.CreateNew @@ -330,7 +330,7 @@ def test_publish_unnamed_file_object(self): new_datasource, file_object, publish_mode ) - def test_refresh_id(self): + def test_refresh_id(self) -> None: self.server.version = '2.8' self.baseurl = self.server.datasources.baseurl response_xml = read_xml_asset(REFRESH_XML) @@ -345,7 +345,7 @@ def test_refresh_id(self): self.assertEqual('2020-03-05T22:05:32Z', format_datetime(new_job.created_at)) self.assertEqual(-1, new_job.finish_code) - def test_refresh_object(self): + def test_refresh_object(self) -> None: self.server.version = '2.8' self.baseurl = self.server.datasources.baseurl datasource = TSC.DatasourceItem('') @@ -359,7 +359,7 @@ def test_refresh_object(self): # We only check the `id`; remaining fields are already tested in `test_refresh_id` self.assertEqual('7c3d599e-949f-44c3-94a1-f30ba85757e4', new_job.id) - def test_update_hyper_data_datasource_object(self): + def test_update_hyper_data_datasource_object(self) -> None: """Calling `update_hyper_data` with a `DatasourceItem` should update that datasource""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl @@ -378,7 +378,7 @@ def test_update_hyper_data_datasource_object(self): self.assertEqual('2021-09-18T09:40:12Z', format_datetime(new_job.created_at)) self.assertEqual(-1, new_job.finish_code) - def test_update_hyper_data_connection_object(self): + def test_update_hyper_data_connection_object(self) -> None: """Calling `update_hyper_data` with a `ConnectionItem` should update that connection""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl @@ -395,7 +395,7 @@ def test_update_hyper_data_connection_object(self): # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object` self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) - def test_update_hyper_data_datasource_string(self): + def test_update_hyper_data_datasource_string(self) -> None: """For convenience, calling `update_hyper_data` with a `str` should update the datasource with the corresponding UUID""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl @@ -410,7 +410,7 @@ def test_update_hyper_data_datasource_string(self): # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object` self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) - def test_update_hyper_data_datasource_payload_file(self): + def test_update_hyper_data_datasource_payload_file(self) -> None: """If `payload` is present, we upload it and associate the job with it""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl @@ -428,7 +428,7 @@ def test_update_hyper_data_datasource_payload_file(self): # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object` self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) - def test_update_hyper_data_datasource_invalid_payload_file(self): + def test_update_hyper_data_datasource_invalid_payload_file(self) -> None: """If `payload` points to a non-existing file, we report an error""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl @@ -439,12 +439,12 @@ def test_update_hyper_data_datasource_invalid_payload_file(self): exception = cm.exception self.assertEqual(str(exception), "File path does not lead to an existing file.") - def test_delete(self): + def test_delete(self) -> None: with requests_mock.mock() as m: m.delete(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', status_code=204) self.server.datasources.delete('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb') - def test_download(self): + def test_download(self) -> None: with requests_mock.mock() as m: m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content', headers={'Content-Disposition': 'name="tableau_datasource"; filename="Sample datasource.tds"'}) @@ -452,7 +452,7 @@ def test_download(self): self.assertTrue(os.path.exists(file_path)) os.remove(file_path) - def test_download_sanitizes_name(self): + def test_download_sanitizes_name(self) -> None: filename = "Name,With,Commas.tds" disposition = 'name="tableau_workbook"; filename="{}"'.format(filename) with requests_mock.mock() as m: @@ -463,7 +463,7 @@ def test_download_sanitizes_name(self): self.assertTrue(os.path.exists(file_path)) os.remove(file_path) - def test_download_extract_only(self): + def test_download_extract_only(self) -> None: # Pretend we're 2.5 for 'extract_only' self.server.version = "2.5" self.baseurl = self.server.datasources.baseurl @@ -476,32 +476,32 @@ def test_download_extract_only(self): self.assertTrue(os.path.exists(file_path)) os.remove(file_path) - def test_update_missing_id(self): + def test_update_missing_id(self) -> None: 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): + def test_publish_missing_path(self) -> None: 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): + def test_publish_missing_mode(self) -> None: 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): + def test_publish_invalid_file_type(self) -> None: 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): + def test_publish_hyper_file_object_raises_exception(self) -> None: new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') with open(asset('World Indicators.hyper'), 'rb') 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): + def test_publish_tde_file_object_raises_exception(self) -> None: new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') tds_asset = asset(os.path.join('Data', 'Tableau Samples', 'World Indicators.tde')) @@ -509,7 +509,7 @@ def test_publish_tde_file_object_raises_exception(self): 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): + def test_publish_file_object_of_unknown_type_raises_exception(self) -> None: new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') with BytesIO() as file_object: @@ -518,7 +518,7 @@ def test_publish_file_object_of_unknown_type_raises_exception(self): self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, file_object, self.server.PublishMode.Append) - def test_publish_multi_connection(self): + def test_publish_multi_connection(self) -> None: new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') connection1 = TSC.ConnectionItem() connection1.server_address = 'mysql.test.com' @@ -532,11 +532,11 @@ def test_publish_multi_connection(self): connection_results = ET.fromstring(response).findall('.//connection') self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') - self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') + self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') # type: ignore[union-attr] self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') - self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') + self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') # type: ignore[union-attr] - def test_publish_single_connection(self): + def test_publish_single_connection(self) -> None: new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') connection_creds = TSC.ConnectionCredentials('test', 'secret', True) @@ -549,7 +549,7 @@ def test_publish_single_connection(self): self.assertEqual(credentials[0].get('password', None), 'secret') self.assertEqual(credentials[0].get('embed', None), 'true') - def test_credentials_and_multi_connect_raises_exception(self): + def test_credentials_and_multi_connect_raises_exception(self) -> None: new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') connection_creds = TSC.ConnectionCredentials('test', 'secret', True) @@ -563,7 +563,7 @@ def test_credentials_and_multi_connect_raises_exception(self): connection_credentials=connection_creds, connections=[connection1]) - def test_synchronous_publish_timeout_error(self): + def test_synchronous_publish_timeout_error(self) -> None: with requests_mock.mock() as m: m.register_uri('POST', self.baseurl, status_code=504) @@ -574,14 +574,14 @@ def test_synchronous_publish_timeout_error(self): self.server.datasources.publish, new_datasource, asset('SampleDS.tds'), publish_mode) - def test_delete_extracts(self): + def test_delete_extracts(self) -> None: self.server.version = "3.10" self.baseurl = self.server.datasources.baseurl with requests_mock.mock() as m: m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract', status_code=200) self.server.datasources.delete_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') - def test_create_extracts(self): + def test_create_extracts(self) -> None: self.server.version = "3.10" self.baseurl = self.server.datasources.baseurl @@ -591,7 +591,7 @@ def test_create_extracts(self): status_code=200, text=response_xml) self.server.datasources.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') - def test_create_extracts_encrypted(self): + def test_create_extracts_encrypted(self) -> None: self.server.version = "3.10" self.baseurl = self.server.datasources.baseurl diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index 281f3fbca..52ea03b92 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -3,7 +3,7 @@ try: from unittest import mock except ImportError: - import mock + import mock # type: ignore[no-redef] import tableauserverclient.server.request_factory as factory from tableauserverclient.server.endpoint import Endpoint diff --git a/test/test_workbook.py b/test/test_workbook.py index 919d92e21..5ace90b2f 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -38,7 +38,7 @@ class WorkbookTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.server = TSC.Server('http://test') # Fake sign in @@ -47,7 +47,7 @@ def setUp(self): self.baseurl = self.server.workbooks.baseurl - def test_get(self): + def test_get(self) -> None: with open(GET_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -82,7 +82,7 @@ def test_get(self): self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_workbooks[1].owner_id) self.assertEqual(set(['Safari', 'Sample']), all_workbooks[1].tags) - def test_get_ignore_invalid_date(self): + def test_get_ignore_invalid_date(self) -> None: with open(GET_INVALID_DATE_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -91,11 +91,11 @@ def test_get_ignore_invalid_date(self): self.assertEqual(None, format_datetime(all_workbooks[0].created_at)) self.assertEqual('2016-08-04T17:56:41Z', format_datetime(all_workbooks[0].updated_at)) - def test_get_before_signin(self): + def test_get_before_signin(self) -> None: self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.workbooks.get) - def test_get_empty(self): + def test_get_empty(self) -> None: with open(GET_EMPTY_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -105,7 +105,7 @@ def test_get_empty(self): self.assertEqual(0, pagination_item.total_available) self.assertEqual([], all_workbooks) - def test_get_by_id(self): + def test_get_by_id(self) -> None: with open(GET_BY_ID_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -129,7 +129,7 @@ def test_get_by_id(self): self.assertEqual('ENDANGERED SAFARI', single_workbook.views[0].name) self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', single_workbook.views[0].content_url) - def test_get_by_id_personal(self): + def test_get_by_id_personal(self) -> None: # workbooks in personal space don't have project_id or project_name with open(GET_BY_ID_XML_PERSONAL, 'rb') as f: response_xml = f.read().decode('utf-8') @@ -154,10 +154,10 @@ def test_get_by_id_personal(self): self.assertEqual('ENDANGERED SAFARI', single_workbook.views[0].name) self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', single_workbook.views[0].content_url) - def test_get_by_id_missing_id(self): + def test_get_by_id_missing_id(self) -> None: self.assertRaises(ValueError, self.server.workbooks.get_by_id, '') - def test_refresh_id(self): + def test_refresh_id(self) -> None: self.server.version = '2.8' self.baseurl = self.server.workbooks.baseurl with open(REFRESH_XML, 'rb') as f: @@ -167,7 +167,7 @@ def test_refresh_id(self): status_code=202, text=response_xml) self.server.workbooks.refresh('3cc6cd06-89ce-4fdc-b935-5294135d6d42') - def test_refresh_object(self): + def test_refresh_object(self) -> None: self.server.version = '2.8' self.baseurl = self.server.workbooks.baseurl workbook = TSC.WorkbookItem('') @@ -179,15 +179,15 @@ def test_refresh_object(self): status_code=202, text=response_xml) self.server.workbooks.refresh(workbook) - def test_delete(self): + def test_delete(self) -> None: with requests_mock.mock() as m: m.delete(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42', status_code=204) self.server.workbooks.delete('3cc6cd06-89ce-4fdc-b935-5294135d6d42') - def test_delete_missing_id(self): + def test_delete_missing_id(self) -> None: self.assertRaises(ValueError, self.server.workbooks.delete, '') - def test_update(self): + def test_update(self) -> None: with open(UPDATE_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -210,11 +210,11 @@ def test_update(self): self.assertEqual(True, single_workbook.data_acceleration_config['acceleration_enabled']) self.assertEqual(False, single_workbook.data_acceleration_config['accelerate_now']) - def test_update_missing_id(self): + def test_update_missing_id(self) -> None: single_workbook = TSC.WorkbookItem('test') self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.update, single_workbook) - def test_update_copy_fields(self): + def test_update_copy_fields(self) -> None: with open(POPULATE_CONNECTIONS_XML, 'rb') as f: connection_xml = f.read().decode('utf-8') with open(UPDATE_XML, 'rb') as f: @@ -233,7 +233,7 @@ def test_update_copy_fields(self): self.assertEqual(single_workbook._initial_tags, updated_workbook._initial_tags) self.assertEqual(single_workbook._preview_image, updated_workbook._preview_image) - def test_update_tags(self): + def test_update_tags(self) -> None: with open(ADD_TAGS_XML, 'rb') as f: add_tags_xml = f.read().decode('utf-8') with open(UPDATE_XML, 'rb') as f: @@ -252,7 +252,7 @@ def test_update_tags(self): self.assertEqual(single_workbook.tags, updated_workbook.tags) self.assertEqual(single_workbook._initial_tags, updated_workbook._initial_tags) - def test_download(self): + def test_download(self) -> None: with requests_mock.mock() as m: m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/content', headers={'Content-Disposition': 'name="tableau_workbook"; filename="RESTAPISample.twbx"'}) @@ -260,7 +260,7 @@ def test_download(self): self.assertTrue(os.path.exists(file_path)) os.remove(file_path) - def test_download_sanitizes_name(self): + def test_download_sanitizes_name(self) -> None: filename = "Name,With,Commas.twbx" disposition = 'name="tableau_workbook"; filename="{}"'.format(filename) with requests_mock.mock() as m: @@ -271,7 +271,7 @@ def test_download_sanitizes_name(self): self.assertTrue(os.path.exists(file_path)) os.remove(file_path) - def test_download_extract_only(self): + def test_download_extract_only(self) -> None: # Pretend we're 2.5 for 'extract_only' self.server.version = "2.5" self.baseurl = self.server.workbooks.baseurl @@ -285,10 +285,10 @@ def test_download_extract_only(self): self.assertTrue(os.path.exists(file_path)) os.remove(file_path) - def test_download_missing_id(self): + def test_download_missing_id(self) -> None: self.assertRaises(ValueError, self.server.workbooks.download, '') - def test_populate_views(self): + def test_populate_views(self) -> None: with open(POPULATE_VIEWS_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -310,7 +310,7 @@ def test_populate_views(self): self.assertEqual('Interest rates', views_list[2].name) self.assertEqual('RESTAPISample/sheets/Interestrates', views_list[2].content_url) - def test_populate_views_with_usage(self): + def test_populate_views_with_usage(self) -> None: with open(POPULATE_VIEWS_USAGE_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -328,11 +328,11 @@ def test_populate_views_with_usage(self): self.assertEqual('0599c28c-6d82-457e-a453-e52c1bdb00f5', views_list[2].id) self.assertEqual(0, views_list[2].total_views) - def test_populate_views_missing_id(self): + def test_populate_views_missing_id(self) -> None: single_workbook = TSC.WorkbookItem('test') self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_views, single_workbook) - def test_populate_connections(self): + def test_populate_connections(self) -> None: with open(POPULATE_CONNECTIONS_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -346,7 +346,7 @@ def test_populate_connections(self): self.assertEqual('4506225a-0d32-4ab1-82d3-c24e85f7afba', single_workbook.connections[0].datasource_id) self.assertEqual('World Indicators', single_workbook.connections[0].datasource_name) - def test_populate_permissions(self): + def test_populate_permissions(self) -> None: with open(POPULATE_PERMISSIONS_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -375,7 +375,7 @@ def test_populate_permissions(self): TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Deny }) - def test_add_permissions(self): + def test_add_permissions(self) -> None: with open(UPDATE_PERMISSIONS, 'rb') as f: response_xml = f.read().decode('utf-8') @@ -406,13 +406,13 @@ def test_add_permissions(self): TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow }) - def test_populate_connections_missing_id(self): + def test_populate_connections_missing_id(self) -> None: single_workbook = TSC.WorkbookItem('test') self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_connections, single_workbook) - def test_populate_pdf(self): + def test_populate_pdf(self) -> None: self.server.version = "3.4" self.baseurl = self.server.workbooks.baseurl with open(POPULATE_PDF, "rb") as f: @@ -430,7 +430,7 @@ def test_populate_pdf(self): self.server.workbooks.populate_pdf(single_workbook, req_option) self.assertEqual(response, single_workbook.pdf) - def test_populate_preview_image(self): + def test_populate_preview_image(self) -> None: with open(POPULATE_PREVIEW_IMAGE, 'rb') as f: response = f.read() with requests_mock.mock() as m: @@ -441,13 +441,13 @@ def test_populate_preview_image(self): self.assertEqual(response, single_workbook.preview_image) - def test_populate_preview_image_missing_id(self): + def test_populate_preview_image_missing_id(self) -> None: single_workbook = TSC.WorkbookItem('test') self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_preview_image, single_workbook) - def test_publish(self): + def test_publish(self) -> None: with open(PUBLISH_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -478,7 +478,7 @@ def test_publish(self): self.assertEqual('GDP per capita', new_workbook.views[0].name) self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) - def test_publish_a_packaged_file_object(self): + def test_publish_a_packaged_file_object(self) -> None: with open(PUBLISH_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -512,7 +512,7 @@ def test_publish_a_packaged_file_object(self): self.assertEqual('GDP per capita', new_workbook.views[0].name) self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) - def test_publish_non_packeged_file_object(self): + def test_publish_non_packeged_file_object(self) -> None: with open(PUBLISH_XML, 'rb') as f: response_xml = f.read().decode('utf-8') @@ -547,7 +547,7 @@ def test_publish_non_packeged_file_object(self): self.assertEqual('GDP per capita', new_workbook.views[0].name) self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) - def test_publish_path_object(self): + def test_publish_path_object(self) -> None: with open(PUBLISH_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -578,7 +578,7 @@ def test_publish_path_object(self): self.assertEqual('GDP per capita', new_workbook.views[0].name) self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) - def test_publish_with_hidden_view(self): + def test_publish_with_hidden_view(self) -> None: with open(PUBLISH_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -601,7 +601,7 @@ def test_publish_with_hidden_view(self): self.assertTrue(re.search(rb'<\/views>', request_body)) self.assertTrue(re.search(rb'<\/views>', request_body)) - def test_publish_with_query_params(self): + def test_publish_with_query_params(self) -> None: with open(PUBLISH_ASYNC_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -623,7 +623,7 @@ def test_publish_with_query_params(self): self.assertTrue('skipconnectioncheck' in request_query_params) self.assertTrue(request_query_params['skipconnectioncheck']) - def test_publish_async(self): + def test_publish_async(self) -> None: self.server.version = '3.0' baseurl = self.server.workbooks.baseurl with open(PUBLISH_ASYNC_XML, 'rb') as f: @@ -649,18 +649,18 @@ def test_publish_async(self): self.assertEqual('2018-06-29T23:22:32Z', format_datetime(new_job.created_at)) self.assertEqual(1, new_job.finish_code) - def test_publish_invalid_file(self): + def test_publish_invalid_file(self) -> None: new_workbook = TSC.WorkbookItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') self.assertRaises(IOError, self.server.workbooks.publish, new_workbook, '.', self.server.PublishMode.CreateNew) - def test_publish_invalid_file_type(self): + def test_publish_invalid_file_type(self) -> None: new_workbook = TSC.WorkbookItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') self.assertRaises(ValueError, self.server.workbooks.publish, new_workbook, os.path.join(TEST_ASSET_DIR, 'SampleDS.tds'), self.server.PublishMode.CreateNew) - def test_publish_unnamed_file_object(self): + def test_publish_unnamed_file_object(self) -> None: new_workbook = TSC.WorkbookItem('test') with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx'), 'rb') as f: @@ -669,7 +669,7 @@ def test_publish_unnamed_file_object(self): new_workbook, f, self.server.PublishMode.CreateNew ) - def test_publish_non_bytes_file_object(self): + def test_publish_non_bytes_file_object(self) -> None: new_workbook = TSC.WorkbookItem('test') with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx')) as f: @@ -678,7 +678,7 @@ def test_publish_non_bytes_file_object(self): new_workbook, f, self.server.PublishMode.CreateNew ) - def test_publish_file_object_of_unknown_type_raises_exception(self): + def test_publish_file_object_of_unknown_type_raises_exception(self) -> None: new_workbook = TSC.WorkbookItem('test') with BytesIO() as file_object: file_object.write(bytes.fromhex('89504E470D0A1A0A')) @@ -686,7 +686,7 @@ def test_publish_file_object_of_unknown_type_raises_exception(self): self.assertRaises(ValueError, self.server.workbooks.publish, new_workbook, file_object, self.server.PublishMode.CreateNew) - def test_publish_multi_connection(self): + def test_publish_multi_connection(self) -> None: new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') connection1 = TSC.ConnectionItem() @@ -701,11 +701,11 @@ def test_publish_multi_connection(self): connection_results = ET.fromstring(response).findall('.//connection') self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') - self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') + self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') # type: ignore[union-attr] self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') - self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') + self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') # type: ignore[union-attr] - def test_publish_single_connection(self): + def test_publish_single_connection(self) -> None: new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') connection_creds = TSC.ConnectionCredentials('test', 'secret', True) @@ -718,7 +718,7 @@ def test_publish_single_connection(self): self.assertEqual(credentials[0].get('password', None), 'secret') self.assertEqual(credentials[0].get('embed', None), 'true') - def test_credentials_and_multi_connect_raises_exception(self): + def test_credentials_and_multi_connect_raises_exception(self) -> None: new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') @@ -733,7 +733,7 @@ def test_credentials_and_multi_connect_raises_exception(self): connection_credentials=connection_creds, connections=[connection1]) - def test_synchronous_publish_timeout_error(self): + def test_synchronous_publish_timeout_error(self) -> None: with requests_mock.mock() as m: m.register_uri('POST', self.baseurl, status_code=504) @@ -743,14 +743,14 @@ def test_synchronous_publish_timeout_error(self): self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts', self.server.workbooks.publish, new_workbook, asset('SampleWB.twbx'), publish_mode) - def test_delete_extracts_all(self): + def test_delete_extracts_all(self) -> None: self.server.version = "3.10" self.baseurl = self.server.workbooks.baseurl with requests_mock.mock() as m: m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract', status_code=200) self.server.workbooks.delete_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') - def test_create_extracts_all(self): + def test_create_extracts_all(self) -> None: self.server.version = "3.10" self.baseurl = self.server.workbooks.baseurl @@ -761,7 +761,7 @@ def test_create_extracts_all(self): status_code=200, text=response_xml) self.server.workbooks.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') - def test_create_extracts_one(self): + def test_create_extracts_one(self) -> None: self.server.version = "3.10" self.baseurl = self.server.workbooks.baseurl From 9853fa18acb1eb614ab8403358c137ec56a4c377 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Tue, 19 Oct 2021 21:27:10 -0400 Subject: [PATCH 61/69] Add pull request trigger to github actions --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 126339854..2b4039fec 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,6 +1,6 @@ name: Python tests -on: [push] +on: [push, pull_request] jobs: build: From de7037ea69b8f1aa72ce8af0eb33cadd175cd0ac Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 20 Oct 2021 06:54:16 -0400 Subject: [PATCH 62/69] Add str to update_hyper_data type hints --- tableauserverclient/server/endpoint/datasources_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 0df048cb6..2486ceb31 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -321,7 +321,7 @@ def publish( @api(version="3.13") def update_hyper_data( self, - datasource_or_connection_item: Union[DatasourceItem, ConnectionItem], + datasource_or_connection_item: Union[DatasourceItem, ConnectionItem, str], *, request_id: str, actions: List[str], From 13da99d9e3a21a39fe7893ab6f3a97687114fc82 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 20 Oct 2021 08:00:06 -0400 Subject: [PATCH 63/69] Remove unused imports --- tableauserverclient/models/datasource_item.py | 2 +- tableauserverclient/models/workbook_item.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 32b632f8c..3357a336b 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -9,7 +9,7 @@ from ..datetime_helpers import parse_datetime import copy -from typing import Dict, List, Mapping, Optional, Sequence, Set, Tuple, TypeVar, TYPE_CHECKING +from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING if TYPE_CHECKING: from .permissions_item import PermissionsRule diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 1bbe9a6e8..7f43c44ce 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -15,11 +15,9 @@ from typing import ( Dict, List, - Mapping, Optional, Set, TYPE_CHECKING, - Union, ) if TYPE_CHECKING: From 477e67924f3bf3f9a3651ca898c6a452c2243eee Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 20 Oct 2021 08:09:33 -0400 Subject: [PATCH 64/69] Fix actions type hint --- tableauserverclient/server/endpoint/datasources_endpoint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 2486ceb31..16e7bf234 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -25,11 +25,11 @@ from pathlib import Path from typing import ( List, + Mapping, Optional, Sequence, Tuple, TYPE_CHECKING, - TypeVar, Union, ) @@ -324,7 +324,7 @@ def update_hyper_data( datasource_or_connection_item: Union[DatasourceItem, ConnectionItem, str], *, request_id: str, - actions: List[str], + actions: Sequence[Mapping[str, Union[str, Mapping[str, str]]]], payload: Optional[FilePath] = None ) -> JobItem: if isinstance(datasource_or_connection_item, DatasourceItem): From ab49a565ffb825056a9fc28e07ac4553075c649a Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Wed, 20 Oct 2021 20:21:01 -0400 Subject: [PATCH 65/69] Change actions type to Sequence[Mapping] --- tableauserverclient/server/endpoint/datasources_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 16e7bf234..f7a2a4405 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -324,7 +324,7 @@ def update_hyper_data( datasource_or_connection_item: Union[DatasourceItem, ConnectionItem, str], *, request_id: str, - actions: Sequence[Mapping[str, Union[str, Mapping[str, str]]]], + actions: Sequence[Mapping], payload: Optional[FilePath] = None ) -> JobItem: if isinstance(datasource_or_connection_item, DatasourceItem): From a0f4dd2be01995524c6375bda951854f0371c418 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Thu, 21 Oct 2021 08:10:57 -0400 Subject: [PATCH 66/69] Fix tag type hints --- tableauserverclient/models/datasource_item.py | 4 ++-- tableauserverclient/models/workbook_item.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 3357a336b..665be9db1 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -9,7 +9,7 @@ from ..datetime_helpers import parse_datetime import copy -from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING, Union if TYPE_CHECKING: from .permissions_item import PermissionsRule @@ -44,7 +44,7 @@ def __init__(self, project_id: str, name: str = None) -> None: self.name = name self.owner_id: Optional[str] = None self.project_id = project_id - self.tags: Set = set() + self.tags: Set[str] = set() self._permissions = None self._data_quality_warnings = None diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 7f43c44ce..ef0dc6f6f 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -18,6 +18,7 @@ Optional, Set, TYPE_CHECKING, + Union ) if TYPE_CHECKING: From c56edf77ae456c59a2b06e7eb6876187f1d38b3c Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Mon, 25 Oct 2021 22:56:04 -0500 Subject: [PATCH 67/69] Type hint webhooks --- tableauserverclient/models/webhook_item.py | 23 ++++++++++--------- tableauserverclient/models/workbook_item.py | 9 +------- .../server/endpoint/webhooks_endpoint.py | 20 ++++++++++------ tableauserverclient/server/request_factory.py | 22 ++++++++++++++---- test/test_webhook.py | 16 ++++++------- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/tableauserverclient/models/webhook_item.py b/tableauserverclient/models/webhook_item.py index 5fc5c5749..8479a2922 100644 --- a/tableauserverclient/models/webhook_item.py +++ b/tableauserverclient/models/webhook_item.py @@ -2,6 +2,7 @@ import re +from typing import List, Optional, TYPE_CHECKING, Tuple, Type NAMESPACE_RE = re.compile(r"^{.*}") @@ -14,11 +15,11 @@ def _parse_event(events): class WebhookItem(object): def __init__(self): - self._id = None - self.name = None - self.url = None - self._event = None - self.owner_id = None + self._id: Optional[str] = None + self.name: Optional[str] = None + self.url: Optional[str] = None + self._event: Optional[str] = None + self.owner_id: Optional[str] = None def _set_values(self, id, name, url, event, owner_id): if id is not None: @@ -33,21 +34,21 @@ def _set_values(self, id, name, url, event, owner_id): self.owner_id = owner_id @property - def id(self): + def id(self) -> Optional[str]: return self._id @property - def event(self): + def event(self) -> Optional[str]: if self._event: return self._event.replace("webhook-source-event-", "") return None @event.setter - def event(self, value): + def event(self, value: str) -> None: self._event = "webhook-source-event-{}".format(value) @classmethod - def from_response(cls, resp, ns): + def from_response(cls: Type["WebhookItem"], resp: bytes, ns) -> List["WebhookItem"]: all_webhooks_items = list() parsed_response = ET.fromstring(resp) all_webhooks_xml = parsed_response.findall(".//t:webhook", namespaces=ns) @@ -60,7 +61,7 @@ def from_response(cls, resp, ns): return all_webhooks_items @staticmethod - def _parse_element(webhook_xml, ns): + def _parse_element(webhook_xml: ET.Element, ns) -> Tuple: id = webhook_xml.get("id", None) name = webhook_xml.get("name", None) @@ -80,5 +81,5 @@ def _parse_element(webhook_xml, ns): return id, name, url, event, owner_id - def __repr__(self): + def __repr__(self) -> str: 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 ef0dc6f6f..19642f3b1 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -12,14 +12,7 @@ import copy import uuid -from typing import ( - Dict, - List, - Optional, - Set, - TYPE_CHECKING, - Union -) +from typing import Dict, List, Optional, Set, TYPE_CHECKING, Union if TYPE_CHECKING: from .connection_item import ConnectionItem diff --git a/tableauserverclient/server/endpoint/webhooks_endpoint.py b/tableauserverclient/server/endpoint/webhooks_endpoint.py index 6f5135ac1..773f06ed8 100644 --- a/tableauserverclient/server/endpoint/webhooks_endpoint.py +++ b/tableauserverclient/server/endpoint/webhooks_endpoint.py @@ -6,17 +6,23 @@ logger = logging.getLogger("tableau.endpoint.webhooks") +from typing import List, Optional, TYPE_CHECKING, Tuple + +if TYPE_CHECKING: + from ..server import Server + from ..request_options import RequestOptions + class Webhooks(Endpoint): - def __init__(self, parent_srv): + def __init__(self, parent_srv: "Server") -> None: super(Webhooks, self).__init__(parent_srv) @property - def baseurl(self): + def baseurl(self) -> str: return "{0}/sites/{1}/webhooks".format(self.parent_srv.baseurl, self.parent_srv.site_id) @api(version="3.6") - def get(self, req_options=None): + def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[WebhookItem], PaginationItem]: logger.info("Querying all Webhooks on site") url = self.baseurl server_response = self.get_request(url, req_options) @@ -25,7 +31,7 @@ def get(self, req_options=None): return all_webhook_items, pagination_item @api(version="3.6") - def get_by_id(self, webhook_id): + def get_by_id(self, webhook_id: str) -> WebhookItem: if not webhook_id: error = "Webhook ID undefined." raise ValueError(error) @@ -35,7 +41,7 @@ def get_by_id(self, webhook_id): return WebhookItem.from_response(server_response.content, self.parent_srv.namespace)[0] @api(version="3.6") - def delete(self, webhook_id): + def delete(self, webhook_id: str) -> None: if not webhook_id: error = "Webhook ID undefined." raise ValueError(error) @@ -44,7 +50,7 @@ def delete(self, webhook_id): logger.info("Deleted single webhook (ID: {0})".format(webhook_id)) @api(version="3.6") - def create(self, webhook_item): + def create(self, webhook_item: WebhookItem) -> WebhookItem: url = self.baseurl create_req = RequestFactory.Webhook.create_req(webhook_item) server_response = self.post_request(url, create_req) @@ -54,7 +60,7 @@ def create(self, webhook_item): return new_webhook @api(version="3.6") - def test(self, webhook_id): + def test(self, webhook_id: str): if not webhook_id: error = "Webhook ID undefined." raise ValueError(error) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 21db9c484..49821ad44 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -5,6 +5,11 @@ from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ..models import WebhookItem + def _add_multipart(parts): mime_multipart_parts = list() @@ -970,17 +975,26 @@ def empty_req(self, xml_request): class WebhookRequest(object): @_tsrequest_wrapped - def create_req(self, xml_request, webhook_item): + def create_req(self, xml_request: ET.Element, webhook_item: "WebhookItem") -> bytes: webhook = ET.SubElement(xml_request, "webhook") - webhook.attrib["name"] = webhook_item.name + if isinstance(webhook_item.name, str): + webhook.attrib["name"] = webhook_item.name + else: + raise ValueError(f"Name must be provided for {webhook_item}") source = ET.SubElement(webhook, "webhook-source") - ET.SubElement(source, webhook_item._event) + if isinstance(webhook_item._event, str): + ET.SubElement(source, webhook_item._event) + else: + raise ValueError(f"_event for Webhook must be provided. {webhook_item}") destination = ET.SubElement(webhook, "webhook-destination") post = ET.SubElement(destination, "webhook-destination-http") post.attrib["method"] = "POST" - post.attrib["url"] = webhook_item.url + if isinstance(webhook_item.url, str): + post.attrib["url"] = webhook_item.url + else: + raise ValueError(f"URL must be provided on {webhook_item}") return ET.tostring(xml_request) diff --git a/test/test_webhook.py b/test/test_webhook.py index 819de18ae..3b002314f 100644 --- a/test/test_webhook.py +++ b/test/test_webhook.py @@ -14,7 +14,7 @@ class WebhookTests(unittest.TestCase): - def setUp(self): + def setUp(self) -> None: self.server = TSC.Server('http://test') self.server.version = "3.6" @@ -24,7 +24,7 @@ def setUp(self): self.baseurl = self.server.webhooks.baseurl - def test_get(self): + def test_get(self) -> None: with open(GET_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -39,24 +39,24 @@ def test_get(self): self.assertEqual(webhook.name, "webhook-name") self.assertEqual(webhook.id, "webhook-id") - def test_get_before_signin(self): + def test_get_before_signin(self) -> None: self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.webhooks.get) - def test_delete(self): + def test_delete(self) -> None: with requests_mock.mock() as m: m.delete(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) self.server.webhooks.delete('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - def test_delete_missing_id(self): + def test_delete_missing_id(self) -> None: self.assertRaises(ValueError, self.server.webhooks.delete, '') - def test_test(self): + def test_test(self) -> None: with requests_mock.mock() as m: m.get(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760/test', status_code=200) self.server.webhooks.test('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - def test_create(self): + def test_create(self) -> None: with open(CREATE_XML, 'rb') as f: response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: @@ -70,7 +70,7 @@ def test_create(self): self.assertNotEqual(new_webhook.id, None) - def test_request_factory(self): + def test_request_factory(self) -> None: with open(CREATE_REQUEST_XML, 'rb') as f: webhook_request_expected = f.read().decode('utf-8') From f6345809a6d72a5629567370c2d4c7eb09a19448 Mon Sep 17 00:00:00 2001 From: Jordan Woods Date: Thu, 27 Jan 2022 23:04:32 -0600 Subject: [PATCH 68/69] Squashed commit of the following: commit ed940a25227b1bddb64ad980cab5fdcbb0b19606 Author: Jac Date: Thu Jan 27 20:16:31 2022 -0800 make tests run even if code style fails (#980) commit fb49f31078b211ec1a9a385bcd7ee08452980747 Author: jorwoods Date: Thu Jan 27 21:54:31 2022 -0600 Jorwoods/type hint flows (#937) * Type hint Flows * Type hint request_factory for flows * Add ConnectionItem type hints on to Flow publish request * Clean up flows publish signature * Fix flow update_permissions type hint * Fix update_permissions type hint * Add workbook.hidden_views type hint * Update permissions only accepts an iterable * Fix formatting commit f40d4bc7f01cea275b168299b5770460c485b996 Author: jorwoods Date: Thu Jan 27 21:47:53 2022 -0600 Jorwoods/type hint favorites (#936) * Type hint Favorites * Type hint FavoriteRequest commit 4d0142d1b5aa5429d520671357dea70d4823bdb3 Author: jorwoods Date: Thu Jan 27 21:45:14 2022 -0600 Jorwoods/type hint data alert (#934) * Type hint data alerts * Type hint DataAlertRequest * Remove type annotations from Frequency namespace * Update test_dataalert.py commit b836f331f6fa46141526050f60b8d62e591f5f91 Author: Brian Cantoni Date: Thu Jan 27 19:29:50 2022 -0800 WIP: Enable Black for CI and add as dependency (#935) * Enable Black for CI and add as dependency * Bulk reformat with Black, line length 120 Co-authored-by: Jac Fitzgerald commit ad5785175eeae00eb7e978f0557410633697c0f1 Author: jorwoods Date: Thu Nov 11 15:55:01 2021 -0600 Jorwoods/type hint revisions (#956) Add type hints and tests for workbook and data source revisions commit bb0feb8ab558994e9bfff9f339c8401548025848 Author: elsherif Date: Wed Nov 10 01:21:16 2021 +0100 Add support to datasource and workbook revisions (#931) * add support to datasource and workbook revisions (get all item revisions / downlad item by revision number) * add type hints * remove wrong type hints commit 31d420f8509a349de3f01030941dc55e01832767 Author: Stephen Mitchell Date: Tue Nov 9 01:02:14 2021 -0500 Clean up hidden_views by making it an attribute of WorkbookItem (#617) * Clean up hidden views and make it an attribute of workbookitem * Pycodestyle error fix for one letter variable Co-authored-by: Jac Co-authored by: Stephen Mitchell https://github.com/scuml commit 7443f686788b689899b4c02a5a1ef3ee5aba4634 Author: jorwoods Date: Sun Nov 7 05:57:42 2021 -0600 Add type hints to `Job` (#939) commit c8170ae195e39981d2649bacb2e4682e7a92a73d Author: Tyler Doyle Date: Mon Nov 1 20:36:17 2021 -0400 Fix slack once and for all (#946) The red X keeps coming back so I'd like to mark this as allowably fail-able -- that way the innards of Slack/OAuth/Tableau credentials don't keep polluting test run reports :) commit 946469cedf4fc4241a8c139206dc18dddc342ae2 Author: jorwoods Date: Mon Nov 1 14:33:22 2021 -0500 Type hinting on Datasources and Workbooks (#804) Adds types to function signatures for Workbook and Data Source endpoints. Co-authored-by: Jordan Woods commit feed39c2e0d9398d4165c0521c92f4003e874658 Author: Brian Cantoni Date: Tue Oct 26 08:04:10 2021 -0700 Switch to release Python 3.10 release for CI (#927) * Switch to release Python 3.10 release for CI commit 4007d3d84c2c89d74dfead0ff83cec9e096028b5 Author: Jac Date: Fri Oct 22 19:06:20 2021 -0700 Jac/publish samples (#918) * add publish-samples option to create/update project commit a899a959587e2d9f6f027089d814bed00b71bf49 Author: mmuttreja-tableau <87720143+mmuttreja-tableau@users.noreply.github.com> Date: Thu Oct 21 12:10:37 2021 -0400 Adjusting changelog to include missing updates for release 0.17 (#922) commit f81039ae2fc460b9bd18452ffb708e92a068a977 Merge: fefd6f1 46bbe2e Author: mmuttreja-tableau <87720143+mmuttreja-tableau@users.noreply.github.com> Date: Wed Oct 20 17:43:50 2021 -0400 Merge pull request #921 from tableau/development Merging Development to Main branch for releasing v 0.17 commit 46bbe2ede00cc0ae1de383def7e7ca653b36c158 Author: mmuttreja-tableau <87720143+mmuttreja-tableau@users.noreply.github.com> Date: Wed Oct 20 17:26:45 2021 -0400 Update contributors and Changelog for Release 0.17 (#920) * Update CONTRIBUTORS.md & changelog for v 0.17 Update contributors & changelog for v 0.17 commit 428eb550dbeee9aea0e7941f67ceb37819c439fa Author: jorwoods Date: Tue Oct 19 12:32:51 2021 -0500 Add FlowRun Item and Endpoints. (#884) * Add tests for fetching flow runs * Implement basics of FlowRuns * Add tests for cancel flow run * Make FlowRuns a Queryset endpoint for easier filtering * Add test for flow refresh endpoint * Align to naming conventions * Apply name change consistently * Change flowrun_id into flow_run_id * Add wait_for_job to FlowRun * Tag wait_for_job with version number * Rewrite flow_run to use ExponentialBackoffTimer * Test flow run wait with backoff * Remove 3.5 from test matrix * Standardize spelling of cancelled Co-authored-by: Jordan Woods commit fefd6f18d8a6617829c6323879d2c3ed77a4cda6 Merge: 1d8a9be 0845b7b Author: Jac Date: Thu Sep 2 17:49:07 2021 -0700 Merge pull request #880 from tableau/upgrade-slack-action Upgrade to newer Slack action provider commit 0845b7b148eb9aab427549bdd73c92f4abdee8e2 Author: Brian Cantoni Date: Wed Sep 1 14:22:53 2021 -0700 Upgrade to newer Slack action provider commit 1d8a9beacea8949af5bdc698b9deceebaa0dd86b Merge: 5cbfc34 770d48a Author: Ovini Nanayakkara <44311587+ovinis@users.noreply.github.com> Date: Fri Jul 16 12:42:30 2021 -0400 Merge pull request #864 from tableau/development Merging Development to Main branch commit 5cbfc3452453bedac2ef3039fa11b0d59fdacb86 Merge: ec3cc50 ffefd80 Author: Jac Date: Wed Jun 23 23:33:47 2021 -0700 Merge pull request #851 from tableau/jacalata-patch-1 Create slack.yml action commit ffefd80d7985b4881c8dc9f9c6fdb44e952f67a6 Author: Jac Date: Mon May 24 13:34:07 2021 -0700 whitespace change to re-try PR commit 0c4fd4a8201b15300dda8441f280b1d10c6d800d Author: Jac Date: Mon May 24 13:15:49 2021 -0700 Create slack.yml Created a new action from https://github.com/marketplace/actions/send-message-to-slack --- .github/workflows/run-tests.yml | 8 +- .github/workflows/slack.yml | 20 + CHANGELOG.md | 15 + CONTRIBUTORS.md | 1 + contributing.md | 5 + samples/add_default_permission.py | 32 +- samples/create_group.py | 29 +- samples/create_project.py | 49 +- samples/create_schedules.py | 79 +- samples/download_view_image.py | 44 +- samples/explore_datasource.py | 44 +- samples/explore_webhooks.py | 35 +- samples/explore_workbook.py | 50 +- samples/export.py | 60 +- samples/export_wb.py | 41 +- samples/filter_sort_groups.py | 60 +- samples/filter_sort_projects.py | 73 +- samples/initialize_server.py | 43 +- samples/kill_all_jobs.py | 27 +- samples/list.py | 41 +- samples/login.py | 33 +- samples/metadata_query.py | 44 +- samples/move_workbook_projects.py | 36 +- samples/move_workbook_sites.py | 55 +- samples/pagination_sample.py | 27 +- samples/publish_datasource.py | 62 +- samples/publish_workbook.py | 56 +- samples/query_permissions.py | 46 +- samples/refresh.py | 33 +- samples/refresh_tasks.py | 37 +- samples/set_http_options.py | 33 +- samples/set_refresh_schedule.py | 32 +- samples/update_connection.py | 44 +- samples/update_datasource_data.py | 33 +- setup.cfg | 4 - setup.py | 2 +- tableauserverclient/__init__.py | 2 + tableauserverclient/_version.py | 8 +- tableauserverclient/models/__init__.py | 2 + tableauserverclient/models/data_alert_item.py | 80 +- tableauserverclient/models/datasource_item.py | 12 + tableauserverclient/models/favorites_item.py | 35 +- tableauserverclient/models/flow_item.py | 54 +- tableauserverclient/models/flow_run_item.py | 96 ++ tableauserverclient/models/job_item.py | 117 ++- tableauserverclient/models/revision_item.py | 81 ++ tableauserverclient/models/workbook_item.py | 13 + tableauserverclient/server/__init__.py | 2 + .../server/endpoint/__init__.py | 1 + .../server/endpoint/data_alert_endpoint.py | 57 +- .../server/endpoint/datasources_endpoint.py | 75 +- .../server/endpoint/endpoint.py | 3 + .../server/endpoint/exceptions.py | 15 +- .../server/endpoint/favorites_endpoint.py | 43 +- .../server/endpoint/flow_runs_endpoint.py | 74 ++ .../server/endpoint/flows_endpoint.py | 53 +- .../server/endpoint/jobs_endpoint.py | 22 +- .../server/endpoint/projects_endpoint.py | 14 +- .../server/endpoint/workbooks_endpoint.py | 74 +- tableauserverclient/server/request_factory.py | 82 +- tableauserverclient/server/request_options.py | 1 + tableauserverclient/server/server.py | 2 + test/_utils.py | 13 +- test/assets/datasource_revision.xml | 14 + test/assets/flow_refresh.xml | 11 + test/assets/flow_runs_get.xml | 19 + test/assets/flow_runs_get_by_id.xml | 10 + test/assets/flow_runs_get_by_id_failed.xml | 10 + .../assets/flow_runs_get_by_id_inprogress.xml | 10 + test/assets/workbook_revision.xml | 14 + test/test_auth.py | 126 +-- test/test_data_acceleration_report.py | 8 +- test/test_dataalert.py | 126 +-- test/test_database.py | 98 +- test/test_datasource.py | 654 +++++++------ test/test_exponential_backoff.py | 5 +- test/test_favorites.py | 141 ++- test/test_filesys_helpers.py | 48 +- test/test_fileuploads.py | 46 +- test/test_flow.py | 156 +-- test/test_flowruns.py | 101 ++ test/test_group.py | 255 ++--- test/test_job.py | 78 +- test/test_metadata.py | 85 +- test/test_pager.py | 56 +- test/test_project.py | 330 +++---- test/test_regression_tests.py | 39 +- test/test_request_option.py | 185 ++-- test/test_requests.py | 30 +- test/test_schedule.py | 96 +- test/test_server_info.py | 50 +- test/test_site.py | 194 ++-- test/test_sort.py | 78 +- test/test_subscription.py | 63 +- test/test_table.py | 38 +- test/test_tableauauth_model.py | 9 +- test/test_task.py | 71 +- test/test_user.py | 228 +++-- test/test_view.py | 248 ++--- test/test_webhook.py | 43 +- test/test_workbook.py | 921 ++++++++++-------- 101 files changed, 4192 insertions(+), 2866 deletions(-) create mode 100644 .github/workflows/slack.yml create mode 100644 tableauserverclient/models/flow_run_item.py create mode 100644 tableauserverclient/models/revision_item.py create mode 100644 tableauserverclient/server/endpoint/flow_runs_endpoint.py create mode 100644 test/assets/datasource_revision.xml create mode 100644 test/assets/flow_refresh.xml create mode 100644 test/assets/flow_runs_get.xml create mode 100644 test/assets/flow_runs_get_by_id.xml create mode 100644 test/assets/flow_runs_get_by_id_failed.xml create mode 100644 test/assets/flow_runs_get_by_id_inprogress.xml create mode 100644 test/assets/workbook_revision.xml create mode 100644 test/test_flowruns.py diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 2b4039fec..93ba83b5a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0-rc.2] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] runs-on: ${{ matrix.os }} @@ -25,10 +25,16 @@ jobs: python -m pip install --upgrade pip pip install -e .[test] + - name: Format with black + run: | + black --check --line-length 120 tableauserverclient samples test + - name: Test with pytest + if: always() run: | pytest test - name: Run Mypy tests + if: always() run: | mypy --show-error-codes --disable-error-code misc --disable-error-code import tableauserverclient test diff --git a/.github/workflows/slack.yml b/.github/workflows/slack.yml new file mode 100644 index 000000000..b11f4009a --- /dev/null +++ b/.github/workflows/slack.yml @@ -0,0 +1,20 @@ +name: 💬 Send Message to Slack 🚀 + +on: [push, pull_request, issues] + +jobs: + slack-notifications: + continue-on-error: true + runs-on: ubuntu-20.04 + name: Sends a message to Slack when a push, a pull request or an issue is made + steps: + - name: Send message to Slack API + continue-on-error: true + uses: archive/github-actions-slack@v2.2.2 + id: notify + with: + slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} + slack-channel: C019HCX84L9 + slack-text: Hello! Event "${{ github.event_name }}" in "${{ github.repository }}" 🤓 + - name: Result from "Send Message" + run: echo "The result was ${{ steps.notify.outputs.slack-result }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c9197f5..f5c753cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## 0.17.0 (20 October 2021) +* Added support for accepting parameters for post request of the metadata api (#850) +* Fixed jobs.get_by_id(job_id) example & reference docs (#867, #868) +* Fixed handling for workbooks in personal spaces which do not have projectID or Name (#875) +* Updated links to Data Source Methods page in REST API docs (#879) +* Unified arguments of sample scripts (#889) +* Updated docs for - links to Datasource API (#879) , sample scripts (#892) & metadata query (#896) +* Added support for scheduling DataUpdate Jobs (#891) +* Exposed the fileuploads API endpoint (#894) +* Added a new sample & documentation for metadata API (#895, #896) +* Added support to the package for getting flow run status, as well as the ability to cancel flow runs. (#884) +* Added jobs.wait_for_job method (#903) +* Added description support for datasources item (#912) +* Dropped support for Python 3.5 (#911) + ## 0.16.0 (15 July 2021) * Documentation updates (#800, #818, #839, #842) * Fixed data alert repr in subscription item (#821) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 74b20d93d..89b8d213c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -59,3 +59,4 @@ The following people have contributed to this project to make it possible, and w * [Dan Zucker](https://github.com/dzucker-tab) * [Brian Cantoni](https://github.com/bcantoni) * [Ovini Nanayakkara](https://github.com/ovinis) +* [Manish Muttreja](https://github.com/mmuttreja-tableau) diff --git a/contributing.md b/contributing.md index 3d5cd3d43..fc8502b11 100644 --- a/contributing.md +++ b/contributing.md @@ -62,6 +62,11 @@ python setup.py build python setup.py test ``` +### To use your locally built version +```shell +pip install . +``` + ### Before Committing 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. diff --git a/samples/add_default_permission.py b/samples/add_default_permission.py index 8018c7b30..56d3afdf1 100644 --- a/samples/add_default_permission.py +++ b/samples/add_default_permission.py @@ -16,16 +16,23 @@ def main(): - parser = argparse.ArgumentParser(description='Add workbook default permissions for a given project.') + parser = argparse.ArgumentParser(description="Add workbook default permissions for a given project.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -53,10 +60,7 @@ def main(): new_capabilities = {TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Allow} # Each PermissionRule in the list contains a grantee and a dict of capabilities - new_rules = [TSC.PermissionsRule( - grantee=default_permissions.grantee, - capabilities=new_capabilities - )] + new_rules = [TSC.PermissionsRule(grantee=default_permissions.grantee, capabilities=new_capabilities)] new_default_permissions = server.projects.update_workbook_default_permissions(project, new_rules) @@ -78,5 +82,5 @@ def main(): # server.projects.delete(project.id) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/create_group.py b/samples/create_group.py index ad0e6cc4f..16016398d 100644 --- a/samples/create_group.py +++ b/samples/create_group.py @@ -16,16 +16,23 @@ def main(): - parser = argparse.ArgumentParser(description='Creates a sample user group.') + parser = argparse.ArgumentParser(description="Creates a sample user group.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -38,10 +45,10 @@ def main(): tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - group = TSC.GroupItem('test') + group = TSC.GroupItem("test") group = server.groups.create(group) print(group) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/create_project.py b/samples/create_project.py index 814d35617..6271f3d93 100644 --- a/samples/create_project.py +++ b/samples/create_project.py @@ -14,27 +14,34 @@ import tableauserverclient as TSC -def create_project(server, project_item): +def create_project(server, project_item, samples=False): try: - project_item = server.projects.create(project_item) - print('Created a new project called: %s' % project_item.name) + project_item = server.projects.create(project_item, samples) + print("Created a new project called: %s" % project_item.name) return project_item except TSC.ServerResponseError: - print('We have already created this project: %s' % project_item.name) + print("We have already created this project: %s" % project_item.name) sys.exit(1) def main(): - parser = argparse.ArgumentParser(description='Create new projects.') + parser = argparse.ArgumentParser(description="Create new projects.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -45,23 +52,27 @@ def main(): logging.basicConfig(level=logging_level) tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) - server = TSC.Server(args.server, use_server_version=True) + server = TSC.Server(args.server) + server.use_server_version() with server.auth.sign_in(tableau_auth): # Use highest Server REST API version available server.use_server_version() # Without parent_id specified, projects are created at the top level. - top_level_project = TSC.ProjectItem(name='Top Level Project') + top_level_project = TSC.ProjectItem(name="Top Level Project") top_level_project = create_project(server, top_level_project) # Specifying parent_id creates a nested projects. - child_project = TSC.ProjectItem(name='Child Project', parent_id=top_level_project.id) - child_project = create_project(server, child_project) + child_project = TSC.ProjectItem(name="Child Project", parent_id=top_level_project.id) + child_project = create_project(server, child_project, samples=True) # Projects can be nested at any level. - grand_child_project = TSC.ProjectItem(name='Grand Child Project', parent_id=child_project.id) + grand_child_project = TSC.ProjectItem(name="Grand Child Project", parent_id=child_project.id) grand_child_project = create_project(server, grand_child_project) + # Projects can be updated + changed_project = server.projects.update(grand_child_project, samples=True) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/samples/create_schedules.py b/samples/create_schedules.py index 39332713b..420bb59b1 100644 --- a/samples/create_schedules.py +++ b/samples/create_schedules.py @@ -16,16 +16,23 @@ def main(): - parser = argparse.ArgumentParser(description='Creates sample schedules for each type of frequency.') + parser = argparse.ArgumentParser(description="Creates sample schedules for each type of frequency.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -40,43 +47,59 @@ def main(): with server.auth.sign_in(tableau_auth): # Hourly Schedule # This schedule will run every 2 hours between 2:30AM and 11:00PM - hourly_interval = TSC.HourlyInterval(start_time=time(2, 30), - end_time=time(23, 0), - interval_value=2) - - hourly_schedule = TSC.ScheduleItem("Hourly-Schedule", 50, TSC.ScheduleItem.Type.Extract, - TSC.ScheduleItem.ExecutionOrder.Parallel, hourly_interval) + hourly_interval = TSC.HourlyInterval(start_time=time(2, 30), end_time=time(23, 0), interval_value=2) + + hourly_schedule = TSC.ScheduleItem( + "Hourly-Schedule", + 50, + TSC.ScheduleItem.Type.Extract, + TSC.ScheduleItem.ExecutionOrder.Parallel, + hourly_interval, + ) hourly_schedule = server.schedules.create(hourly_schedule) print("Hourly schedule created (ID: {}).".format(hourly_schedule.id)) # Daily Schedule # This schedule will run every day at 5AM daily_interval = TSC.DailyInterval(start_time=time(5)) - daily_schedule = TSC.ScheduleItem("Daily-Schedule", 60, TSC.ScheduleItem.Type.Subscription, - TSC.ScheduleItem.ExecutionOrder.Serial, daily_interval) + daily_schedule = TSC.ScheduleItem( + "Daily-Schedule", + 60, + TSC.ScheduleItem.Type.Subscription, + TSC.ScheduleItem.ExecutionOrder.Serial, + daily_interval, + ) daily_schedule = server.schedules.create(daily_schedule) print("Daily schedule created (ID: {}).".format(daily_schedule.id)) # Weekly Schedule # This schedule will wun every Monday, Wednesday, and Friday at 7:15PM - weekly_interval = TSC.WeeklyInterval(time(19, 15), - TSC.IntervalItem.Day.Monday, - TSC.IntervalItem.Day.Wednesday, - TSC.IntervalItem.Day.Friday) - weekly_schedule = TSC.ScheduleItem("Weekly-Schedule", 70, TSC.ScheduleItem.Type.Extract, - TSC.ScheduleItem.ExecutionOrder.Serial, weekly_interval) + weekly_interval = TSC.WeeklyInterval( + time(19, 15), TSC.IntervalItem.Day.Monday, TSC.IntervalItem.Day.Wednesday, TSC.IntervalItem.Day.Friday + ) + weekly_schedule = TSC.ScheduleItem( + "Weekly-Schedule", + 70, + TSC.ScheduleItem.Type.Extract, + TSC.ScheduleItem.ExecutionOrder.Serial, + weekly_interval, + ) weekly_schedule = server.schedules.create(weekly_schedule) print("Weekly schedule created (ID: {}).".format(weekly_schedule.id)) # Monthly Schedule # This schedule will run on the 15th of every month at 11:30PM - monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30), - interval_value=15) - monthly_schedule = TSC.ScheduleItem("Monthly-Schedule", 80, TSC.ScheduleItem.Type.Subscription, - TSC.ScheduleItem.ExecutionOrder.Parallel, monthly_interval) + monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30), interval_value=15) + monthly_schedule = TSC.ScheduleItem( + "Monthly-Schedule", + 80, + TSC.ScheduleItem.Type.Subscription, + TSC.ScheduleItem.ExecutionOrder.Parallel, + monthly_interval, + ) monthly_schedule = server.schedules.create(monthly_schedule) print("Monthly schedule created (ID: {}).".format(monthly_schedule.id)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/download_view_image.py b/samples/download_view_image.py index 3ac2ed4d5..3b2fbac1c 100644 --- a/samples/download_view_image.py +++ b/samples/download_view_image.py @@ -16,21 +16,27 @@ def main(): - parser = argparse.ArgumentParser(description='Download image of a specified view.') + parser = argparse.ArgumentParser(description="Download image of a specified view.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--view-name', '-vn', required=True, - help='name of view to download an image of') - parser.add_argument('--filepath', '-f', required=True, help='filepath to save the image returned') - parser.add_argument('--maxage', '-m', required=False, help='max age of the image in the cache in minutes.') + parser.add_argument("--view-name", "-vn", required=True, help="name of view to download an image of") + parser.add_argument("--filepath", "-f", required=True, help="filepath to save the image returned") + parser.add_argument("--maxage", "-m", required=False, help="max age of the image in the cache in minutes.") args = parser.parse_args() @@ -44,8 +50,9 @@ def main(): with server.auth.sign_in(tableau_auth): # Step 2: Query for the view that we want an image of req_option = TSC.RequestOptions() - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, args.view_name)) + req_option.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, args.view_name) + ) all_views, pagination_item = server.views.get(req_option) if not all_views: raise LookupError("View with the specified name was not found.") @@ -55,8 +62,9 @@ def main(): if not max_age: max_age = 1 - image_req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High, - maxage=max_age) + image_req_option = TSC.ImageRequestOptions( + imageresolution=TSC.ImageRequestOptions.Resolution.High, maxage=max_age + ) server.views.populate_image(view_item, image_req_option) with open(args.filepath, "wb") as image_file: @@ -65,5 +73,5 @@ def main(): print("View image saved to {0}".format(args.filepath)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/explore_datasource.py b/samples/explore_datasource.py index a78345122..014a274ef 100644 --- a/samples/explore_datasource.py +++ b/samples/explore_datasource.py @@ -17,19 +17,26 @@ def main(): - parser = argparse.ArgumentParser(description='Explore datasource functions supported by the Server API.') + parser = argparse.ArgumentParser(description="Explore datasource functions supported by the Server API.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--publish', metavar='FILEPATH', help='path to datasource to publish') - parser.add_argument('--download', metavar='FILEPATH', help='path to save downloaded datasource') + parser.add_argument("--publish", metavar="FILEPATH", help="path to datasource to publish") + parser.add_argument("--download", metavar="FILEPATH", help="path to save downloaded datasource") args = parser.parse_args() @@ -50,7 +57,8 @@ def main(): if default_project is not None: new_datasource = TSC.DatasourceItem(default_project.id) new_datasource = server.datasources.publish( - new_datasource, args.publish, TSC.Server.PublishMode.Overwrite) + new_datasource, args.publish, TSC.Server.PublishMode.Overwrite + ) print("Datasource published. ID: {}".format(new_datasource.id)) else: print("Publish failed. Could not find the default project.") @@ -67,12 +75,16 @@ def main(): # Populate connections server.datasources.populate_connections(sample_datasource) print("\nConnections for {}: ".format(sample_datasource.name)) - print(["{0}({1})".format(connection.id, connection.datasource_name) - for connection in sample_datasource.connections]) + print( + [ + "{0}({1})".format(connection.id, connection.datasource_name) + for connection in sample_datasource.connections + ] + ) # Add some tags to the datasource original_tag_set = set(sample_datasource.tags) - sample_datasource.tags.update('a', 'b', 'c', 'd') + sample_datasource.tags.update("a", "b", "c", "d") server.datasources.update(sample_datasource) print("\nOld tag set: {}".format(original_tag_set)) print("New tag set: {}".format(sample_datasource.tags)) @@ -82,5 +94,5 @@ def main(): server.datasources.update(sample_datasource) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/explore_webhooks.py b/samples/explore_webhooks.py index 50c677cba..764fb0904 100644 --- a/samples/explore_webhooks.py +++ b/samples/explore_webhooks.py @@ -18,19 +18,26 @@ def main(): - parser = argparse.ArgumentParser(description='Explore webhook functions supported by the Server API.') + parser = argparse.ArgumentParser(description="Explore webhook functions supported by the Server API.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--create', help='create a webhook') - parser.add_argument('--delete', help='delete a webhook', action='store_true') + parser.add_argument("--create", help="create a webhook") + parser.add_argument("--delete", help="delete a webhook", action="store_true") args = parser.parse_args() @@ -63,12 +70,12 @@ def main(): # Pick one webhook from the list and delete it sample_webhook = all_webhooks[0] # sample_webhook.delete() - print("+++"+sample_webhook.name) + print("+++" + sample_webhook.name) - if (args.delete): + if args.delete: print("Deleting webhook " + sample_webhook.name) server.webhooks.delete(sample_webhook.id) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/explore_workbook.py b/samples/explore_workbook.py index 8746db80e..a5a337653 100644 --- a/samples/explore_workbook.py +++ b/samples/explore_workbook.py @@ -18,21 +18,29 @@ def main(): - parser = argparse.ArgumentParser(description='Explore workbook functions supported by the Server API.') + parser = argparse.ArgumentParser(description="Explore workbook functions supported by the Server API.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--publish', metavar='FILEPATH', help='path to workbook to publish') - parser.add_argument('--download', metavar='FILEPATH', help='path to save downloaded workbook') - parser.add_argument('--preview-image', '-i', metavar='FILENAME', - help='filename (a .png file) to save the preview image') + parser.add_argument("--publish", metavar="FILEPATH", help="path to workbook to publish") + parser.add_argument("--download", metavar="FILEPATH", help="path to save downloaded workbook") + parser.add_argument( + "--preview-image", "-i", metavar="FILENAME", help="filename (a .png file) to save the preview image" + ) args = parser.parse_args() @@ -56,7 +64,7 @@ def main(): new_workbook = server.workbooks.publish(new_workbook, args.publish, overwrite_true) print("Workbook published. ID: {}".format(new_workbook.id)) else: - print('Publish failed. Could not find the default project.') + print("Publish failed. Could not find the default project.") # Gets all workbook items all_workbooks, pagination_item = server.workbooks.get() @@ -75,12 +83,16 @@ def main(): # Populate connections server.workbooks.populate_connections(sample_workbook) print("\nConnections for {}: ".format(sample_workbook.name)) - print(["{0}({1})".format(connection.id, connection.datasource_name) - for connection in sample_workbook.connections]) + print( + [ + "{0}({1})".format(connection.id, connection.datasource_name) + for connection in sample_workbook.connections + ] + ) # Update tags and show_tabs flag original_tag_set = set(sample_workbook.tags) - sample_workbook.tags.update('a', 'b', 'c', 'd') + sample_workbook.tags.update("a", "b", "c", "d") sample_workbook.show_tabs = True server.workbooks.update(sample_workbook) print("\nWorkbook's old tag set: {}".format(original_tag_set)) @@ -111,10 +123,10 @@ def main(): if args.preview_image: # Populate workbook preview image server.workbooks.populate_preview_image(sample_workbook) - with open(args.preview_image, 'wb') as f: + with open(args.preview_image, "wb") as f: f.write(sample_workbook.preview_image) print("\nDownloaded preview image of workbook to {}".format(os.path.abspath(args.preview_image))) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/export.py b/samples/export.py index 6317ec53b..b5f2eec43 100644 --- a/samples/export.py +++ b/samples/export.py @@ -12,29 +12,38 @@ def main(): - parser = argparse.ArgumentParser(description='Export a view as an image, PDF, or CSV') + parser = argparse.ArgumentParser(description="Export a view as an image, PDF, or CSV") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('--pdf', dest='type', action='store_const', const=('populate_pdf', 'PDFRequestOptions', 'pdf', - 'pdf')) - group.add_argument('--png', dest='type', action='store_const', const=('populate_image', 'ImageRequestOptions', - 'image', 'png')) - group.add_argument('--csv', dest='type', action='store_const', const=('populate_csv', 'CSVRequestOptions', 'csv', - 'csv')) + group.add_argument( + "--pdf", dest="type", action="store_const", const=("populate_pdf", "PDFRequestOptions", "pdf", "pdf") + ) + group.add_argument( + "--png", dest="type", action="store_const", const=("populate_image", "ImageRequestOptions", "image", "png") + ) + group.add_argument( + "--csv", dest="type", action="store_const", const=("populate_csv", "CSVRequestOptions", "csv", "csv") + ) - parser.add_argument('--file', '-f', help='filename to store the exported data') - parser.add_argument('--filter', '-vf', metavar='COLUMN:VALUE', - help='View filter to apply to the view') - parser.add_argument('resource_id', help='LUID for the view') + parser.add_argument("--file", "-f", help="filename to store the exported data") + parser.add_argument("--filter", "-vf", metavar="COLUMN:VALUE", help="View filter to apply to the view") + parser.add_argument("resource_id", help="LUID for the view") args = parser.parse_args() @@ -45,8 +54,7 @@ def main(): tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - views = filter(lambda x: x.id == args.resource_id, - TSC.Pager(server.views.get)) + views = filter(lambda x: x.id == args.resource_id, TSC.Pager(server.views.get)) view = views.pop() # We have a number of different types and functions for each different export type. @@ -58,21 +66,21 @@ def main(): option_factory = getattr(TSC, option_factory_name) if args.filter: - options = option_factory().vf(*args.filter.split(':')) + options = option_factory().vf(*args.filter.split(":")) else: options = None if args.file: filename = args.file else: - filename = 'out.{}'.format(extension) + filename = "out.{}".format(extension) populate(view, options) - with file(filename, 'wb') as f: - if member_name == 'csv': + with file(filename, "wb") as f: + if member_name == "csv": f.writelines(getattr(view, member_name)) else: f.write(getattr(view, member_name)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/export_wb.py b/samples/export_wb.py index 2be476130..2376ee62b 100644 --- a/samples/export_wb.py +++ b/samples/export_wb.py @@ -16,11 +16,13 @@ import os.path import tableauserverclient as TSC + try: import PyPDF2 except ImportError: - print('Please `pip install PyPDF2` to use this sample') + print("Please `pip install PyPDF2` to use this sample") import sys + sys.exit(1) @@ -34,7 +36,7 @@ def download_pdf(server, tempdir, view): # -> Filename to downloaded pdf logging.info("Exporting {}".format(view.id)) destination_filename = os.path.join(tempdir, view.id) server.views.populate_pdf(view) - with file(destination_filename, 'wb') as f: + with file(destination_filename, "wb") as f: f.write(view.pdf) return destination_filename @@ -50,19 +52,26 @@ def cleanup(tempdir): def main(): - parser = argparse.ArgumentParser(description='Export to PDF all of the views in a workbook.') + parser = argparse.ArgumentParser(description="Export to PDF all of the views in a workbook.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--file', '-f', default='out.pdf', help='filename to store the exported data') - parser.add_argument('resource_id', help='LUID for the workbook') + parser.add_argument("--file", "-f", default="out.pdf", help="filename to store the exported data") + parser.add_argument("resource_id", help="LUID for the workbook") args = parser.parse_args() @@ -70,7 +79,7 @@ def main(): logging_level = getattr(logging, args.logging_level.upper()) logging.basicConfig(level=logging_level) - tempdir = tempfile.mkdtemp('tsc') + tempdir = tempfile.mkdtemp("tsc") logging.debug("Saving to tempdir: %s", tempdir) try: @@ -82,11 +91,11 @@ def main(): downloaded = (download(x) for x in get_list(args.resource_id)) output = reduce(combine_into, downloaded, PyPDF2.PdfFileMerger()) - with file(args.file, 'wb') as f: + with file(args.file, "wb") as f: output.write(f) finally: cleanup(tempdir) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/filter_sort_groups.py b/samples/filter_sort_groups.py index 24dee791d..f883a5a08 100644 --- a/samples/filter_sort_groups.py +++ b/samples/filter_sort_groups.py @@ -12,27 +12,34 @@ import tableauserverclient as TSC -def create_example_group(group_name='Example Group', server=None): +def create_example_group(group_name="Example Group", server=None): new_group = TSC.GroupItem(group_name) try: new_group = server.groups.create(new_group) - print('Created a new project called: \'%s\'' % group_name) + print("Created a new project called: '%s'" % group_name) print(new_group) except TSC.ServerResponseError: - print('Group \'%s\' already existed' % group_name) + print("Group '%s' already existed" % group_name) def main(): - parser = argparse.ArgumentParser(description='Filter and sort groups.') + parser = argparse.ArgumentParser(description="Filter and sort groups.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -46,21 +53,21 @@ def main(): server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - group_name = 'SALES NORTHWEST' + group_name = "SALES NORTHWEST" # Try to create a group named "SALES NORTHWEST" create_example_group(group_name, server) - group_name = 'SALES ROMANIA' + group_name = "SALES ROMANIA" # Try to create a group named "SALES ROMANIA" create_example_group(group_name, server) # URL Encode the name of the group that we want to filter on # i.e. turn spaces into plus signs - filter_group_name = 'SALES+ROMANIA' + filter_group_name = "SALES+ROMANIA" options = TSC.RequestOptions() - options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, - filter_group_name)) + options.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, filter_group_name) + ) filtered_groups, _ = server.groups.get(req_options=options) # Result can either be a matching group or an empty list @@ -72,18 +79,21 @@ def main(): print(error) options = TSC.RequestOptions() - options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.In, - ['SALES+NORTHWEST', 'SALES+ROMANIA', 'this_group'])) + options.filter.add( + TSC.Filter( + TSC.RequestOptions.Field.Name, + TSC.RequestOptions.Operator.In, + ["SALES+NORTHWEST", "SALES+ROMANIA", "this_group"], + ) + ) - options.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Direction.Desc)) + options.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Desc)) matching_groups, pagination_item = server.groups.get(req_options=options) - print('Filtered groups are:') + print("Filtered groups are:") for group in matching_groups: print(group.name) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/filter_sort_projects.py b/samples/filter_sort_projects.py index 23b350fa6..75b13f848 100644 --- a/samples/filter_sort_projects.py +++ b/samples/filter_sort_projects.py @@ -11,29 +11,39 @@ import tableauserverclient as TSC -def create_example_project(name='Example Project', content_permissions='LockedToProject', - description='Project created for testing', server=None): - - new_project = TSC.ProjectItem(name=name, content_permissions=content_permissions, - description=description) +def create_example_project( + name="Example Project", + content_permissions="LockedToProject", + description="Project created for testing", + server=None, +): + + new_project = TSC.ProjectItem(name=name, content_permissions=content_permissions, description=description) try: server.projects.create(new_project) - print('Created a new project called: %s' % name) + print("Created a new project called: %s" % name) except TSC.ServerResponseError: - print('We have already created this resource: %s' % name) + print("We have already created this resource: %s" % name) def main(): - parser = argparse.ArgumentParser(description='Filter and sort projects.') + parser = argparse.ArgumentParser(description="Filter and sort projects.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -49,12 +59,12 @@ def main(): # Use highest Server REST API version available server.use_server_version() - filter_project_name = 'default' + filter_project_name = "default" options = TSC.RequestOptions() - options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, - filter_project_name)) + options.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, filter_project_name) + ) filtered_projects, _ = server.projects.get(req_options=options) # Result can either be a matching project or an empty list @@ -65,26 +75,27 @@ def main(): error = "No project named '{}' found".format(filter_project_name) print(error) - create_example_project(name='Example 1', server=server) - create_example_project(name='Example 2', server=server) - create_example_project(name='Example 3', server=server) - create_example_project(name='Proiect ca Exemplu', server=server) + create_example_project(name="Example 1", server=server) + create_example_project(name="Example 2", server=server) + create_example_project(name="Example 3", server=server) + create_example_project(name="Proiect ca Exemplu", server=server) options = TSC.RequestOptions() # don't forget to URL encode the query names - options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.In, - ['Example+1', 'Example+2', 'Example+3'])) + options.filter.add( + TSC.Filter( + TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In, ["Example+1", "Example+2", "Example+3"] + ) + ) - options.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Direction.Desc)) + options.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Desc)) matching_projects, pagination_item = server.projects.get(req_options=options) - print('Filtered projects are:') + print("Filtered projects are:") for project in matching_projects: print(project.name, project.id) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/initialize_server.py b/samples/initialize_server.py index a7dd552e1..586011120 100644 --- a/samples/initialize_server.py +++ b/samples/initialize_server.py @@ -11,20 +11,27 @@ def main(): - parser = argparse.ArgumentParser(description='Initialize a server with content.') + parser = argparse.ArgumentParser(description="Initialize a server with content.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--datasources-folder', '-df', required=True, help='folder containing datasources') - parser.add_argument('--workbooks-folder', '-wf', required=True, help='folder containing workbooks') - parser.add_argument('--project', required=False, default='Default', help='project to use') + parser.add_argument("--datasources-folder", "-df", required=True, help="folder containing datasources") + parser.add_argument("--workbooks-folder", "-wf", required=True, help="folder containing workbooks") + parser.add_argument("--project", required=False, default="Default", help="project to use") args = parser.parse_args() @@ -50,8 +57,11 @@ def main(): # Create the site if it doesn't exist if existing_site is None: print("Site not found: {0} Creating it...").format(args.site_id) - new_site = TSC.SiteItem(name=args.site_id, content_url=args.site_id.replace(" ", ""), - admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers) + new_site = TSC.SiteItem( + name=args.site_id, + content_url=args.site_id.replace(" ", ""), + admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers, + ) server.sites.create(new_site) else: print("Site {0} exists. Moving on...").format(args.site_id) @@ -70,6 +80,7 @@ def main(): # Step 4: Create the project we need only if it doesn't exist ################################################################################ import time + time.sleep(2) # sad panda...something about eventually consistent model all_projects = TSC.Pager(server_upload.projects) project = next((p for p in all_projects if p.name.lower() == args.project.lower()), None) @@ -90,7 +101,7 @@ def main(): def publish_datasources_to_site(server_object, project, folder): - path = folder + '/*.tds*' + path = folder + "/*.tds*" for fname in glob.glob(path): new_ds = TSC.DatasourceItem(project.id) @@ -99,7 +110,7 @@ def publish_datasources_to_site(server_object, project, folder): def publish_workbooks_to_site(server_object, project, folder): - path = folder + '/*.twb*' + path = folder + "/*.twb*" for fname in glob.glob(path): new_workbook = TSC.WorkbookItem(project.id) diff --git a/samples/kill_all_jobs.py b/samples/kill_all_jobs.py index 196da4b01..02f19d976 100644 --- a/samples/kill_all_jobs.py +++ b/samples/kill_all_jobs.py @@ -11,16 +11,23 @@ def main(): - parser = argparse.ArgumentParser(description='Cancel all of the running background jobs.') + parser = argparse.ArgumentParser(description="Cancel all of the running background jobs.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -40,5 +47,5 @@ def main(): print(server.jobs.cancel(job.id), job.id, job.status, job.type) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/list.py b/samples/list.py index 867757668..7053118a7 100644 --- a/samples/list.py +++ b/samples/list.py @@ -13,18 +13,25 @@ def main(): - parser = argparse.ArgumentParser(description='List out the names and LUIDs for different resource types.') + parser = argparse.ArgumentParser(description="List out the names and LUIDs for different resource types.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-n', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-n", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('resource_type', choices=['workbook', 'datasource', 'project', 'view', 'job', 'webhooks']) + parser.add_argument("resource_type", choices=["workbook", "datasource", "project", "view", "job", "webhooks"]) args = parser.parse_args() @@ -37,17 +44,17 @@ def main(): server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): endpoint = { - 'workbook': server.workbooks, - 'datasource': server.datasources, - 'view': server.views, - 'job': server.jobs, - 'project': server.projects, - 'webhooks': server.webhooks, + "workbook": server.workbooks, + "datasource": server.datasources, + "view": server.views, + "job": server.jobs, + "project": server.projects, + "webhooks": server.webhooks, }.get(args.resource_type) for resource in TSC.Pager(endpoint.get): print(resource.id, resource.name) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/login.py b/samples/login.py index c8af97505..3fa2ab1ae 100644 --- a/samples/login.py +++ b/samples/login.py @@ -12,18 +12,23 @@ def main(): - parser = argparse.ArgumentParser(description='Logs in to the server.') + parser = argparse.ArgumentParser(description="Logs in to the server.") # This command is special, as it doesn't take `token-value` and it offer both token-based and password based authentication. # Please still try to keep common options like `server` and `site` consistent across samples # Common options: - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('--username', '-u', help='username to sign into the server') - group.add_argument('--token-name', '-n', help='name of the personal access token used to sign into the server') + group.add_argument("--username", "-u", help="username to sign into the server") + group.add_argument("--token-name", "-n", help="name of the personal access token used to sign into the server") args = parser.parse_args() @@ -41,19 +46,19 @@ def main(): print("\nSigning in...\nServer: {}\nSite: {}\nUsername: {}".format(args.server, args.site, args.username)) tableau_auth = TSC.TableauAuth(args.username, password, site_id=args.site) with server.auth.sign_in(tableau_auth): - print('Logged in successfully') + print("Logged in successfully") else: # Trying to authenticate using personal access tokens. personal_access_token = getpass.getpass("Personal Access Token: ") - print("\nSigning in...\nServer: {}\nSite: {}\nToken name: {}" - .format(args.server, args.site, args.token_name)) - tableau_auth = TSC.PersonalAccessTokenAuth(token_name=args.token_name, - personal_access_token=personal_access_token, site_id=args.site) + print("\nSigning in...\nServer: {}\nSite: {}\nToken name: {}".format(args.server, args.site, args.token_name)) + tableau_auth = TSC.PersonalAccessTokenAuth( + token_name=args.token_name, personal_access_token=personal_access_token, site_id=args.site + ) with server.auth.sign_in_with_personal_access_token(tableau_auth): - print('Logged in successfully') + print("Logged in successfully") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/metadata_query.py b/samples/metadata_query.py index c9cf7394c..65df9ddb0 100644 --- a/samples/metadata_query.py +++ b/samples/metadata_query.py @@ -12,19 +12,29 @@ def main(): - parser = argparse.ArgumentParser(description='Use the metadata API to get information on a published data source.') + parser = argparse.ArgumentParser(description="Use the metadata API to get information on a published data source.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-n', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-n", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('datasource_name', nargs='?', help="The name of the published datasource. If not present, we query all data sources.") - + parser.add_argument( + "datasource_name", + nargs="?", + help="The name of the published datasource. If not present, we query all data sources.", + ) args = parser.parse_args() @@ -37,7 +47,8 @@ def main(): server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): # Execute the query - result = server.metadata.query(""" + result = server.metadata.query( + """ query useMetadataApiToQueryOrdersDatabases($name: String){ publishedDatasources (filter: {name: $name}) { luid @@ -48,17 +59,20 @@ def main(): name } } - }""", {"name": args.datasource_name}) + }""", + {"name": args.datasource_name}, + ) # Display warnings/errors (if any) if result.get("errors"): print("### Errors/Warnings:") pprint(result["errors"]) - + # Print the results if result.get("data"): print("### Results:") pprint(result["data"]["publishedDatasources"]) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/samples/move_workbook_projects.py b/samples/move_workbook_projects.py index c8227aeda..172112b20 100644 --- a/samples/move_workbook_projects.py +++ b/samples/move_workbook_projects.py @@ -15,19 +15,26 @@ def main(): - parser = argparse.ArgumentParser(description='Move one workbook from the default project to another.') + parser = argparse.ArgumentParser(description="Move one workbook from the default project to another.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--workbook-name', '-w', required=True, help='name of workbook to move') - parser.add_argument('--destination-project', '-d', required=True, help='name of project to move workbook into') + parser.add_argument("--workbook-name", "-w", required=True, help="name of workbook to move") + parser.add_argument("--destination-project", "-d", required=True, help="name of project to move workbook into") args = parser.parse_args() @@ -41,8 +48,9 @@ def main(): with server.auth.sign_in(tableau_auth): # Step 2: Query workbook to move req_option = TSC.RequestOptions() - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, args.workbook_name)) + req_option.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, args.workbook_name) + ) all_workbooks, pagination_item = server.workbooks.get(req_option) # Step 3: Find destination project @@ -64,5 +72,5 @@ def main(): raise LookupError(error) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/move_workbook_sites.py b/samples/move_workbook_sites.py index e0475ac06..c473712e4 100644 --- a/samples/move_workbook_sites.py +++ b/samples/move_workbook_sites.py @@ -17,22 +17,30 @@ def main(): - parser = argparse.ArgumentParser(description="Move one workbook from the" - "default project of the default site to" - "the default project of another site.") + parser = argparse.ArgumentParser( + description="Move one workbook from the" + "default project of the default site to" + "the default project of another site." + ) # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--workbook-name', '-w', required=True, help='name of workbook to move') - parser.add_argument('--destination-site', '-d', required=True, help='name of site to move workbook into') - + parser.add_argument("--workbook-name", "-w", required=True, help="name of workbook to move") + parser.add_argument("--destination-site", "-d", required=True, help="name of site to move workbook into") args = parser.parse_args() @@ -49,13 +57,14 @@ def main(): with source_server.auth.sign_in(tableau_auth): # Step 2: Query workbook to move req_option = TSC.RequestOptions() - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, args.workbook_name)) + req_option.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, args.workbook_name) + ) all_workbooks, pagination_item = source_server.workbooks.get(req_option) # Step 3: Download workbook to a temp directory if len(all_workbooks) == 0: - print('No workbook named {} found.'.format(args.workbook_name)) + print("No workbook named {} found.".format(args.workbook_name)) else: tmpdir = tempfile.mkdtemp() try: @@ -63,8 +72,9 @@ def main(): # Step 4: Check if destination site exists, then sign in to the site all_sites, pagination_info = source_server.sites.get() - found_destination_site = any((True for site in all_sites if - args.destination_site.lower() == site.content_url.lower())) + found_destination_site = any( + (True for site in all_sites if args.destination_site.lower() == site.content_url.lower()) + ) if not found_destination_site: error = "No site named {} found.".format(args.destination_site) raise LookupError(error) @@ -78,8 +88,9 @@ def main(): # Step 5: Create a new workbook item and publish workbook. Note that # an empty project_id will publish to the 'Default' project. new_workbook = TSC.WorkbookItem(name=args.workbook_name, project_id="") - new_workbook = dest_server.workbooks.publish(new_workbook, workbook_path, - mode=TSC.Server.PublishMode.Overwrite) + new_workbook = dest_server.workbooks.publish( + new_workbook, workbook_path, mode=TSC.Server.PublishMode.Overwrite + ) print("Successfully moved {0} ({1})".format(new_workbook.name, new_workbook.id)) # Step 6: Delete workbook from source site and delete temp directory @@ -89,5 +100,5 @@ def main(): shutil.rmtree(tmpdir) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/pagination_sample.py b/samples/pagination_sample.py index 2ebd011dc..5c1a78fd5 100644 --- a/samples/pagination_sample.py +++ b/samples/pagination_sample.py @@ -18,16 +18,23 @@ def main(): - parser = argparse.ArgumentParser(description='Demonstrate pagination on the list of workbooks on the server.') + parser = argparse.ArgumentParser(description="Demonstrate pagination on the list of workbooks on the server.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-n', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-n", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -68,5 +75,5 @@ def main(): # >>> all_workbooks = list(TSC.Pager(server.workbooks, request_options)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/publish_datasource.py b/samples/publish_datasource.py index 8ae744185..ad929fd99 100644 --- a/samples/publish_datasource.py +++ b/samples/publish_datasource.py @@ -25,24 +25,31 @@ def main(): - parser = argparse.ArgumentParser(description='Publish a datasource to server.') + parser = argparse.ArgumentParser(description="Publish a datasource to server.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--file', '-f', required=True, help='filepath to the datasource to publish') - parser.add_argument('--project', help='Project within which to publish the datasource') - parser.add_argument('--async', '-a', help='Publishing asynchronously', dest='async_', action='store_true') - parser.add_argument('--conn-username', help='connection username') - parser.add_argument('--conn-password', help='connection password') - parser.add_argument('--conn-embed', help='embed connection password to datasource', action='store_true') - parser.add_argument('--conn-oauth', help='connection is configured to use oAuth', action='store_true') + parser.add_argument("--file", "-f", required=True, help="filepath to the datasource to publish") + parser.add_argument("--project", help="Project within which to publish the datasource") + parser.add_argument("--async", "-a", help="Publishing asynchronously", dest="async_", action="store_true") + parser.add_argument("--conn-username", help="connection username") + parser.add_argument("--conn-password", help="connection password") + parser.add_argument("--conn-embed", help="embed connection password to datasource", action="store_true") + parser.add_argument("--conn-oauth", help="connection is configured to use oAuth", action="store_true") args = parser.parse_args() @@ -64,9 +71,9 @@ def main(): # Retrieve the project id, if a project name was passed if args.project is not None: req_options = TSC.RequestOptions() - req_options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, - args.project)) + req_options.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, args.project) + ) projects = list(TSC.Pager(server.projects, req_options)) if len(projects) > 1: raise ValueError("The project name is not unique") @@ -78,8 +85,9 @@ def main(): # Create a connection_credentials item if connection details are provided new_conn_creds = None if args.conn_username: - new_conn_creds = TSC.ConnectionCredentials(args.conn_username, args.conn_password, - embed=args.conn_embed, oauth=args.conn_oauth) + new_conn_creds = TSC.ConnectionCredentials( + args.conn_username, args.conn_password, embed=args.conn_embed, oauth=args.conn_oauth + ) # Define publish mode - Overwrite, Append, or CreateNew publish_mode = TSC.Server.PublishMode.Overwrite @@ -87,15 +95,17 @@ def main(): # Publish datasource if args.async_: # Async publishing, returns a job_item - new_job = server.datasources.publish(new_datasource, args.filepath, publish_mode, - connection_credentials=new_conn_creds, as_job=True) + new_job = server.datasources.publish( + new_datasource, args.filepath, publish_mode, connection_credentials=new_conn_creds, as_job=True + ) print("Datasource published asynchronously. Job ID: {0}".format(new_job.id)) else: # Normal publishing, returns a datasource_item - new_datasource = server.datasources.publish(new_datasource, args.filepath, publish_mode, - connection_credentials=new_conn_creds) + new_datasource = server.datasources.publish( + new_datasource, args.filepath, publish_mode, connection_credentials=new_conn_creds + ) print("Datasource published. Datasource ID: {0}".format(new_datasource.id)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/publish_workbook.py b/samples/publish_workbook.py index fcfcddc15..c553eda0b 100644 --- a/samples/publish_workbook.py +++ b/samples/publish_workbook.py @@ -23,21 +23,27 @@ def main(): - parser = argparse.ArgumentParser(description='Publish a workbook to server.') + parser = argparse.ArgumentParser(description="Publish a workbook to server.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('--file', '-f', required=True, help='local filepath of the workbook to publish') - parser.add_argument('--as-job', '-a', help='Publishing asynchronously', action='store_true') - parser.add_argument('--skip-connection-check', '-c', help='Skip live connection check', action='store_true') - + parser.add_argument("--file", "-f", required=True, help="local filepath of the workbook to publish") + parser.add_argument("--as-job", "-a", help="Publishing asynchronously", action="store_true") + parser.add_argument("--skip-connection-check", "-c", help="Skip live connection check", action="store_true") args = parser.parse_args() @@ -72,19 +78,29 @@ def main(): if default_project is not None: new_workbook = TSC.WorkbookItem(default_project.id) if args.as_job: - new_job = server.workbooks.publish(new_workbook, args.filepath, overwrite_true, - connections=all_connections, as_job=args.as_job, - skip_connection_check=args.skip_connection_check) + new_job = server.workbooks.publish( + new_workbook, + args.filepath, + overwrite_true, + connections=all_connections, + as_job=args.as_job, + skip_connection_check=args.skip_connection_check, + ) print("Workbook published. JOB ID: {0}".format(new_job.id)) else: - new_workbook = server.workbooks.publish(new_workbook, args.filepath, overwrite_true, - connections=all_connections, as_job=args.as_job, - skip_connection_check=args.skip_connection_check) + new_workbook = server.workbooks.publish( + new_workbook, + args.filepath, + overwrite_true, + connections=all_connections, + as_job=args.as_job, + skip_connection_check=args.skip_connection_check, + ) print("Workbook published. ID: {0}".format(new_workbook.id)) else: error = "The default project could not be found." raise LookupError(error) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/query_permissions.py b/samples/query_permissions.py index 0909f915d..c0d1c3afa 100644 --- a/samples/query_permissions.py +++ b/samples/query_permissions.py @@ -13,19 +13,26 @@ def main(): - parser = argparse.ArgumentParser(description='Query permissions of a given resource.') + parser = argparse.ArgumentParser(description="Query permissions of a given resource.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('resource_type', choices=['workbook', 'datasource', 'flow', 'table', 'database']) - parser.add_argument('resource_id') + parser.add_argument("resource_type", choices=["workbook", "datasource", "flow", "table", "database"]) + parser.add_argument("resource_id") args = parser.parse_args() @@ -40,11 +47,11 @@ def main(): # Mapping to grab the handler for the user-inputted resource type endpoint = { - 'workbook': server.workbooks, - 'datasource': server.datasources, - 'flow': server.flows, - 'table': server.tables, - 'database': server.databases + "workbook": server.workbooks, + "datasource": server.datasources, + "flow": server.flows, + "table": server.tables, + "database": server.databases, }.get(args.resource_type) # Get the resource by its ID @@ -55,8 +62,9 @@ def main(): permissions = resource.permissions # Print result - print("\n{0} permission rule(s) found for {1} {2}." - .format(len(permissions), args.resource_type, args.resource_id)) + print( + "\n{0} permission rule(s) found for {1} {2}.".format(len(permissions), args.resource_type, args.resource_id) + ) for permission in permissions: grantee = permission.grantee @@ -67,5 +75,5 @@ def main(): print("\t{0} - {1}".format(capability, capabilities[capability])) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/refresh.py b/samples/refresh.py index 3eed5b4be..18a7f36e2 100644 --- a/samples/refresh.py +++ b/samples/refresh.py @@ -11,19 +11,26 @@ def main(): - parser = argparse.ArgumentParser(description='Trigger a refresh task on a workbook or datasource.') + parser = argparse.ArgumentParser(description="Trigger a refresh task on a workbook or datasource.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('resource_type', choices=['workbook', 'datasource']) - parser.add_argument('resource_id') + parser.add_argument("resource_type", choices=["workbook", "datasource"]) + parser.add_argument("resource_id") args = parser.parse_args() @@ -46,7 +53,7 @@ def main(): # trigger the refresh, you'll get a job id back which can be used to poll for when the refresh is done job = server.datasources.refresh(resource) - + print(f"Update job posted (ID: {job.id})") print("Waiting for job...") # `wait_for_job` will throw if the job isn't executed successfully @@ -54,5 +61,5 @@ def main(): print("Job finished succesfully") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/refresh_tasks.py b/samples/refresh_tasks.py index bf69d064a..6ef781544 100644 --- a/samples/refresh_tasks.py +++ b/samples/refresh_tasks.py @@ -28,28 +28,35 @@ def handle_info(server, args): def main(): - parser = argparse.ArgumentParser(description='Get all of the refresh tasks available on a server') + parser = argparse.ArgumentParser(description="Get all of the refresh tasks available on a server") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample subcommands = parser.add_subparsers() - list_arguments = subcommands.add_parser('list') + list_arguments = subcommands.add_parser("list") list_arguments.set_defaults(func=handle_list) - run_arguments = subcommands.add_parser('run') - run_arguments.add_argument('id', default=None) + run_arguments = subcommands.add_parser("run") + run_arguments.add_argument("id", default=None) run_arguments.set_defaults(func=handle_run) - info_arguments = subcommands.add_parser('info') - info_arguments.add_argument('id', default=None) + info_arguments = subcommands.add_parser("info") + info_arguments.add_argument("id", default=None) info_arguments.set_defaults(func=handle_info) args = parser.parse_args() @@ -65,5 +72,5 @@ def main(): args.func(server, args) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/set_http_options.py b/samples/set_http_options.py index 40ed9167e..238da1d7c 100644 --- a/samples/set_http_options.py +++ b/samples/set_http_options.py @@ -13,16 +13,23 @@ def main(): - parser = argparse.ArgumentParser(description='List workbooks on site, with option set to ignore SSL verification.') + parser = argparse.ArgumentParser(description="List workbooks on site, with option set to ignore SSL verification.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample # This sample has no additional options, yet. If you add some, please add them here @@ -37,16 +44,16 @@ def main(): server = TSC.Server(args.server) # Step 2: Set http options to disable verifying SSL - server.add_http_options({'verify': False}) + server.add_http_options({"verify": False}) with server.auth.sign_in(tableau_auth): # Step 3: Query all workbooks and list them all_workbooks, pagination_item = server.workbooks.get() - print('{0} workbooks found. Showing {1}:'.format(pagination_item.total_available, pagination_item.page_size)) + print("{0} workbooks found. Showing {1}:".format(pagination_item.total_available, pagination_item.page_size)) for workbook in all_workbooks: - print('\t{0} (ID: {1})'.format(workbook.name, workbook.id)) + print("\t{0} (ID: {1})".format(workbook.name, workbook.id)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/set_refresh_schedule.py b/samples/set_refresh_schedule.py index 862ea2372..50fccaf2b 100644 --- a/samples/set_refresh_schedule.py +++ b/samples/set_refresh_schedule.py @@ -13,21 +13,28 @@ def usage(args): - parser = argparse.ArgumentParser(description='Set refresh schedule for a workbook or datasource.') + parser = argparse.ArgumentParser(description="Set refresh schedule for a workbook or datasource.") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('--workbook', '-w') - group.add_argument('--datasource', '-d') - parser.add_argument('schedule') + group.add_argument("--workbook", "-w") + group.add_argument("--datasource", "-d") + parser.add_argument("schedule") return parser.parse_args(args) @@ -84,6 +91,7 @@ def run(args): def main(): import sys + args = usage(sys.argv[1:]) run(args) diff --git a/samples/update_connection.py b/samples/update_connection.py index 0e87217e8..44f8ec6c0 100644 --- a/samples/update_connection.py +++ b/samples/update_connection.py @@ -11,22 +11,29 @@ def main(): - parser = argparse.ArgumentParser(description='Update a connection on a datasource or workbook to embed credentials') + parser = argparse.ArgumentParser(description="Update a connection on a datasource or workbook to embed credentials") # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('resource_type', choices=['workbook', 'datasource']) - parser.add_argument('resource_id') - parser.add_argument('connection_id') - parser.add_argument('datasource_username') - parser.add_argument('datasource_password') + parser.add_argument("resource_type", choices=["workbook", "datasource"]) + parser.add_argument("resource_id") + parser.add_argument("connection_id") + parser.add_argument("datasource_username") + parser.add_argument("datasource_password") args = parser.parse_args() @@ -37,16 +44,13 @@ def main(): tableau_auth = TSC.PersonalAccessTokenAuth(args.token_name, args.token_value, site_id=args.site) server = TSC.Server(args.server, use_server_version=True) with server.auth.sign_in(tableau_auth): - endpoint = { - 'workbook': server.workbooks, - 'datasource': server.datasources - }.get(args.resource_type) + endpoint = {"workbook": server.workbooks, "datasource": server.datasources}.get(args.resource_type) update_function = endpoint.update_connection resource = endpoint.get_by_id(args.resource_id) endpoint.populate_connections(resource) connections = list(filter(lambda x: x.id == args.connection_id, resource.connections)) - assert(len(connections) == 1) + assert len(connections) == 1 connection = connections[0] connection.username = args.datasource_username connection.password = args.datasource_password @@ -54,5 +58,5 @@ def main(): print(update_function(resource, connection).__dict__) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/update_datasource_data.py b/samples/update_datasource_data.py index 74c8ea6fb..41f42ee74 100644 --- a/samples/update_datasource_data.py +++ b/samples/update_datasource_data.py @@ -21,18 +21,27 @@ def main(): - parser = argparse.ArgumentParser(description='Delete the `Europe` region from a published `World Indicators` datasource.') + parser = argparse.ArgumentParser( + description="Delete the `Europe` region from a published `World Indicators` datasource." + ) # Common options; please keep those in sync across all samples - parser.add_argument('--server', '-s', required=True, help='server address') - parser.add_argument('--site', '-S', help='site name') - parser.add_argument('--token-name', '-p', required=True, - help='name of the personal access token used to sign into the server') - parser.add_argument('--token-value', '-v', required=True, - help='value of the personal access token used to sign into the server') - parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error', - help='desired logging level (set to error by default)') + parser.add_argument("--server", "-s", required=True, help="server address") + parser.add_argument("--site", "-S", help="site name") + parser.add_argument( + "--token-name", "-p", required=True, help="name of the personal access token used to sign into the server" + ) + parser.add_argument( + "--token-value", "-v", required=True, help="value of the personal access token used to sign into the server" + ) + parser.add_argument( + "--logging-level", + "-l", + choices=["debug", "info", "error"], + default="error", + help="desired logging level (set to error by default)", + ) # Options specific to this sample - parser.add_argument('datasource_id', help="The LUID of the `World Indicators` datasource") + parser.add_argument("datasource_id", help="The LUID of the `World Indicators` datasource") args = parser.parse_args() @@ -61,7 +70,7 @@ def main(): "action": "delete", "target-table": "Extract", "target-schema": "Extract", - "condition": {"op": "eq", "target-col": "Region", "const": {"type": "string", "v": "Europe"}} + "condition": {"op": "eq", "target-col": "Region", "const": {"type": "string", "v": "Europe"}}, } ] @@ -74,5 +83,5 @@ def main(): print("Job finished succesfully") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/setup.cfg b/setup.cfg index 1debabe18..e40e4a8c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,10 +1,6 @@ [wheel] universal = 1 -[pycodestyle] -select = -max_line_length = 120 - [pep8] max_line_length = 120 diff --git a/setup.py b/setup.py index 429e7c09d..fc4d4dcd7 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ # This makes work easier for offline installs or low bandwidth machines needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv) pytest_runner = ['pytest-runner'] if needs_pytest else [] -test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy==0.910'] +test_requirements = ['black', 'mock', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy==0.910'] setup( name='tableauserverclient', diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index fcce4e0c7..a098afad4 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -34,6 +34,8 @@ FlowItem, WebhookItem, PersonalAccessTokenAuth, + FlowRunItem, + RevisionItem, ) from .server import ( RequestOptions, diff --git a/tableauserverclient/_version.py b/tableauserverclient/_version.py index 62c3ce236..d47374097 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/__init__.py b/tableauserverclient/models/__init__.py index c0ddc2e75..8b96b1b7f 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -10,6 +10,7 @@ from .favorites_item import FavoriteItem from .group_item import GroupItem from .flow_item import FlowItem +from .flow_run_item import FlowRunItem from .interval_item import ( IntervalItem, DailyInterval, @@ -35,3 +36,4 @@ from .permissions_item import PermissionsRule, Permission from .webhook_item import WebhookItem from .personal_access_token_auth import PersonalAccessTokenAuth +from .revision_item import RevisionItem diff --git a/tableauserverclient/models/data_alert_item.py b/tableauserverclient/models/data_alert_item.py index a4d11ca5e..62796fd6a 100644 --- a/tableauserverclient/models/data_alert_item.py +++ b/tableauserverclient/models/data_alert_item.py @@ -9,6 +9,12 @@ from .view_item import ViewItem +from typing import List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from datetime import datetime + + class DataAlertItem(object): class Frequency: Once = "Once" @@ -18,35 +24,35 @@ class Frequency: Weekly = "Weekly" def __init__(self): - self._id = None - self._subject = None - self._creatorId = None - self._createdAt = None - self._updatedAt = None - self._frequency = None - self._public = None - self._owner_id = None - self._owner_name = None - self._view_id = None - self._view_name = None - self._workbook_id = None - self._workbook_name = None - self._project_id = None - self._project_name = None - self._recipients = None - - def __repr__(self): + self._id: Optional[str] = None + self._subject: Optional[str] = None + self._creatorId: Optional[str] = None + self._createdAt: Optional["datetime"] = None + self._updatedAt: Optional["datetime"] = None + self._frequency: Optional[str] = None + self._public: Optional[bool] = None + self._owner_id: Optional[str] = None + self._owner_name: Optional[str] = None + self._view_id: Optional[str] = None + self._view_name: Optional[str] = None + self._workbook_id: Optional[str] = None + self._workbook_name: Optional[str] = None + self._project_id: Optional[str] = None + self._project_name: Optional[str] = None + self._recipients: Optional[List[str]] = None + + def __repr__(self) -> str: return "".format( **self.__dict__ ) @property - def id(self): + def id(self) -> Optional[str]: return self._id @property - def subject(self): + def subject(self) -> Optional[str]: return self._subject @subject.setter @@ -55,69 +61,69 @@ def subject(self, value): self._subject = value @property - def frequency(self): + def frequency(self) -> Optional[str]: return self._frequency @frequency.setter @property_is_enum(Frequency) - def frequency(self, value): + def frequency(self, value: str) -> None: self._frequency = value @property - def public(self): + def public(self) -> Optional[bool]: return self._public @public.setter @property_is_boolean - def public(self, value): + def public(self, value: bool) -> None: self._public = value @property - def creatorId(self): + def creatorId(self) -> Optional[str]: return self._creatorId @property - def recipients(self): + def recipients(self) -> List[str]: return self._recipients or list() @property - def createdAt(self): + def createdAt(self) -> Optional["datetime"]: return self._createdAt @property - def updatedAt(self): + def updatedAt(self) -> Optional["datetime"]: return self._updatedAt @property - def owner_id(self): + def owner_id(self) -> Optional[str]: return self._owner_id @property - def owner_name(self): + def owner_name(self) -> Optional[str]: return self._owner_name @property - def view_id(self): + def view_id(self) -> Optional[str]: return self._view_id @property - def view_name(self): + def view_name(self) -> Optional[str]: return self._view_name @property - def workbook_id(self): + def workbook_id(self) -> Optional[str]: return self._workbook_id @property - def workbook_name(self): + def workbook_name(self) -> Optional[str]: return self._workbook_name @property - def project_id(self): + def project_id(self) -> Optional[str]: return self._project_id @property - def project_name(self): + def project_name(self) -> Optional[str]: return self._project_name def _set_values( @@ -173,7 +179,7 @@ def _set_values( self._recipients = recipients @classmethod - def from_response(cls, resp, ns): + def from_response(cls, resp, ns) -> List["DataAlertItem"]: all_alert_items = list() parsed_response = ET.fromstring(resp) all_alert_xml = parsed_response.findall(".//t:dataAlert", namespaces=ns) diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 665be9db1..3a5996dea 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from .permissions_item import PermissionsRule from .connection_item import ConnectionItem + from .revision_item import RevisionItem import datetime @@ -37,6 +38,7 @@ def __init__(self, project_id: str, name: str = None) -> None: self._id: Optional[str] = None self._initial_tags: Set = set() self._project_name: Optional[str] = None + self._revisions = None self._updated_at = None self._use_remote_query_agent = None self._webpage_url = None @@ -166,6 +168,13 @@ def use_remote_query_agent(self, value: bool): def webpage_url(self) -> Optional[str]: return self._webpage_url + @property + def revisions(self) -> List["RevisionItem"]: + if self._revisions is None: + error = "Datasource item must be populated with revisions first." + raise UnpopulatedPropertyError(error) + return self._revisions() + def _set_connections(self, connections): self._connections = connections @@ -175,6 +184,9 @@ def _set_permissions(self, permissions): def _set_data_quality_warnings(self, dqws): self._data_quality_warnings = dqws + def _set_revisions(self, revisions): + self._revisions = revisions + 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/favorites_item.py b/tableauserverclient/models/favorites_item.py index 3d6feff5d..cb3ebdf98 100644 --- a/tableauserverclient/models/favorites_item.py +++ b/tableauserverclient/models/favorites_item.py @@ -4,21 +4,39 @@ from .view_item import ViewItem from .project_item import ProjectItem from .datasource_item import DatasourceItem +from .flow_item import FlowItem logger = logging.getLogger("tableau.models.favorites_item") +from typing import Dict, List, Union + +FavoriteType = Dict[ + str, + List[ + Union[ + DatasourceItem, + ProjectItem, + FlowItem, + ViewItem, + WorkbookItem, + ] + ], +] + class FavoriteItem: class Type: - Workbook = "workbook" - Datasource = "datasource" - View = "view" - Project = "project" + Workbook: str = "workbook" + Datasource: str = "datasource" + View: str = "view" + Project: str = "project" + Flow: str = "flow" @classmethod - def from_response(cls, xml, namespace): - favorites = { + def from_response(cls, xml: str, namespace: Dict) -> FavoriteType: + favorites: FavoriteType = { "datasources": [], + "flows": [], "projects": [], "views": [], "workbooks": [], @@ -45,5 +63,10 @@ def from_response(cls, xml, namespace): fav_project._set_values(*fav_project._parse_element(project)) if fav_project: favorites["projects"].append(fav_project) + for flow in parsed_response.findall(".//t:favorite/t:flow", namespace): + fav_flow = FlowItem("flows") + fav_flow._set_values(*fav_flow._parse_element(flow, namespace)) + if fav_flow: + favorites["flows"].append(fav_flow) return favorites diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index d1387f368..9056144a4 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -5,20 +5,28 @@ from ..datetime_helpers import parse_datetime import copy +from typing import List, Optional, TYPE_CHECKING, Set + +if TYPE_CHECKING: + import datetime + from .connection_item import ConnectionItem + from .permissions_item import Permission + from .dqw_item import DQWItem + class FlowItem(object): - def __init__(self, project_id, name=None): - self._webpage_url = None - self._created_at = None - self._id = None - self._initial_tags = set() - self._project_name = None - self._updated_at = None - self.name = name - self.owner_id = None - self.project_id = project_id - self.tags = set() - self.description = None + def __init__(self, project_id: str, name: Optional[str] = None) -> None: + self._webpage_url: Optional[str] = None + self._created_at: Optional["datetime.datetime"] = None + self._id: Optional[str] = None + self._initial_tags: Set[str] = set() + self._project_name: Optional[str] = None + self._updated_at: Optional["datetime.datetime"] = None + self.name: Optional[str] = name + self.owner_id: Optional[str] = None + self.project_id: str = project_id + self.tags: Set[str] = set() + self.description: Optional[str] = None self._connections = None self._permissions = None @@ -39,11 +47,11 @@ def permissions(self): return self._permissions() @property - def webpage_url(self): + def webpage_url(self) -> Optional[str]: return self._webpage_url @property - def created_at(self): + def created_at(self) -> Optional["datetime.datetime"]: return self._created_at @property @@ -54,36 +62,36 @@ def dqws(self): return self._data_quality_warnings() @property - def id(self): + def id(self) -> Optional[str]: return self._id @property - def project_id(self): + def project_id(self) -> str: return self._project_id @project_id.setter @property_not_nullable - def project_id(self, value): + def project_id(self, value: str) -> None: self._project_id = value @property - def description(self): + def description(self) -> Optional[str]: return self._description @description.setter - def description(self, value): + def description(self, value: str) -> None: self._description = value @property - def project_name(self): + def project_name(self) -> Optional[str]: return self._project_name @property - def flow_type(self): + def flow_type(self): # What is this? It doesn't seem to get set anywhere. return self._flow_type @property - def updated_at(self): + def updated_at(self) -> Optional["datetime.datetime"]: return self._updated_at def _set_connections(self, connections): @@ -161,7 +169,7 @@ def _set_values( self.owner_id = owner_id @classmethod - def from_response(cls, resp, ns): + def from_response(cls, resp, ns) -> List["FlowItem"]: all_flow_items = list() parsed_response = ET.fromstring(resp) all_flow_xml = parsed_response.findall(".//t:flow", namespaces=ns) diff --git a/tableauserverclient/models/flow_run_item.py b/tableauserverclient/models/flow_run_item.py new file mode 100644 index 000000000..a0cbd55ab --- /dev/null +++ b/tableauserverclient/models/flow_run_item.py @@ -0,0 +1,96 @@ +import xml.etree.ElementTree as ET +from ..datetime_helpers import parse_datetime +import itertools + + +class FlowRunItem(object): + def __init__(self) -> None: + self._id = None + self._flow_id = None + self._status = None + self._started_at = None + self._completed_at = None + self._progress = None + self._background_job_id = None + + @property + def id(self): + return self._id + + @property + def flow_id(self): + return self._flow_id + + @property + def status(self): + return self._status + + @property + def started_at(self): + return self._started_at + + @property + def completed_at(self): + return self._completed_at + + @property + def progress(self): + return self._progress + + @property + def background_job_id(self): + return self._background_job_id + + def _set_values( + self, + id, + flow_id, + status, + started_at, + completed_at, + progress, + background_job_id, + ): + if id is not None: + self._id = id + if flow_id is not None: + self._flow_id = flow_id + if status is not None: + self._status = status + if started_at is not None: + self._started_at = started_at + if completed_at is not None: + self._completed_at = completed_at + if progress is not None: + self._progress = progress + if background_job_id is not None: + self._background_job_id = background_job_id + + @classmethod + def from_response(cls, resp, ns): + all_flowrun_items = list() + parsed_response = ET.fromstring(resp) + all_flowrun_xml = itertools.chain( + parsed_response.findall(".//t:flowRun[@id]", namespaces=ns), + parsed_response.findall(".//t:flowRuns[@id]", namespaces=ns), + ) + + for flowrun_xml in all_flowrun_xml: + parsed = cls._parse_element(flowrun_xml, ns) + flowrun_item = cls() + flowrun_item._set_values(**parsed) + all_flowrun_items.append(flowrun_item) + return all_flowrun_items + + @staticmethod + def _parse_element(flowrun_xml, ns): + result = {} + result["id"] = flowrun_xml.get("id", None) + result["flow_id"] = flowrun_xml.get("flowId", None) + result["status"] = flowrun_xml.get("status", None) + result["started_at"] = parse_datetime(flowrun_xml.get("startedAt", None)) + result["completed_at"] = parse_datetime(flowrun_xml.get("completedAt", None)) + result["progress"] = flowrun_xml.get("progress", None) + result["background_job_id"] = flowrun_xml.get("backgroundJobId", None) + + return result diff --git a/tableauserverclient/models/job_item.py b/tableauserverclient/models/job_item.py index f8c00b555..7a2e26170 100644 --- a/tableauserverclient/models/job_item.py +++ b/tableauserverclient/models/job_item.py @@ -1,7 +1,14 @@ import xml.etree.ElementTree as ET +from .flow_run_item import FlowRunItem from ..datetime_helpers import parse_datetime +from typing import List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + import datetime + + class JobItem(object): class FinishCode: """ @@ -15,15 +22,16 @@ class FinishCode: def __init__( self, - id_, - job_type, - progress, - created_at, - started_at=None, - completed_at=None, - finish_code=0, - notes=None, - mode=None, + id_: str, + job_type: str, + progress: str, + created_at: "datetime.datetime", + started_at: Optional["datetime.datetime"] = None, + completed_at: Optional["datetime.datetime"] = None, + finish_code: int = 0, + notes: Optional[List[str]] = None, + mode: Optional[str] = None, + flow_run: Optional[FlowRunItem] = None, ): self._id = id_ self._type = job_type @@ -32,50 +40,59 @@ def __init__( self._started_at = started_at self._completed_at = completed_at self._finish_code = finish_code - self._notes = notes or [] + self._notes: List[str] = notes or [] self._mode = mode + self._flow_run = flow_run @property - def id(self): + def id(self) -> str: return self._id @property - def type(self): + def type(self) -> str: return self._type @property - def progress(self): + def progress(self) -> str: return self._progress @property - def created_at(self): + def created_at(self) -> "datetime.datetime": return self._created_at @property - def started_at(self): + def started_at(self) -> Optional["datetime.datetime"]: return self._started_at @property - def completed_at(self): + def completed_at(self) -> Optional["datetime.datetime"]: return self._completed_at @property - def finish_code(self): + def finish_code(self) -> int: return self._finish_code @property - def notes(self): + def notes(self) -> List[str]: return self._notes @property - def mode(self): + def mode(self) -> Optional[str]: return self._mode @mode.setter - def mode(self, value): + def mode(self, value: str) -> None: # check for valid data here self._mode = value + @property + def flow_run(self): + return self._flow_run + + @flow_run.setter + def flow_run(self, value): + self._flow_run = value + def __repr__(self): return ( " List["JobItem"]: parsed_response = ET.fromstring(xml) all_tasks_xml = parsed_response.findall(".//t:job", namespaces=ns) @@ -102,6 +119,13 @@ def _parse_element(cls, element, ns): finish_code = int(element.get("finishCode", -1)) notes = [note.text for note in element.findall(".//t:notes", namespaces=ns)] or None mode = element.get("mode", None) + flow_run = None + for flow_job in element.findall(".//t:runFlowJobType", namespaces=ns): + flow_run = FlowRunItem() + flow_run._id = flow_job.get("flowRunId", None) + for flow in flow_job.findall(".//t:flow", namespaces=ns): + flow_run._flow_id = flow.get("id", None) + flow_run._started_at = created_at or started_at return cls( id_, type_, @@ -112,28 +136,29 @@ def _parse_element(cls, element, ns): finish_code, notes, mode, + flow_run, ) class BackgroundJobItem(object): class Status: - Pending = "Pending" - InProgress = "InProgress" - Success = "Success" - Failed = "Failed" - Cancelled = "Cancelled" + Pending: str = "Pending" + InProgress: str = "InProgress" + Success: str = "Success" + Failed: str = "Failed" + Cancelled: str = "Cancelled" def __init__( self, - id_, - created_at, - priority, - job_type, - status, - title=None, - subtitle=None, - started_at=None, - ended_at=None, + id_: str, + created_at: "datetime.datetime", + priority: int, + job_type: str, + status: str, + title: Optional[str] = None, + subtitle: Optional[str] = None, + started_at: Optional["datetime.datetime"] = None, + ended_at: Optional["datetime.datetime"] = None, ): self._id = id_ self._type = job_type @@ -146,49 +171,49 @@ def __init__( self._subtitle = subtitle @property - def id(self): + def id(self) -> str: return self._id @property - def name(self): + def name(self) -> Optional[str]: """For API consistency - all other resource endpoints have a name attribute which is used to display what they are. Alias title as name to allow consistent handling of resources in the list sample.""" return self._title @property - def status(self): + def status(self) -> str: return self._status @property - def type(self): + def type(self) -> str: return self._type @property - def created_at(self): + def created_at(self) -> "datetime.datetime": return self._created_at @property - def started_at(self): + def started_at(self) -> Optional["datetime.datetime"]: return self._started_at @property - def ended_at(self): + def ended_at(self) -> Optional["datetime.datetime"]: return self._ended_at @property - def title(self): + def title(self) -> Optional[str]: return self._title @property - def subtitle(self): + def subtitle(self) -> Optional[str]: return self._subtitle @property - def priority(self): + def priority(self) -> int: return self._priority @classmethod - def from_response(cls, xml, ns): + def from_response(cls, xml, ns) -> List["BackgroundJobItem"]: parsed_response = ET.fromstring(xml) all_tasks_xml = parsed_response.findall(".//t:backgroundJob", namespaces=ns) return [cls._parse_element(x, ns) for x in all_tasks_xml] diff --git a/tableauserverclient/models/revision_item.py b/tableauserverclient/models/revision_item.py new file mode 100644 index 000000000..1c4510269 --- /dev/null +++ b/tableauserverclient/models/revision_item.py @@ -0,0 +1,81 @@ +import xml.etree.ElementTree as ET +from ..datetime_helpers import parse_datetime +from typing import List, Optional, TYPE_CHECKING, Type + +if TYPE_CHECKING: + from datetime import datetime + + +class RevisionItem(object): + def __init__(self): + self._resource_id: Optional[str] = None + self._resource_name: Optional[str] = None + self._revision_number: Optional[str] = None + self._current: Optional[bool] = None + self._deleted: Optional[bool] = None + self._created_at: Optional["datetime"] = None + self._user_id: Optional[str] = None + self._user_name: Optional[str] = None + + @property + def resource_id(self) -> Optional[str]: + return self._resource_id + + @property + def resource_name(self) -> Optional[str]: + return self._resource_name + + @property + def revision_number(self) -> Optional[str]: + return self._revision_number + + @property + def current(self) -> Optional[bool]: + return self._current + + @property + def deleted(self) -> Optional[bool]: + return self._deleted + + @property + def created_at(self) -> Optional["datetime"]: + return self._created_at + + @property + def user_id(self) -> Optional[str]: + return self._user_id + + @property + def user_name(self) -> Optional[str]: + return self._user_name + + def __repr__(self): + return ( + "".format(**self.__dict__) + ) + + @classmethod + def from_response(cls, resp: bytes, ns, resource_item) -> List["RevisionItem"]: + all_revision_items = list() + parsed_response = ET.fromstring(resp) + all_revision_xml = parsed_response.findall(".//t:revision", namespaces=ns) + for revision_xml in all_revision_xml: + revision_item = cls() + revision_item._resource_id = resource_item.id + revision_item._resource_name = resource_item.name + revision_item._revision_number = revision_xml.get("revisionNumber", None) + revision_item._current = string_to_bool(revision_xml.get("isCurrent", "")) + revision_item._deleted = string_to_bool(revision_xml.get("isDeleted", "")) + revision_item._created_at = parse_datetime(revision_xml.get("createdAt", None)) + for user in revision_xml.findall(".//t:user", namespaces=ns): + revision_item._user_id = user.get("id", None) + revision_item._user_name = user.get("name", None) + + all_revision_items.append(revision_item) + return all_revision_items + + +# Used to convert string represented boolean to a boolean type +def string_to_bool(s: str) -> bool: + return s.lower() == "true" diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index 19642f3b1..f7ba75c73 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -18,6 +18,7 @@ from .connection_item import ConnectionItem from .permissions_item import PermissionsRule import datetime + from .revision_item import RevisionItem class WorkbookItem(object): @@ -31,6 +32,7 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self._pdf = None self._preview_image = None self._project_name = None + self._revisions = None self._size = None self._updated_at = None self._views = None @@ -39,6 +41,7 @@ def __init__(self, project_id: str, name: str = None, show_tabs: bool = False) - self.owner_id: Optional[str] = None self.project_id = project_id self.show_tabs = show_tabs + self.hidden_views: Optional[List[str]] = None self.tags: Set[str] = set() self.data_acceleration_config = { "acceleration_enabled": None, @@ -154,6 +157,13 @@ def data_acceleration_config(self): def data_acceleration_config(self, value): self._data_acceleration_config = value + @property + def revisions(self) -> List["RevisionItem"]: + if self._revisions is None: + error = "Workbook item must be populated with revisions first." + raise UnpopulatedPropertyError(error) + return self._revisions() + def _set_connections(self, connections): self._connections = connections @@ -169,6 +179,9 @@ def _set_pdf(self, pdf): def _set_preview_image(self, preview_image): self._preview_image = preview_image + def _set_revisions(self, revisions): + self._revisions = revisions + 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) diff --git a/tableauserverclient/server/__init__.py b/tableauserverclient/server/__init__.py index c653a8966..242766ae0 100644 --- a/tableauserverclient/server/__init__.py +++ b/tableauserverclient/server/__init__.py @@ -32,6 +32,8 @@ ColumnItem, FlowItem, WebhookItem, + FlowRunItem, + RevisionItem, ) from .endpoint import ( Auth, diff --git a/tableauserverclient/server/endpoint/__init__.py b/tableauserverclient/server/endpoint/__init__.py index 29fe93299..291cd7bbb 100644 --- a/tableauserverclient/server/endpoint/__init__.py +++ b/tableauserverclient/server/endpoint/__init__.py @@ -7,6 +7,7 @@ from .favorites_endpoint import Favorites from .fileuploads_endpoint import Fileuploads from .flows_endpoint import Flows +from .flow_runs_endpoint import FlowRuns from .exceptions import ( ServerResponseError, MissingRequiredFieldError, diff --git a/tableauserverclient/server/endpoint/data_alert_endpoint.py b/tableauserverclient/server/endpoint/data_alert_endpoint.py index d2e5d55a7..78d28a434 100644 --- a/tableauserverclient/server/endpoint/data_alert_endpoint.py +++ b/tableauserverclient/server/endpoint/data_alert_endpoint.py @@ -1,7 +1,5 @@ from .endpoint import api, Endpoint from .exceptions import MissingRequiredFieldError -from .permissions_endpoint import _PermissionsEndpoint -from .default_permissions_endpoint import _DefaultPermissionsEndpoint from .. import RequestFactory, DataAlertItem, PaginationItem, UserItem @@ -9,17 +7,24 @@ logger = logging.getLogger("tableau.endpoint.dataAlerts") +from typing import List, Optional, TYPE_CHECKING, Tuple, Union + + +if TYPE_CHECKING: + from ..server import Server + from ..request_options import RequestOptions + class DataAlerts(Endpoint): - def __init__(self, parent_srv): + def __init__(self, parent_srv: "Server") -> None: super(DataAlerts, self).__init__(parent_srv) @property - def baseurl(self): + def baseurl(self) -> str: return "{0}/sites/{1}/dataAlerts".format(self.parent_srv.baseurl, self.parent_srv.site_id) @api(version="3.2") - def get(self, req_options=None): + def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[DataAlertItem], PaginationItem]: logger.info("Querying all dataAlerts on site") url = self.baseurl server_response = self.get_request(url, req_options) @@ -29,7 +34,7 @@ def get(self, req_options=None): # Get 1 dataAlert @api(version="3.2") - def get_by_id(self, dataAlert_id): + def get_by_id(self, dataAlert_id: str) -> DataAlertItem: if not dataAlert_id: error = "dataAlert ID undefined." raise ValueError(error) @@ -39,8 +44,13 @@ def get_by_id(self, dataAlert_id): return DataAlertItem.from_response(server_response.content, self.parent_srv.namespace)[0] @api(version="3.2") - def delete(self, dataAlert): - dataAlert_id = getattr(dataAlert, "id", dataAlert) + def delete(self, dataAlert: Union[DataAlertItem, str]) -> None: + if isinstance(dataAlert, DataAlertItem): + dataAlert_id = dataAlert.id + elif isinstance(dataAlert, str): + dataAlert_id = dataAlert + else: + raise TypeError("dataAlert should be a DataAlertItem or a string of an id.") if not dataAlert_id: error = "Dataalert ID undefined." raise ValueError(error) @@ -50,9 +60,19 @@ def delete(self, dataAlert): logger.info("Deleted single dataAlert (ID: {0})".format(dataAlert_id)) @api(version="3.2") - def delete_user_from_alert(self, dataAlert, user): - dataAlert_id = getattr(dataAlert, "id", dataAlert) - user_id = getattr(user, "id", user) + def delete_user_from_alert(self, dataAlert: Union[DataAlertItem, str], user: Union[UserItem, str]) -> None: + if isinstance(dataAlert, DataAlertItem): + dataAlert_id = dataAlert.id + elif isinstance(dataAlert, str): + dataAlert_id = dataAlert + else: + raise TypeError("dataAlert should be a DataAlertItem or a string of an id.") + if isinstance(user, UserItem): + user_id = user.id + elif isinstance(user, str): + user_id = user + else: + raise TypeError("user should be a UserItem or a string of an id.") if not dataAlert_id: error = "Dataalert ID undefined." raise ValueError(error) @@ -65,11 +85,16 @@ def delete_user_from_alert(self, dataAlert, user): logger.info("Deleted User (ID {0}) from dataAlert (ID: {1})".format(user_id, dataAlert_id)) @api(version="3.2") - def add_user_to_alert(self, dataAlert_item, user): + def add_user_to_alert(self, dataAlert_item: DataAlertItem, user: Union[UserItem, str]) -> UserItem: + if isinstance(user, UserItem): + user_id = user.id + elif isinstance(user, str): + user_id = user + else: + raise TypeError("user should be a UserItem or a string of an id.") if not dataAlert_item.id: error = "Dataalert item missing ID." raise MissingRequiredFieldError(error) - user_id = getattr(user, "id", user) if not user_id: error = "User ID undefined." raise ValueError(error) @@ -77,11 +102,11 @@ def add_user_to_alert(self, dataAlert_item, user): update_req = RequestFactory.DataAlert.add_user_to_alert(dataAlert_item, user_id) server_response = self.post_request(url, update_req) logger.info("Added user (ID {0}) to dataAlert item (ID: {1})".format(user_id, dataAlert_item.id)) - user = UserItem.from_response(server_response.content, self.parent_srv.namespace)[0] - return user + added_user = UserItem.from_response(server_response.content, self.parent_srv.namespace)[0] + return added_user @api(version="3.2") - def update(self, dataAlert_item): + def update(self, dataAlert_item: DataAlertItem) -> DataAlertItem: if not dataAlert_item.id: error = "Dataalert item missing ID." raise MissingRequiredFieldError(error) diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index f7a2a4405..d29e52bea 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -12,7 +12,7 @@ get_file_object_size, ) from ...models.job_item import JobItem -from ...models import ConnectionCredentials +from ...models import ConnectionCredentials, RevisionItem import io import os @@ -391,3 +391,76 @@ def add_dqw(self, item, warning): @api(version="3.5") def delete_dqw(self, item): self._data_quality_warnings.clear(item) + + # Populate datasource item's revisions + @api(version="2.3") + def populate_revisions(self, datasource_item: DatasourceItem) -> None: + if not datasource_item.id: + error = "Datasource item missing ID. Datasource must be retrieved from server first." + raise MissingRequiredFieldError(error) + + def revisions_fetcher(): + return self._get_datasource_revisions(datasource_item) + + datasource_item._set_revisions(revisions_fetcher) + logger.info("Populated revisions for datasource (ID: {0})".format(datasource_item.id)) + + def _get_datasource_revisions( + self, datasource_item: DatasourceItem, req_options: Optional["RequestOptions"] = None + ) -> List[RevisionItem]: + url = "{0}/{1}/revisions".format(self.baseurl, datasource_item.id) + server_response = self.get_request(url, req_options) + revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, datasource_item) + return revisions + + # Download 1 datasource revision by revision number + @api(version="2.3") + def download_revision( + self, + datasource_id: str, + revision_number: str, + filepath: Optional[PathOrFile] = None, + include_extract: bool = True, + no_extract: Optional[bool] = None, + ) -> str: + if not datasource_id: + error = "Datasource ID undefined." + raise ValueError(error) + url = "{0}/{1}/revisions/{2}/content".format(self.baseurl, datasource_id, revision_number) + if no_extract is False or no_extract is True: + import warnings + + 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"])) + + download_path = make_download_path(filepath, filename) + + with open(download_path, "wb") as f: + for chunk in server_response.iter_content(1024): # 1KB + f.write(chunk) + + logger.info( + "Downloaded datasource revision {0} to {1} (ID: {2})".format(revision_number, download_path, datasource_id) + ) + return os.path.abspath(download_path) + + @api(version="2.3") + def delete_revision(self, datasource_id: str, revision_number: str) -> None: + if datasource_id is None or revision_number is None: + raise ValueError + url = "/".join([self.baseurl, datasource_id, "revisions", revision_number]) + + self.delete_request(url) + logger.info( + "Deleted single datasource revsision (ID: {0}) (Revision: {1})".format(datasource_id, revision_number) + ) diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 9cc0a6050..73e25cf4d 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -15,6 +15,9 @@ Success_codes = [200, 201, 202, 204] +XML_CONTENT_TYPE = "text/xml" +JSON_CONTENT_TYPE = "application/json" + class Endpoint(object): def __init__(self, parent_srv): diff --git a/tableauserverclient/server/endpoint/exceptions.py b/tableauserverclient/server/endpoint/exceptions.py index 3d39e7102..26de0c3e3 100644 --- a/tableauserverclient/server/endpoint/exceptions.py +++ b/tableauserverclient/server/endpoint/exceptions.py @@ -75,5 +75,18 @@ def __str__(self): return f"Job {self.job.id} failed with notes {self.notes}" -class JobCanceledException(JobFailedException): +class JobCancelledException(JobFailedException): + pass + + +class FlowRunFailedException(Exception): + def __init__(self, flow_run): + self.background_job_id = flow_run.background_job_id + self.flow_run = flow_run + + def __str__(self): + return f"FlowRun {self.flow_run.id} failed with job id {self.background_job_id}" + + +class FlowRunCancelledException(FlowRunFailedException): pass diff --git a/tableauserverclient/server/endpoint/favorites_endpoint.py b/tableauserverclient/server/endpoint/favorites_endpoint.py index 459d852e6..70c1ebfef 100644 --- a/tableauserverclient/server/endpoint/favorites_endpoint.py +++ b/tableauserverclient/server/endpoint/favorites_endpoint.py @@ -1,23 +1,25 @@ from .endpoint import Endpoint, api -from .exceptions import MissingRequiredFieldError from .. import RequestFactory from ...models import FavoriteItem -from ..pager import Pager -import xml.etree.ElementTree as ET import logging -import copy logger = logging.getLogger("tableau.endpoint.favorites") +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from ...models import DatasourceItem, FlowItem, ProjectItem, UserItem, ViewItem, WorkbookItem + from ..request_options import RequestOptions + class Favorites(Endpoint): @property - def baseurl(self): + def baseurl(self) -> str: return "{0}/sites/{1}/favorites".format(self.parent_srv.baseurl, self.parent_srv.site_id) # Gets all favorites @api(version="2.5") - def get(self, user_item, req_options=None): + def get(self, user_item: "UserItem", req_options: Optional["RequestOptions"] = None) -> None: 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) @@ -25,53 +27,66 @@ def get(self, user_item, req_options=None): 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): + def add_favorite_workbook(self, user_item: "UserItem", workbook_item: "WorkbookItem") -> None: 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)) @api(version="2.0") - def add_favorite_view(self, user_item, view_item): + def add_favorite_view(self, user_item: "UserItem", view_item: "ViewItem") -> None: 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)) @api(version="2.3") - def add_favorite_datasource(self, user_item, datasource_item): + def add_favorite_datasource(self, user_item: "UserItem", datasource_item: "DatasourceItem") -> None: 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)) @api(version="3.1") - def add_favorite_project(self, user_item, project_item): + def add_favorite_project(self, user_item: "UserItem", project_item: "ProjectItem") -> None: 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)) + @api(version="3.3") + def add_favorite_flow(self, user_item: "UserItem", flow_item: "FlowItem") -> None: + url = "{0}/{1}".format(self.baseurl, user_item.id) + add_req = RequestFactory.Favorite.add_flow_req(flow_item.id, flow_item.name) + server_response = self.put_request(url, add_req) + logger.info("Favorited {0} for user (ID: {1})".format(flow_item.name, user_item.id)) + @api(version="2.0") - def delete_favorite_workbook(self, user_item, workbook_item): + def delete_favorite_workbook(self, user_item: "UserItem", workbook_item: "WorkbookItem") -> None: 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): + def delete_favorite_view(self, user_item: "UserItem", view_item: "ViewItem") -> None: 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): + def delete_favorite_datasource(self, user_item: "UserItem", datasource_item: "DatasourceItem") -> None: 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): + def delete_favorite_project(self, user_item: "UserItem", project_item: "ProjectItem") -> None: 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) + + @api(version="3.3") + def delete_favorite_flow(self, user_item: "UserItem", flow_item: "FlowItem") -> None: + url = "{0}/{1}/projects/{2}".format(self.baseurl, user_item.id, flow_item.id) + logger.info("Removing favorite {0} for user (ID: {1})".format(flow_item.id, user_item.id)) + self.delete_request(url) diff --git a/tableauserverclient/server/endpoint/flow_runs_endpoint.py b/tableauserverclient/server/endpoint/flow_runs_endpoint.py new file mode 100644 index 000000000..e6784a62c --- /dev/null +++ b/tableauserverclient/server/endpoint/flow_runs_endpoint.py @@ -0,0 +1,74 @@ +from .endpoint import Endpoint, QuerysetEndpoint, api +from .exceptions import FlowRunFailedException, FlowRunCancelledException +from .. import FlowRunItem, PaginationItem +from ...exponential_backoff import ExponentialBackoffTimer + +import logging + +logger = logging.getLogger("tableau.endpoint.flowruns") + + +class FlowRuns(QuerysetEndpoint): + def __init__(self, parent_srv): + super(FlowRuns, self).__init__(parent_srv) + + @property + def baseurl(self): + return "{0}/sites/{1}/flows/runs".format(self.parent_srv.baseurl, self.parent_srv.site_id) + + # Get all flows + @api(version="3.10") + def get(self, req_options=None): + logger.info("Querying all flow runs 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_flow_run_items = FlowRunItem.from_response(server_response.content, self.parent_srv.namespace) + return all_flow_run_items, pagination_item + + # Get 1 flow by id + @api(version="3.10") + def get_by_id(self, flow_run_id): + if not flow_run_id: + error = "Flow ID undefined." + raise ValueError(error) + logger.info("Querying single flow (ID: {0})".format(flow_run_id)) + url = "{0}/{1}".format(self.baseurl, flow_run_id) + server_response = self.get_request(url) + return FlowRunItem.from_response(server_response.content, self.parent_srv.namespace)[0] + + # Cancel 1 flow run by id + @api(version="3.10") + def cancel(self, flow_run_id): + if not flow_run_id: + error = "Flow ID undefined." + raise ValueError(error) + id_ = getattr(flow_run_id, "id", flow_run_id) + url = "{0}/{1}".format(self.baseurl, id_) + self.put_request(url) + logger.info("Deleted single flow (ID: {0})".format(id_)) + + @api(version="3.10") + def wait_for_job(self, flow_run_id, *, timeout=None): + if isinstance(flow_run_id, FlowRunItem): + flow_run_id = flow_run_id.id + assert isinstance(flow_run_id, str) + logger.debug(f"Waiting for flow run {flow_run_id}") + + backoffTimer = ExponentialBackoffTimer(timeout=timeout) + flow_run = self.get_by_id(flow_run_id) + while flow_run.completed_at is None: + backoffTimer.sleep() + flow_run = self.get_by_id(flow_run_id) + logger.debug(f"\tFlowRun {flow_run_id} progress={flow_run.progress}") + + logger.info("FlowRun {} Completed: Status: {}".format(flow_run_id, flow_run.status)) + + if flow_run.status == "Success": + return flow_run + elif flow_run.status == "Failed": + raise FlowRunFailedException(flow_run) + elif flow_run.status == "Cancelled": + raise FlowRunCancelledException(flow_run) + else: + raise AssertionError("Unexpected status in flow_run", flow_run) diff --git a/tableauserverclient/server/endpoint/flows_endpoint.py b/tableauserverclient/server/endpoint/flows_endpoint.py index eb2de4ac9..44c9d9273 100644 --- a/tableauserverclient/server/endpoint/flows_endpoint.py +++ b/tableauserverclient/server/endpoint/flows_endpoint.py @@ -13,6 +13,8 @@ import cgi from contextlib import closing +from typing import Iterable, List, Optional, TYPE_CHECKING, Tuple, Union + # The maximum size of a file that can be published in a single request is 64MB FILESIZE_LIMIT = 1024 * 1024 * 64 # 64MB @@ -20,6 +22,14 @@ logger = logging.getLogger("tableau.endpoint.flows") +if TYPE_CHECKING: + from .. import DQWItem + from ..request_options import RequestOptions + from ...models.permissions_item import Permission, PermissionsRule + + +FilePath = Union[str, os.PathLike] + class Flows(Endpoint): def __init__(self, parent_srv): @@ -29,12 +39,12 @@ def __init__(self, parent_srv): self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "flow") @property - def baseurl(self): + def baseurl(self) -> str: return "{0}/sites/{1}/flows".format(self.parent_srv.baseurl, self.parent_srv.site_id) # Get all flows @api(version="3.3") - def get(self, req_options=None): + def get(self, req_options: Optional["RequestOptions"] = None) -> Tuple[List[FlowItem], PaginationItem]: logger.info("Querying all flows on site") url = self.baseurl server_response = self.get_request(url, req_options) @@ -44,7 +54,7 @@ def get(self, req_options=None): # Get 1 flow by id @api(version="3.3") - def get_by_id(self, flow_id): + def get_by_id(self, flow_id: str) -> FlowItem: if not flow_id: error = "Flow ID undefined." raise ValueError(error) @@ -55,7 +65,7 @@ def get_by_id(self, flow_id): # Populate flow item's connections @api(version="3.3") - def populate_connections(self, flow_item): + def populate_connections(self, flow_item: FlowItem) -> None: if not flow_item.id: error = "Flow item missing ID. Flow must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -66,7 +76,7 @@ def connections_fetcher(): flow_item._set_connections(connections_fetcher) logger.info("Populated connections for flow (ID: {0})".format(flow_item.id)) - def _get_flow_connections(self, flow_item, req_options=None): + def _get_flow_connections(self, flow_item, req_options: Optional["RequestOptions"] = None) -> List[ConnectionItem]: 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) @@ -74,7 +84,7 @@ def _get_flow_connections(self, flow_item, req_options=None): # Delete 1 flow by id @api(version="3.3") - def delete(self, flow_id): + def delete(self, flow_id: str) -> None: if not flow_id: error = "Flow ID undefined." raise ValueError(error) @@ -84,7 +94,7 @@ def delete(self, flow_id): # Download 1 flow by id @api(version="3.3") - def download(self, flow_id, filepath=None): + def download(self, flow_id: str, filepath: FilePath = None) -> str: if not flow_id: error = "Flow ID undefined." raise ValueError(error) @@ -105,7 +115,7 @@ def download(self, flow_id, filepath=None): # Update flow @api(version="3.3") - def update(self, flow_item): + def update(self, flow_item: FlowItem) -> FlowItem: if not flow_item.id: error = "Flow item missing ID. Flow must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -122,7 +132,7 @@ def update(self, flow_item): # Update flow connections @api(version="3.3") - def update_connection(self, flow_item, connection_item): + def update_connection(self, flow_item: FlowItem, connection_item: ConnectionItem) -> ConnectionItem: url = "{0}/{1}/connections/{2}".format(self.baseurl, flow_item.id, connection_item.id) update_req = RequestFactory.Connection.update_req(connection_item) @@ -133,7 +143,7 @@ def update_connection(self, flow_item, connection_item): return connection @api(version="3.3") - def refresh(self, flow_item): + def refresh(self, flow_item: FlowItem) -> JobItem: url = "{0}/{1}/run".format(self.baseurl, flow_item.id) empty_req = RequestFactory.Empty.empty_req() server_response = self.post_request(url, empty_req) @@ -142,7 +152,9 @@ def refresh(self, flow_item): # Publish flow @api(version="3.3") - def publish(self, flow_item, file_path, mode, connections=None): + def publish( + self, flow_item: FlowItem, file_path: FilePath, mode: str, connections: Optional[List[ConnectionItem]] = None + ) -> FlowItem: if not os.path.isfile(file_path): error = "File path does not lead to an existing file." raise IOError(error) @@ -189,13 +201,8 @@ def publish(self, flow_item, file_path, mode, connections=None): 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)) - return new_flow - @api(version="3.3") - def populate_permissions(self, item): + def populate_permissions(self, item: FlowItem) -> None: self._permissions.populate(item) @api(version="3.3") @@ -209,25 +216,25 @@ def update_permission(self, item, permission_item): self._permissions.update(item, permission_item) @api(version="3.3") - def update_permissions(self, item, permission_item): + def update_permissions(self, item: FlowItem, permission_item: Iterable["PermissionsRule"]) -> None: self._permissions.update(item, permission_item) @api(version="3.3") - def delete_permission(self, item, capability_item): + def delete_permission(self, item: FlowItem, capability_item: "PermissionsRule") -> None: self._permissions.delete(item, capability_item) @api(version="3.5") - def populate_dqw(self, item): + def populate_dqw(self, item: FlowItem) -> None: self._data_quality_warnings.populate(item) @api(version="3.5") - def update_dqw(self, item, warning): + def update_dqw(self, item: FlowItem, warning: "DQWItem") -> None: return self._data_quality_warnings.update(item, warning) @api(version="3.5") - def add_dqw(self, item, warning): + def add_dqw(self, item: FlowItem, warning: "DQWItem") -> None: return self._data_quality_warnings.add(item, warning) @api(version="3.5") - def delete_dqw(self, item): + def delete_dqw(self, item: FlowItem) -> None: self._data_quality_warnings.clear(item) diff --git a/tableauserverclient/server/endpoint/jobs_endpoint.py b/tableauserverclient/server/endpoint/jobs_endpoint.py index ab0002ac0..6049f9ce1 100644 --- a/tableauserverclient/server/endpoint/jobs_endpoint.py +++ b/tableauserverclient/server/endpoint/jobs_endpoint.py @@ -1,5 +1,5 @@ from .endpoint import Endpoint, api -from .exceptions import JobCanceledException, JobFailedException +from .exceptions import JobCancelledException, JobFailedException from .. import JobItem, BackgroundJobItem, PaginationItem from ..request_options import RequestOptionsBase from ...exponential_backoff import ExponentialBackoffTimer @@ -8,6 +8,8 @@ logger = logging.getLogger("tableau.endpoint.jobs") +from typing import List, Optional, Tuple, TYPE_CHECKING, Union + class Jobs(Endpoint): @property @@ -15,7 +17,9 @@ def baseurl(self): return "{0}/sites/{1}/jobs".format(self.parent_srv.baseurl, self.parent_srv.site_id) @api(version="2.6") - def get(self, job_id=None, req_options=None): + def get( + self, job_id: Optional[str] = None, req_options: Optional[RequestOptionsBase] = None + ) -> Tuple[List[BackgroundJobItem], PaginationItem]: # Backwards Compatibility fix until we rev the major version if job_id is not None and isinstance(job_id, str): import warnings @@ -32,20 +36,22 @@ def get(self, job_id=None, req_options=None): return jobs, pagination_item @api(version="3.1") - def cancel(self, job_id): - id_ = getattr(job_id, "id", job_id) - url = "{0}/{1}".format(self.baseurl, id_) + def cancel(self, job_id: Union[str, JobItem]): + if isinstance(job_id, JobItem): + job_id = job_id.id + assert isinstance(job_id, str) + url = "{0}/{1}".format(self.baseurl, job_id) return self.put_request(url) @api(version="2.6") - def get_by_id(self, job_id): + def get_by_id(self, job_id: str) -> JobItem: 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] return new_job - def wait_for_job(self, job_id, *, timeout=None): + def wait_for_job(self, job_id: Union[str, JobItem], *, timeout: Optional[float] = None) -> JobItem: if isinstance(job_id, JobItem): job_id = job_id.id assert isinstance(job_id, str) @@ -65,6 +71,6 @@ def wait_for_job(self, job_id, *, timeout=None): elif job.finish_code == JobItem.FinishCode.Failed: raise JobFailedException(job) elif job.finish_code == JobItem.FinishCode.Cancelled: - raise JobCanceledException(job) + raise JobCancelledException(job) else: raise AssertionError("Unexpected finish_code in job", job) diff --git a/tableauserverclient/server/endpoint/projects_endpoint.py b/tableauserverclient/server/endpoint/projects_endpoint.py index 72286e570..85db76722 100644 --- a/tableauserverclient/server/endpoint/projects_endpoint.py +++ b/tableauserverclient/server/endpoint/projects_endpoint.py @@ -1,9 +1,9 @@ -from .endpoint import api, Endpoint +from .endpoint import api, Endpoint, XML_CONTENT_TYPE from .exceptions import MissingRequiredFieldError from .permissions_endpoint import _PermissionsEndpoint from .default_permissions_endpoint import _DefaultPermissionsEndpoint -from .. import RequestFactory, ProjectItem, PaginationItem, Permission +from .. import RequestFactory, RequestOptions, ProjectItem, PaginationItem, Permission import logging @@ -40,23 +40,25 @@ def delete(self, project_id): logger.info("Deleted single project (ID: {0})".format(project_id)) @api(version="2.0") - def update(self, project_item): + def update(self, project_item, samples=False): if not project_item.id: error = "Project item missing ID." raise MissingRequiredFieldError(error) + params = {"params": {RequestOptions.Field.PublishSamples: samples}} 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) + server_response = self.put_request(url, update_req, XML_CONTENT_TYPE, params) 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 @api(version="2.0") - def create(self, project_item): + def create(self, project_item, samples=False): + params = {"params": {RequestOptions.Field.PublishSamples: samples}} url = self.baseurl create_req = RequestFactory.Project.create_req(project_item) - server_response = self.post_request(url, create_req) + server_response = self.post_request(url, create_req, XML_CONTENT_TYPE, params) new_project = ProjectItem.from_response(server_response.content, self.parent_srv.namespace)[0] logger.info("Created new project (ID: {0})".format(new_project.id)) return new_project diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index a631ae170..293f2166d 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -4,6 +4,7 @@ from .resource_tagger import _ResourceTagger from .. import RequestFactory, WorkbookItem, ConnectionItem, ViewItem, PaginationItem from ...models.job_item import JobItem +from ...models.revision_item import RevisionItem from ...filesys_helpers import ( to_filename, make_download_path, @@ -388,7 +389,6 @@ def publish( workbook_item, connection_credentials=conn_creds, connections=connections, - hidden_views=hidden_views, ) else: logger.info("Publishing {0} to server".format(filename)) @@ -410,7 +410,6 @@ def publish( file_contents, connection_credentials=conn_creds, connections=connections, - hidden_views=hidden_views, ) logger.debug("Request xml: {0} ".format(xml_request[:1000])) @@ -430,3 +429,74 @@ def publish( 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)) return new_workbook + + # Populate workbook item's revisions + @api(version="2.3") + def populate_revisions(self, workbook_item: WorkbookItem) -> None: + if not workbook_item.id: + error = "Workbook item missing ID. Workbook must be retrieved from server first." + raise MissingRequiredFieldError(error) + + def revisions_fetcher(): + return self._get_workbook_revisions(workbook_item) + + workbook_item._set_revisions(revisions_fetcher) + logger.info("Populated revisions for workbook (ID: {0})".format(workbook_item.id)) + + def _get_workbook_revisions( + self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None + ) -> List[RevisionItem]: + url = "{0}/{1}/revisions".format(self.baseurl, workbook_item.id) + server_response = self.get_request(url, req_options) + revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, workbook_item) + return revisions + + # Download 1 workbook revision by revision number + @api(version="2.3") + def download_revision( + self, + workbook_id: str, + revision_number: str, + filepath: Optional[PathOrFile] = None, + include_extract: bool = True, + no_extract: Optional[bool] = None, + ) -> str: + if not workbook_id: + error = "Workbook ID undefined." + raise ValueError(error) + url = "{0}/{1}/revisions/{2}/content".format(self.baseurl, workbook_id, revision_number) + + if no_extract is False or no_extract is True: + import warnings + + 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"])) + + download_path = make_download_path(filepath, filename) + + with open(download_path, "wb") as f: + for chunk in server_response.iter_content(1024): # 1KB + f.write(chunk) + logger.info( + "Downloaded workbook revision {0} to {1} (ID: {2})".format(revision_number, download_path, workbook_id) + ) + return os.path.abspath(download_path) + + @api(version="2.3") + def delete_revision(self, workbook_id: str, revision_number: str) -> None: + if workbook_id is None or revision_number is None: + raise ValueError + url = "/".join([self.baseurl, workbook_id, "revisions", revision_number]) + + self.delete_request(url) + logger.info("Deleted single workbook revsision (ID: {0}) (Revision: {1})".format(workbook_id, revision_number)) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 49821ad44..df5418b4f 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -5,13 +5,16 @@ from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem -from typing import TYPE_CHECKING +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple if TYPE_CHECKING: + from ..models import DataAlertItem + from ..models import FlowItem + from ..models import ConnectionItem from ..models import WebhookItem -def _add_multipart(parts): +def _add_multipart(parts: Dict) -> Tuple[Any, str]: mime_multipart_parts = list() for name, (filename, data, content_type) in parts.items(): multipart_part = RequestField(name=name, data=data, filename=filename) @@ -92,22 +95,26 @@ def update_req(self, column_item): class DataAlertRequest(object): - def add_user_to_alert(self, alert_item, user_id): + def add_user_to_alert(self, alert_item: "DataAlertItem", user_id: str) -> bytes: 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): + def update_req(self, alert_item: "DataAlertItem") -> bytes: 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 + if alert_item.subject is not None: + dataAlert_element.attrib["subject"] = alert_item.subject + if alert_item.frequency is not None: + dataAlert_element.attrib["frequency"] = alert_item.frequency.lower() + if alert_item.public is not None: + dataAlert_element.attrib["public"] = str(alert_item.public).lower() owner = ET.SubElement(dataAlert_element, "owner") - owner.attrib["id"] = alert_item.owner_id + if alert_item.owner_id is not None: + owner.attrib["id"] = alert_item.owner_id return ET.tostring(xml_request) @@ -238,7 +245,7 @@ def update_req(self, database_item): class FavoriteRequest(object): - def _add_to_req(self, id_, target_type, label): + def _add_to_req(self, id_: str, target_type: str, label: str) -> bytes: """ @@ -252,16 +259,39 @@ def _add_to_req(self, id_, target_type, label): return ET.tostring(xml_request) - def add_datasource_req(self, id_, name): + def add_datasource_req(self, id_: Optional[str], name: Optional[str]) -> bytes: + if id_ is None: + raise ValueError("id must exist to add to favorites") + if name is None: + raise ValueError("Name must exist to add to favorites.") return self._add_to_req(id_, FavoriteItem.Type.Datasource, name) - def add_project_req(self, id_, name): + def add_flow_req(self, id_: Optional[str], name: Optional[str]) -> bytes: + if id_ is None: + raise ValueError("id must exist to add to favorites") + if name is None: + raise ValueError("Name must exist to add to favorites.") + return self._add_to_req(id_, FavoriteItem.Type.Flow, name) + + def add_project_req(self, id_: Optional[str], name: Optional[str]) -> bytes: + if id_ is None: + raise ValueError("id must exist to add to favorites") + if name is None: + raise ValueError("Name must exist to add to favorites.") return self._add_to_req(id_, FavoriteItem.Type.Project, name) - def add_view_req(self, id_, name): + def add_view_req(self, id_: Optional[str], name: Optional[str]) -> bytes: + if id_ is None: + raise ValueError("id must exist to add to favorites") + if name is None: + raise ValueError("Name must exist to add to favorites.") return self._add_to_req(id_, FavoriteItem.Type.View, name) - def add_workbook_req(self, id_, name): + def add_workbook_req(self, id_: Optional[str], name: Optional[str]) -> bytes: + if id_ is None: + raise ValueError("id must exist to add to favorites") + if name is None: + raise ValueError("Name must exist to add to favorites.") return self._add_to_req(id_, FavoriteItem.Type.Workbook, name) @@ -275,10 +305,11 @@ def chunk_req(self, chunk): class FlowRequest(object): - def _generate_xml(self, flow_item, connections=None): + def _generate_xml(self, flow_item: "FlowItem", connections: Optional[List["ConnectionItem"]] = None) -> bytes: xml_request = ET.Element("tsRequest") flow_element = ET.SubElement(xml_request, "flow") - flow_element.attrib["name"] = flow_item.name + if flow_item.name is not None: + flow_element.attrib["name"] = flow_item.name project_element = ET.SubElement(flow_element, "project") project_element.attrib["id"] = flow_item.project_id @@ -288,7 +319,7 @@ def _generate_xml(self, flow_item, connections=None): _add_connections_element(connections_element, connection) return ET.tostring(xml_request) - def update_req(self, flow_item): + def update_req(self, flow_item: "FlowItem") -> bytes: xml_request = ET.Element("tsRequest") flow_element = ET.SubElement(xml_request, "flow") if flow_item.project_id: @@ -300,7 +331,13 @@ def update_req(self, flow_item): return ET.tostring(xml_request) - def publish_req(self, flow_item, filename, file_contents, connections=None): + def publish_req( + self, + flow_item: "FlowItem", + filename: str, + file_contents: bytes, + connections: Optional[List["ConnectionItem"]] = None, + ) -> Tuple[Any, str]: xml_request = self._generate_xml(flow_item, connections) parts = { @@ -309,7 +346,7 @@ def publish_req(self, flow_item, filename, file_contents, connections=None): } return _add_multipart(parts) - def publish_req_chunked(self, flow_item, connections=None): + def publish_req_chunked(self, flow_item, connections=None) -> Tuple[Any, str]: xml_request = self._generate_xml(flow_item, connections) parts = {"request_payload": ("", xml_request, "text/xml")} @@ -776,7 +813,6 @@ def _generate_xml( workbook_item, connection_credentials=None, connections=None, - hidden_views=None, ): xml_request = ET.Element("tsRequest") workbook_element = ET.SubElement(xml_request, "workbook") @@ -797,9 +833,9 @@ def _generate_xml( for connection in connections: _add_connections_element(connections_element, connection) - if hidden_views is not None: + if workbook_item.hidden_views is not None: views_element = ET.SubElement(workbook_element, "views") - for view_name in hidden_views: + for view_name in workbook_item.hidden_views: _add_hiddenview_element(views_element, view_name) return ET.tostring(xml_request) @@ -837,13 +873,11 @@ def publish_req( 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 = { @@ -857,13 +891,11 @@ def publish_req_chunked( 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")} diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 3047691a9..36ffccd8e 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -48,6 +48,7 @@ class Field: OwnerName = "ownerName" Progress = "progress" ProjectName = "projectName" + PublishSamples = "publishSamples" SiteRole = "siteRole" Subtitle = "subtitle" Tags = "tags" diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index 4d289d5e5..c4c635a91 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -25,6 +25,7 @@ Favorites, DataAlerts, Fileuploads, + FlowRuns, ) from .endpoint.exceptions import ( EndpointUnavailableError, @@ -82,6 +83,7 @@ def __init__(self, server_address, use_server_version=False): self.data_alerts = DataAlerts(self) self.fileuploads = Fileuploads(self) self._namespace = Namespace() + self.flow_runs = FlowRuns(self) if use_server_version: self.use_server_version() diff --git a/test/_utils.py b/test/_utils.py index 93d7a9334..4ee919750 100644 --- a/test/_utils.py +++ b/test/_utils.py @@ -2,7 +2,7 @@ import unittest import os.path -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") def asset(filename): @@ -10,8 +10,8 @@ def asset(filename): def read_xml_asset(filename): - with open(asset(filename), 'rb') as f: - return f.read().decode('utf-8') + with open(asset(filename), "rb") as f: + return f.read().decode("utf-8") def read_xml_assets(*args): @@ -28,7 +28,10 @@ def sleep_mock(interval): def get_time(): return mock_time - - patch = unittest.mock.patch + + try: + patch = unittest.mock.patch + except AttributeError: + from unittest.mock import patch with patch("time.sleep", sleep_mock), patch("time.time", get_time): yield get_time diff --git a/test/assets/datasource_revision.xml b/test/assets/datasource_revision.xml new file mode 100644 index 000000000..598c8ad45 --- /dev/null +++ b/test/assets/datasource_revision.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/assets/flow_refresh.xml b/test/assets/flow_refresh.xml new file mode 100644 index 000000000..b2bb97a5d --- /dev/null +++ b/test/assets/flow_refresh.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/test/assets/flow_runs_get.xml b/test/assets/flow_runs_get.xml new file mode 100644 index 000000000..bdce4cdfb --- /dev/null +++ b/test/assets/flow_runs_get.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/test/assets/flow_runs_get_by_id.xml b/test/assets/flow_runs_get_by_id.xml new file mode 100644 index 000000000..3a768fab4 --- /dev/null +++ b/test/assets/flow_runs_get_by_id.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/test/assets/flow_runs_get_by_id_failed.xml b/test/assets/flow_runs_get_by_id_failed.xml new file mode 100644 index 000000000..9e766680b --- /dev/null +++ b/test/assets/flow_runs_get_by_id_failed.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/test/assets/flow_runs_get_by_id_inprogress.xml b/test/assets/flow_runs_get_by_id_inprogress.xml new file mode 100644 index 000000000..42e1a77f9 --- /dev/null +++ b/test/assets/flow_runs_get_by_id_inprogress.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/test/assets/workbook_revision.xml b/test/assets/workbook_revision.xml new file mode 100644 index 000000000..598c8ad45 --- /dev/null +++ b/test/assets/workbook_revision.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/test_auth.py b/test/test_auth.py index 3dbf87737..ff757909e 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -3,87 +3,89 @@ import requests_mock import tableauserverclient as TSC -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -SIGN_IN_XML = os.path.join(TEST_ASSET_DIR, 'auth_sign_in.xml') -SIGN_IN_IMPERSONATE_XML = os.path.join(TEST_ASSET_DIR, 'auth_sign_in_impersonate.xml') -SIGN_IN_ERROR_XML = os.path.join(TEST_ASSET_DIR, 'auth_sign_in_error.xml') +SIGN_IN_XML = os.path.join(TEST_ASSET_DIR, "auth_sign_in.xml") +SIGN_IN_IMPERSONATE_XML = os.path.join(TEST_ASSET_DIR, "auth_sign_in_impersonate.xml") +SIGN_IN_ERROR_XML = os.path.join(TEST_ASSET_DIR, "auth_sign_in_error.xml") class AuthTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") self.baseurl = self.server.auth.baseurl def test_sign_in(self): - with open(SIGN_IN_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml) - tableau_auth = TSC.TableauAuth('testuser', 'password', site_id='Samples') + m.post(self.baseurl + "/signin", text=response_xml) + tableau_auth = TSC.TableauAuth("testuser", "password", site_id="Samples") self.server.auth.sign_in(tableau_auth) - self.assertEqual('eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l', self.server.auth_token) - self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', self.server.site_id) - self.assertEqual('1a96d216-e9b8-497b-a82a-0b899a965e01', self.server.user_id) + self.assertEqual("eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l", self.server.auth_token) + self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", self.server.site_id) + self.assertEqual("1a96d216-e9b8-497b-a82a-0b899a965e01", self.server.user_id) def test_sign_in_with_personal_access_tokens(self): - with open(SIGN_IN_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml) - tableau_auth = TSC.PersonalAccessTokenAuth(token_name='mytoken', - personal_access_token='Random123Generated', site_id='Samples') + m.post(self.baseurl + "/signin", text=response_xml) + tableau_auth = TSC.PersonalAccessTokenAuth( + token_name="mytoken", personal_access_token="Random123Generated", site_id="Samples" + ) self.server.auth.sign_in(tableau_auth) - self.assertEqual('eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l', self.server.auth_token) - self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', self.server.site_id) - self.assertEqual('1a96d216-e9b8-497b-a82a-0b899a965e01', self.server.user_id) + self.assertEqual("eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l", self.server.auth_token) + self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", self.server.site_id) + self.assertEqual("1a96d216-e9b8-497b-a82a-0b899a965e01", self.server.user_id) def test_sign_in_impersonate(self): - with open(SIGN_IN_IMPERSONATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_IMPERSONATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml) - tableau_auth = TSC.TableauAuth('testuser', 'password', - user_id_to_impersonate='dd2239f6-ddf1-4107-981a-4cf94e415794') + m.post(self.baseurl + "/signin", text=response_xml) + tableau_auth = TSC.TableauAuth( + "testuser", "password", user_id_to_impersonate="dd2239f6-ddf1-4107-981a-4cf94e415794" + ) self.server.auth.sign_in(tableau_auth) - self.assertEqual('MJonFA6HDyy2C3oqR13fRGqE6cmgzwq3', self.server.auth_token) - self.assertEqual('dad65087-b08b-4603-af4e-2887b8aafc67', self.server.site_id) - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', self.server.user_id) + self.assertEqual("MJonFA6HDyy2C3oqR13fRGqE6cmgzwq3", self.server.auth_token) + self.assertEqual("dad65087-b08b-4603-af4e-2887b8aafc67", self.server.site_id) + self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", self.server.user_id) def test_sign_in_error(self): - with open(SIGN_IN_ERROR_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_ERROR_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml, status_code=401) - tableau_auth = TSC.TableauAuth('testuser', 'wrongpassword') + m.post(self.baseurl + "/signin", text=response_xml, status_code=401) + tableau_auth = TSC.TableauAuth("testuser", "wrongpassword") self.assertRaises(TSC.ServerResponseError, self.server.auth.sign_in, tableau_auth) def test_sign_in_invalid_token(self): - with open(SIGN_IN_ERROR_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_ERROR_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml, status_code=401) - tableau_auth = TSC.PersonalAccessTokenAuth(token_name='mytoken', personal_access_token='invalid') + m.post(self.baseurl + "/signin", text=response_xml, status_code=401) + tableau_auth = TSC.PersonalAccessTokenAuth(token_name="mytoken", personal_access_token="invalid") self.assertRaises(TSC.ServerResponseError, self.server.auth.sign_in, tableau_auth) def test_sign_in_without_auth(self): - with open(SIGN_IN_ERROR_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_ERROR_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml, status_code=401) - tableau_auth = TSC.TableauAuth('', '') + m.post(self.baseurl + "/signin", text=response_xml, status_code=401) + tableau_auth = TSC.TableauAuth("", "") self.assertRaises(TSC.ServerResponseError, self.server.auth.sign_in, tableau_auth) def test_sign_out(self): - with open(SIGN_IN_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/signin', text=response_xml) - m.post(self.baseurl + '/signout', text='') - tableau_auth = TSC.TableauAuth('testuser', 'password') + m.post(self.baseurl + "/signin", text=response_xml) + m.post(self.baseurl + "/signout", text="") + tableau_auth = TSC.TableauAuth("testuser", "password") self.server.auth.sign_in(tableau_auth) self.server.auth.sign_out() @@ -92,33 +94,33 @@ def test_sign_out(self): self.assertIsNone(self.server._user_id) def test_switch_site(self): - self.server.version = '2.6' + self.server.version = "2.6" baseurl = self.server.auth.baseurl - site_id, user_id, auth_token = list('123') + site_id, user_id, auth_token = list("123") self.server._set_auth(site_id, user_id, auth_token) - with open(SIGN_IN_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(baseurl + '/switchSite', text=response_xml) - site = TSC.SiteItem('Samples', 'Samples') + m.post(baseurl + "/switchSite", text=response_xml) + site = TSC.SiteItem("Samples", "Samples") self.server.auth.switch_site(site) - self.assertEqual('eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l', self.server.auth_token) - self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', self.server.site_id) - self.assertEqual('1a96d216-e9b8-497b-a82a-0b899a965e01', self.server.user_id) + self.assertEqual("eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l", self.server.auth_token) + self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", self.server.site_id) + self.assertEqual("1a96d216-e9b8-497b-a82a-0b899a965e01", self.server.user_id) def test_revoke_all_server_admin_tokens(self): self.server.version = "3.10" baseurl = self.server.auth.baseurl - with open(SIGN_IN_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SIGN_IN_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(baseurl + '/signin', text=response_xml) - m.post(baseurl + '/revokeAllServerAdminTokens', text='') - tableau_auth = TSC.TableauAuth('testuser', 'password') + m.post(baseurl + "/signin", text=response_xml) + m.post(baseurl + "/revokeAllServerAdminTokens", text="") + tableau_auth = TSC.TableauAuth("testuser", "password") self.server.auth.sign_in(tableau_auth) self.server.auth.revoke_all_server_admin_tokens() - self.assertEqual('eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l', self.server.auth_token) - self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', self.server.site_id) - self.assertEqual('1a96d216-e9b8-497b-a82a-0b899a965e01', self.server.user_id) + self.assertEqual("eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l", self.server.auth_token) + self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", self.server.site_id) + self.assertEqual("1a96d216-e9b8-497b-a82a-0b899a965e01", self.server.user_id) diff --git a/test/test_data_acceleration_report.py b/test/test_data_acceleration_report.py index 7722bf230..d3cf96db1 100644 --- a/test/test_data_acceleration_report.py +++ b/test/test_data_acceleration_report.py @@ -5,16 +5,16 @@ import tableauserverclient as TSC from ._utils import read_xml_asset, read_xml_assets, asset -GET_XML = 'data_acceleration_report.xml' +GET_XML = "data_acceleration_report.xml" class DataAccelerationReportTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.server.version = "3.8" self.baseurl = self.server.data_acceleration_report.baseurl diff --git a/test/test_dataalert.py b/test/test_dataalert.py index 7822d3000..059046123 100644 --- a/test/test_dataalert.py +++ b/test/test_dataalert.py @@ -8,93 +8,93 @@ from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset -GET_XML = 'data_alerts_get.xml' -GET_BY_ID_XML = 'data_alerts_get_by_id.xml' -ADD_USER_TO_ALERT = 'data_alerts_add_user.xml' -UPDATE_XML = 'data_alerts_update.xml' +GET_XML = "data_alerts_get.xml" +GET_BY_ID_XML = "data_alerts_get_by_id.xml" +ADD_USER_TO_ALERT = "data_alerts_add_user.xml" +UPDATE_XML = "data_alerts_update.xml" class DataAlertTests(unittest.TestCase): - def setUp(self): - self.server = TSC.Server('http://test') + def setUp(self) -> None: + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.server.version = "3.2" self.baseurl = self.server.data_alerts.baseurl - def test_get(self): + def test_get(self) -> None: response_xml = read_xml_asset(GET_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_alerts, pagination_item = self.server.data_alerts.get() self.assertEqual(1, pagination_item.total_available) - self.assertEqual('5ea59b45-e497-5673-8809-bfe213236f75', all_alerts[0].id) - self.assertEqual('Data Alert test', all_alerts[0].subject) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_alerts[0].creatorId) - self.assertEqual('2020-08-10T23:17:06Z', all_alerts[0].createdAt) - self.assertEqual('2020-08-10T23:17:06Z', all_alerts[0].updatedAt) - self.assertEqual('Daily', all_alerts[0].frequency) - self.assertEqual('true', all_alerts[0].public) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_alerts[0].owner_id) - self.assertEqual('Bob', all_alerts[0].owner_name) - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', all_alerts[0].view_id) - self.assertEqual('ENDANGERED SAFARI', all_alerts[0].view_name) - self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_alerts[0].workbook_id) - self.assertEqual('Safari stats', all_alerts[0].workbook_name) - self.assertEqual('5241e88d-d384-4fd7-9c2f-648b5247efc5', all_alerts[0].project_id) - self.assertEqual('Default', all_alerts[0].project_name) - - def test_get_by_id(self): + self.assertEqual("5ea59b45-e497-5673-8809-bfe213236f75", all_alerts[0].id) + self.assertEqual("Data Alert test", all_alerts[0].subject) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_alerts[0].creatorId) + self.assertEqual("2020-08-10T23:17:06Z", all_alerts[0].createdAt) + self.assertEqual("2020-08-10T23:17:06Z", all_alerts[0].updatedAt) + self.assertEqual("Daily", all_alerts[0].frequency) + self.assertEqual("true", all_alerts[0].public) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_alerts[0].owner_id) + self.assertEqual("Bob", all_alerts[0].owner_name) + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", all_alerts[0].view_id) + self.assertEqual("ENDANGERED SAFARI", all_alerts[0].view_name) + self.assertEqual("6d13b0ca-043d-4d42-8c9d-3f3313ea3a00", all_alerts[0].workbook_id) + self.assertEqual("Safari stats", all_alerts[0].workbook_name) + self.assertEqual("5241e88d-d384-4fd7-9c2f-648b5247efc5", all_alerts[0].project_id) + self.assertEqual("Default", all_alerts[0].project_name) + + def test_get_by_id(self) -> None: response_xml = read_xml_asset(GET_BY_ID_XML) with requests_mock.mock() as m: - m.get(self.baseurl + '/5ea59b45-e497-5673-8809-bfe213236f75', text=response_xml) - alert = self.server.data_alerts.get_by_id('5ea59b45-e497-5673-8809-bfe213236f75') + m.get(self.baseurl + "/5ea59b45-e497-5673-8809-bfe213236f75", text=response_xml) + alert = self.server.data_alerts.get_by_id("5ea59b45-e497-5673-8809-bfe213236f75") self.assertTrue(isinstance(alert.recipients, list)) self.assertEqual(len(alert.recipients), 1) - self.assertEqual(alert.recipients[0], 'dd2239f6-ddf1-4107-981a-4cf94e415794') + self.assertEqual(alert.recipients[0], "dd2239f6-ddf1-4107-981a-4cf94e415794") - def test_update(self): + def test_update(self) -> None: response_xml = read_xml_asset(UPDATE_XML) with requests_mock.mock() as m: - m.put(self.baseurl + '/5ea59b45-e497-5673-8809-bfe213236f75', text=response_xml) + m.put(self.baseurl + "/5ea59b45-e497-5673-8809-bfe213236f75", text=response_xml) single_alert = TSC.DataAlertItem() - single_alert._id = '5ea59b45-e497-5673-8809-bfe213236f75' - single_alert._subject = 'Data Alert test' - single_alert._frequency = 'Daily' - single_alert._public = "true" + single_alert._id = "5ea59b45-e497-5673-8809-bfe213236f75" + single_alert._subject = "Data Alert test" + single_alert._frequency = "Daily" + single_alert._public = True single_alert._owner_id = "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" single_alert = self.server.data_alerts.update(single_alert) - self.assertEqual('5ea59b45-e497-5673-8809-bfe213236f75', single_alert.id) - self.assertEqual('Data Alert test', single_alert.subject) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', single_alert.creatorId) - self.assertEqual('2020-08-10T23:17:06Z', single_alert.createdAt) - self.assertEqual('2020-08-10T23:17:06Z', single_alert.updatedAt) - self.assertEqual('Daily', single_alert.frequency) - self.assertEqual('true', single_alert.public) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', single_alert.owner_id) - self.assertEqual('Bob', single_alert.owner_name) - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', single_alert.view_id) - self.assertEqual('ENDANGERED SAFARI', single_alert.view_name) - self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', single_alert.workbook_id) - self.assertEqual('Safari stats', single_alert.workbook_name) - self.assertEqual('5241e88d-d384-4fd7-9c2f-648b5247efc5', single_alert.project_id) - self.assertEqual('Default', single_alert.project_name) - - def test_add_user_to_alert(self): + self.assertEqual("5ea59b45-e497-5673-8809-bfe213236f75", single_alert.id) + self.assertEqual("Data Alert test", single_alert.subject) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", single_alert.creatorId) + self.assertEqual("2020-08-10T23:17:06Z", single_alert.createdAt) + self.assertEqual("2020-08-10T23:17:06Z", single_alert.updatedAt) + self.assertEqual("Daily", single_alert.frequency) + self.assertEqual("true", single_alert.public) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", single_alert.owner_id) + self.assertEqual("Bob", single_alert.owner_name) + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", single_alert.view_id) + self.assertEqual("ENDANGERED SAFARI", single_alert.view_name) + self.assertEqual("6d13b0ca-043d-4d42-8c9d-3f3313ea3a00", single_alert.workbook_id) + self.assertEqual("Safari stats", single_alert.workbook_name) + self.assertEqual("5241e88d-d384-4fd7-9c2f-648b5247efc5", single_alert.project_id) + self.assertEqual("Default", single_alert.project_name) + + def test_add_user_to_alert(self) -> None: response_xml = read_xml_asset(ADD_USER_TO_ALERT) single_alert = TSC.DataAlertItem() - single_alert._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' - in_user = TSC.UserItem('Bob', TSC.UserItem.Roles.Explorer) - in_user._id = '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7' + single_alert._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5" + in_user = TSC.UserItem("Bob", TSC.UserItem.Roles.Explorer) + in_user._id = "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" with requests_mock.mock() as m: - m.post(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5/users', text=response_xml) + m.post(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/users", text=response_xml) out_user = self.server.data_alerts.add_user_to_alert(single_alert, in_user) @@ -102,14 +102,14 @@ def test_add_user_to_alert(self): self.assertEqual(out_user.name, in_user.name) self.assertEqual(out_user.site_role, in_user.site_role) - def test_delete(self): + def test_delete(self) -> None: with requests_mock.mock() as m: - m.delete(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5', status_code=204) - self.server.data_alerts.delete('0448d2ed-590d-4fa0-b272-a2a8a24555b5') + m.delete(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5", status_code=204) + self.server.data_alerts.delete("0448d2ed-590d-4fa0-b272-a2a8a24555b5") - def test_delete_user_from_alert(self): - alert_id = '5ea59b45-e497-5673-8809-bfe213236f75' - user_id = '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7' + def test_delete_user_from_alert(self) -> None: + alert_id = "5ea59b45-e497-5673-8809-bfe213236f75" + user_id = "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" with requests_mock.mock() as m: - m.delete(self.baseurl + '/{0}/users/{1}'.format(alert_id, user_id), status_code=204) + m.delete(self.baseurl + "/{0}/users/{1}".format(alert_id, user_id), status_code=204) self.server.data_alerts.delete_user_from_alert(alert_id, user_id) diff --git a/test/test_database.py b/test/test_database.py index e7c6a6fb6..8e27ccf52 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -8,18 +8,18 @@ from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset -GET_XML = 'database_get.xml' -POPULATE_PERMISSIONS_XML = 'database_populate_permissions.xml' -UPDATE_XML = 'database_update.xml' +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') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.server.version = "3.5" self.baseurl = self.server.databases.baseurl @@ -31,64 +31,72 @@ def test_get(self): all_databases, pagination_item = self.server.databases.get() self.assertEqual(5, pagination_item.total_available) - self.assertEqual('5ea59b45-e497-4827-8809-bfe213236f75', all_databases[0].id) - self.assertEqual('hyper', all_databases[0].connection_type) - self.assertEqual('hyper_0.hyper', all_databases[0].name) - - self.assertEqual('23591f2c-4802-4d6a-9e28-574a8ea9bc4c', all_databases[1].id) - self.assertEqual('sqlserver', all_databases[1].connection_type) - self.assertEqual('testv1', all_databases[1].name) - self.assertEqual('9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0', all_databases[1].contact_id) + self.assertEqual("5ea59b45-e497-4827-8809-bfe213236f75", all_databases[0].id) + self.assertEqual("hyper", all_databases[0].connection_type) + self.assertEqual("hyper_0.hyper", all_databases[0].name) + + self.assertEqual("23591f2c-4802-4d6a-9e28-574a8ea9bc4c", all_databases[1].id) + self.assertEqual("sqlserver", all_databases[1].connection_type) + self.assertEqual("testv1", all_databases[1].name) + self.assertEqual("9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0", all_databases[1].contact_id) self.assertEqual(True, all_databases[1].certified) def test_update(self): response_xml = read_xml_asset(UPDATE_XML) with requests_mock.mock() as m: - m.put(self.baseurl + '/23591f2c-4802-4d6a-9e28-574a8ea9bc4c', text=response_xml) - single_database = TSC.DatabaseItem('test') - single_database.contact_id = '9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0' - single_database._id = '23591f2c-4802-4d6a-9e28-574a8ea9bc4c' + m.put(self.baseurl + "/23591f2c-4802-4d6a-9e28-574a8ea9bc4c", text=response_xml) + single_database = TSC.DatabaseItem("test") + single_database.contact_id = "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0" + single_database._id = "23591f2c-4802-4d6a-9e28-574a8ea9bc4c" single_database.certified = True single_database.certification_note = "Test" single_database = self.server.databases.update(single_database) - self.assertEqual('23591f2c-4802-4d6a-9e28-574a8ea9bc4c', single_database.id) - self.assertEqual('9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0', single_database.contact_id) + self.assertEqual("23591f2c-4802-4d6a-9e28-574a8ea9bc4c", single_database.id) + self.assertEqual("9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0", single_database.contact_id) self.assertEqual(True, single_database.certified) self.assertEqual("Test", single_database.certification_note) def test_populate_permissions(self): - with open(asset(POPULATE_PERMISSIONS_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: + 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_database = TSC.DatabaseItem('test') - single_database._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' + m.get(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml) + single_database = TSC.DatabaseItem("test") + single_database._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5" self.server.databases.populate_permissions(single_database) permissions = single_database.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - }) - - self.assertEqual(permissions[1].grantee.tag_name, 'user') - self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') - self.assertDictEqual(permissions[1].capabilities, { - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "5e5e1978-71fa-11e4-87dd-7382f5c437af") + self.assertDictEqual( + permissions[0].capabilities, + { + TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + }, + ) + + self.assertEqual(permissions[1].grantee.tag_name, "user") + self.assertEqual(permissions[1].grantee.id, "7c37ee24-c4b1-42b6-a154-eaeab7ee330a") + self.assertDictEqual( + permissions[1].capabilities, + { + 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 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' + 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 @@ -104,5 +112,5 @@ def test_populate_data_quality_warning(self): def test_delete(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5', status_code=204) - self.server.databases.delete('0448d2ed-590d-4fa0-b272-a2a8a24555b5') + m.delete(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5", status_code=204) + self.server.databases.delete("0448d2ed-590d-4fa0-b272-a2a8a24555b5") diff --git a/test/test_datasource.py b/test/test_datasource.py index d00c05080..67b7b579c 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -5,6 +5,7 @@ import requests_mock import xml.etree.ElementTree as ET from zipfile import ZipFile +import tempfile import tableauserverclient as TSC from tableauserverclient.datetime_helpers import format_datetime @@ -12,28 +13,28 @@ 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' -GET_BY_ID_XML = 'datasource_get_by_id.xml' -POPULATE_CONNECTIONS_XML = 'datasource_populate_connections.xml' -POPULATE_PERMISSIONS_XML = 'datasource_populate_permissions.xml' -PUBLISH_XML = 'datasource_publish.xml' -PUBLISH_XML_ASYNC = 'datasource_publish_async.xml' -REFRESH_XML = 'datasource_refresh.xml' -UPDATE_XML = 'datasource_update.xml' -UPDATE_HYPER_DATA_XML = 'datasource_data_update.xml' -UPDATE_CONNECTION_XML = 'datasource_connection_update.xml' +ADD_TAGS_XML = "datasource_add_tags.xml" +GET_XML = "datasource_get.xml" +GET_EMPTY_XML = "datasource_get_empty.xml" +GET_BY_ID_XML = "datasource_get_by_id.xml" +POPULATE_CONNECTIONS_XML = "datasource_populate_connections.xml" +POPULATE_PERMISSIONS_XML = "datasource_populate_permissions.xml" +PUBLISH_XML = "datasource_publish.xml" +PUBLISH_XML_ASYNC = "datasource_publish_async.xml" +REFRESH_XML = "datasource_refresh.xml" +REVISION_XML = "datasource_revision.xml" +UPDATE_XML = "datasource_update.xml" +UPDATE_HYPER_DATA_XML = "datasource_data_update.xml" +UPDATE_CONNECTION_XML = "datasource_connection_update.xml" class DatasourceTests(unittest.TestCase): def setUp(self) -> None: - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.datasources.baseurl @@ -44,33 +45,33 @@ def test_get(self) -> None: all_datasources, pagination_item = self.server.datasources.get() self.assertEqual(2, pagination_item.total_available) - self.assertEqual('e76a1461-3b1d-4588-bf1b-17551a879ad9', all_datasources[0].id) - self.assertEqual('dataengine', all_datasources[0].datasource_type) - self.assertEqual('SampleDsDescription', all_datasources[0].description) - self.assertEqual('SampleDS', all_datasources[0].content_url) - self.assertEqual('2016-08-11T21:22:40Z', format_datetime(all_datasources[0].created_at)) - self.assertEqual('2016-08-11T21:34:17Z', format_datetime(all_datasources[0].updated_at)) - self.assertEqual('default', all_datasources[0].project_name) - self.assertEqual('SampleDS', all_datasources[0].name) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', all_datasources[0].project_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_datasources[0].owner_id) - self.assertEqual('https://web.com', all_datasources[0].webpage_url) + self.assertEqual("e76a1461-3b1d-4588-bf1b-17551a879ad9", all_datasources[0].id) + self.assertEqual("dataengine", all_datasources[0].datasource_type) + self.assertEqual("SampleDsDescription", all_datasources[0].description) + self.assertEqual("SampleDS", all_datasources[0].content_url) + self.assertEqual("2016-08-11T21:22:40Z", format_datetime(all_datasources[0].created_at)) + self.assertEqual("2016-08-11T21:34:17Z", format_datetime(all_datasources[0].updated_at)) + self.assertEqual("default", all_datasources[0].project_name) + self.assertEqual("SampleDS", all_datasources[0].name) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", all_datasources[0].project_id) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_datasources[0].owner_id) + self.assertEqual("https://web.com", all_datasources[0].webpage_url) self.assertFalse(all_datasources[0].encrypt_extracts) self.assertTrue(all_datasources[0].has_extracts) self.assertFalse(all_datasources[0].use_remote_query_agent) - self.assertEqual('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', all_datasources[1].id) - self.assertEqual('dataengine', all_datasources[1].datasource_type) - self.assertEqual('description Sample', all_datasources[1].description) - self.assertEqual('Sampledatasource', all_datasources[1].content_url) - self.assertEqual('2016-08-04T21:31:55Z', format_datetime(all_datasources[1].created_at)) - self.assertEqual('2016-08-04T21:31:55Z', format_datetime(all_datasources[1].updated_at)) - self.assertEqual('default', all_datasources[1].project_name) - self.assertEqual('Sample datasource', all_datasources[1].name) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', all_datasources[1].project_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_datasources[1].owner_id) - self.assertEqual(set(['world', 'indicators', 'sample']), all_datasources[1].tags) - self.assertEqual('https://page.com', all_datasources[1].webpage_url) + self.assertEqual("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", all_datasources[1].id) + self.assertEqual("dataengine", all_datasources[1].datasource_type) + self.assertEqual("description Sample", all_datasources[1].description) + self.assertEqual("Sampledatasource", all_datasources[1].content_url) + self.assertEqual("2016-08-04T21:31:55Z", format_datetime(all_datasources[1].created_at)) + self.assertEqual("2016-08-04T21:31:55Z", format_datetime(all_datasources[1].updated_at)) + self.assertEqual("default", all_datasources[1].project_name) + self.assertEqual("Sample datasource", all_datasources[1].name) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", all_datasources[1].project_id) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_datasources[1].owner_id) + self.assertEqual(set(["world", "indicators", "sample"]), all_datasources[1].tags) + self.assertEqual("https://page.com", all_datasources[1].webpage_url) self.assertTrue(all_datasources[1].encrypt_extracts) self.assertFalse(all_datasources[1].has_extracts) self.assertTrue(all_datasources[1].use_remote_query_agent) @@ -91,30 +92,30 @@ def test_get_empty(self) -> None: def test_get_by_id(self) -> None: response_xml = read_xml_asset(GET_BY_ID_XML) with requests_mock.mock() as m: - m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', text=response_xml) - single_datasource = self.server.datasources.get_by_id('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb') - - self.assertEqual('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', single_datasource.id) - self.assertEqual('dataengine', single_datasource.datasource_type) - self.assertEqual('abc description xyz', single_datasource.description) - self.assertEqual('Sampledatasource', single_datasource.content_url) - self.assertEqual('2016-08-04T21:31:55Z', format_datetime(single_datasource.created_at)) - self.assertEqual('2016-08-04T21:31:55Z', format_datetime(single_datasource.updated_at)) - self.assertEqual('default', single_datasource.project_name) - self.assertEqual('Sample datasource', single_datasource.name) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', single_datasource.project_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', single_datasource.owner_id) - self.assertEqual(set(['world', 'indicators', 'sample']), single_datasource.tags) + m.get(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml) + single_datasource = self.server.datasources.get_by_id("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb") + + self.assertEqual("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", single_datasource.id) + self.assertEqual("dataengine", single_datasource.datasource_type) + self.assertEqual("abc description xyz", single_datasource.description) + self.assertEqual("Sampledatasource", single_datasource.content_url) + self.assertEqual("2016-08-04T21:31:55Z", format_datetime(single_datasource.created_at)) + self.assertEqual("2016-08-04T21:31:55Z", format_datetime(single_datasource.updated_at)) + self.assertEqual("default", single_datasource.project_name) + self.assertEqual("Sample datasource", single_datasource.name) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", single_datasource.project_id) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", single_datasource.owner_id) + self.assertEqual(set(["world", "indicators", "sample"]), single_datasource.tags) self.assertEqual(TSC.DatasourceItem.AskDataEnablement.SiteDefault, single_datasource.ask_data_enablement) def test_update(self) -> None: 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('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' + m.put(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml) + 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." updated_datasource = self.server.datasources.update(single_datasource) @@ -128,13 +129,13 @@ def test_update(self) -> None: self.assertEqual(updated_datasource.certification_note, single_datasource.certification_note) def test_update_copy_fields(self) -> None: - with open(asset(UPDATE_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + 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('1d0304cd-3796-429f-b815-7258370b9b74', 'test') - single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' - single_datasource._project_name = 'Tester' + m.put(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml) + 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) self.assertEqual(single_datasource.tags, updated_datasource.tags) @@ -143,14 +144,14 @@ def test_update_copy_fields(self) -> None: def test_update_tags(self) -> None: add_tags_xml, update_xml = read_xml_assets(ADD_TAGS_XML, UPDATE_XML) with requests_mock.mock() as m: - m.put(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags', text=add_tags_xml) - m.delete(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags/b', status_code=204) - m.delete(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags/d', status_code=204) - m.put(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', text=update_xml) - single_datasource = TSC.DatasourceItem('1d0304cd-3796-429f-b815-7258370b9b74') - single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' - single_datasource._initial_tags.update(['a', 'b', 'c', 'd']) - single_datasource.tags.update(['a', 'c', 'e']) + m.put(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags", text=add_tags_xml) + m.delete(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags/b", status_code=204) + m.delete(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags/d", status_code=204) + m.put(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=update_xml) + single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74") + single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" + single_datasource._initial_tags.update(["a", "b", "c", "d"]) + single_datasource.tags.update(["a", "c", "e"]) updated_datasource = self.server.datasources.update(single_datasource) self.assertEqual(single_datasource.tags, updated_datasource.tags) @@ -159,147 +160,148 @@ def test_update_tags(self) -> None: def test_populate_connections(self) -> None: 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('1d0304cd-3796-429f-b815-7258370b9b74', 'test') - single_datasource.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' - single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + m.get(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml) + 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) - self.assertEqual('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', single_datasource.id) + self.assertEqual("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", single_datasource.id) connections = single_datasource.connections self.assertTrue(connections) ds1, ds2 = connections - self.assertEqual('be786ae0-d2bf-4a4b-9b34-e2de8d2d4488', ds1.id) - self.assertEqual('textscan', ds1.connection_type) - self.assertEqual('forty-two.net', ds1.server_address) - self.assertEqual('duo', ds1.username) + self.assertEqual("be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", ds1.id) + self.assertEqual("textscan", ds1.connection_type) + self.assertEqual("forty-two.net", ds1.server_address) + self.assertEqual("duo", ds1.username) self.assertEqual(True, ds1.embed_password) - self.assertEqual('970e24bc-e200-4841-a3e9-66e7d122d77e', ds2.id) - self.assertEqual('sqlserver', ds2.connection_type) - self.assertEqual('database.com', ds2.server_address) - self.assertEqual('heero', ds2.username) + self.assertEqual("970e24bc-e200-4841-a3e9-66e7d122d77e", ds2.id) + self.assertEqual("sqlserver", ds2.connection_type) + self.assertEqual("database.com", ds2.server_address) + self.assertEqual("heero", ds2.username) self.assertEqual(False, ds2.embed_password) def test_update_connection(self) -> None: populate_xml, response_xml = read_xml_assets(POPULATE_CONNECTIONS_XML, UPDATE_CONNECTION_XML) with requests_mock.mock() as m: - m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections', text=populate_xml) - m.put(self.baseurl + - '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/be786ae0-d2bf-4a4b-9b34-e2de8d2d4488', - text=response_xml) - 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' + m.get(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=populate_xml) + m.put( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", + text=response_xml, + ) + 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) connection = single_datasource.connections[0] # type: ignore[index] - connection.server_address = 'bar' - connection.server_port = '9876' - connection.username = 'foo' + connection.server_address = "bar" + connection.server_port = "9876" + connection.username = "foo" new_connection = self.server.datasources.update_connection(single_datasource, connection) self.assertEqual(connection.id, new_connection.id) self.assertEqual(connection.connection_type, new_connection.connection_type) - self.assertEqual('bar', new_connection.server_address) - self.assertEqual('9876', new_connection.server_port) - self.assertEqual('foo', new_connection.username) + self.assertEqual("bar", new_connection.server_address) + self.assertEqual("9876", new_connection.server_port) + self.assertEqual("foo", new_connection.username) def test_populate_permissions(self) -> None: - with open(asset(POPULATE_PERMISSIONS_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: + 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('1d0304cd-3796-429f-b815-7258370b9b74', 'test') - single_datasource._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' + m.get(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml) + 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) permissions = single_datasource.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') # type: ignore[index] - self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') # type: ignore[index] - self.assertDictEqual(permissions[0].capabilities, { # type: ignore[index] - TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.Connect: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - }) - - self.assertEqual(permissions[1].grantee.tag_name, 'user') # type: ignore[index] - self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') # type: ignore[index] - self.assertDictEqual(permissions[1].capabilities, { # type: ignore[index] - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") # type: ignore[index] + self.assertEqual(permissions[0].grantee.id, "5e5e1978-71fa-11e4-87dd-7382f5c437af") # type: ignore[index] + self.assertDictEqual( + permissions[0].capabilities, # type: ignore[index] + { + TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny, + TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny, + TSC.Permission.Capability.Connect: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + }, + ) + + self.assertEqual(permissions[1].grantee.tag_name, "user") # type: ignore[index] + self.assertEqual(permissions[1].grantee.id, "7c37ee24-c4b1-42b6-a154-eaeab7ee330a") # type: ignore[index] + self.assertDictEqual( + permissions[1].capabilities, # type: ignore[index] + { + TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, + }, + ) def test_publish(self) -> None: response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') + new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS") publish_mode = self.server.PublishMode.CreateNew - new_datasource = self.server.datasources.publish(new_datasource, - asset('SampleDS.tds'), - mode=publish_mode) + new_datasource = self.server.datasources.publish(new_datasource, asset("SampleDS.tds"), mode=publish_mode) - self.assertEqual('e76a1461-3b1d-4588-bf1b-17551a879ad9', new_datasource.id) - self.assertEqual('SampleDS', new_datasource.name) - self.assertEqual('SampleDS', new_datasource.content_url) - self.assertEqual('dataengine', new_datasource.datasource_type) - self.assertEqual('2016-08-11T21:22:40Z', format_datetime(new_datasource.created_at)) - self.assertEqual('2016-08-17T23:37:08Z', format_datetime(new_datasource.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_datasource.project_id) - self.assertEqual('default', new_datasource.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) + self.assertEqual("e76a1461-3b1d-4588-bf1b-17551a879ad9", new_datasource.id) + self.assertEqual("SampleDS", new_datasource.name) + self.assertEqual("SampleDS", new_datasource.content_url) + self.assertEqual("dataengine", new_datasource.datasource_type) + self.assertEqual("2016-08-11T21:22:40Z", format_datetime(new_datasource.created_at)) + self.assertEqual("2016-08-17T23:37:08Z", format_datetime(new_datasource.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_datasource.project_id) + self.assertEqual("default", new_datasource.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_datasource.owner_id) def test_publish_a_non_packaged_file_object(self) -> None: response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') + 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: - new_datasource = self.server.datasources.publish(new_datasource, - file_object, - mode=publish_mode) - - self.assertEqual('e76a1461-3b1d-4588-bf1b-17551a879ad9', new_datasource.id) - self.assertEqual('SampleDS', new_datasource.name) - self.assertEqual('SampleDS', new_datasource.content_url) - self.assertEqual('dataengine', new_datasource.datasource_type) - self.assertEqual('2016-08-11T21:22:40Z', format_datetime(new_datasource.created_at)) - self.assertEqual('2016-08-17T23:37:08Z', format_datetime(new_datasource.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_datasource.project_id) - self.assertEqual('default', new_datasource.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) + with open(asset("SampleDS.tds"), "rb") as file_object: + new_datasource = self.server.datasources.publish(new_datasource, file_object, mode=publish_mode) + + self.assertEqual("e76a1461-3b1d-4588-bf1b-17551a879ad9", new_datasource.id) + self.assertEqual("SampleDS", new_datasource.name) + self.assertEqual("SampleDS", new_datasource.content_url) + self.assertEqual("dataengine", new_datasource.datasource_type) + self.assertEqual("2016-08-11T21:22:40Z", format_datetime(new_datasource.created_at)) + self.assertEqual("2016-08-17T23:37:08Z", format_datetime(new_datasource.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_datasource.project_id) + self.assertEqual("default", new_datasource.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_datasource.owner_id) def test_publish_a_packaged_file_object(self) -> None: response_xml = read_xml_asset(PUBLISH_XML) with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') + new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS") publish_mode = self.server.PublishMode.CreateNew # Create a dummy tdsx file in memory with BytesIO() as zip_archive: - with ZipFile(zip_archive, 'w') as zf: - zf.write(asset('SampleDS.tds')) + with ZipFile(zip_archive, "w") as zf: + zf.write(asset("SampleDS.tds")) zip_archive.seek(0) - new_datasource = self.server.datasources.publish(new_datasource, - zip_archive, - mode=publish_mode) + new_datasource = self.server.datasources.publish(new_datasource, zip_archive, mode=publish_mode) - self.assertEqual('e76a1461-3b1d-4588-bf1b-17551a879ad9', new_datasource.id) - self.assertEqual('SampleDS', new_datasource.name) - self.assertEqual('SampleDS', new_datasource.content_url) - self.assertEqual('dataengine', new_datasource.datasource_type) - self.assertEqual('2016-08-11T21:22:40Z', format_datetime(new_datasource.created_at)) - self.assertEqual('2016-08-17T23:37:08Z', format_datetime(new_datasource.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_datasource.project_id) - self.assertEqual('default', new_datasource.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_datasource.owner_id) + self.assertEqual("e76a1461-3b1d-4588-bf1b-17551a879ad9", new_datasource.id) + self.assertEqual("SampleDS", new_datasource.name) + self.assertEqual("SampleDS", new_datasource.content_url) + self.assertEqual("dataengine", new_datasource.datasource_type) + self.assertEqual("2016-08-11T21:22:40Z", format_datetime(new_datasource.created_at)) + self.assertEqual("2016-08-17T23:37:08Z", format_datetime(new_datasource.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_datasource.project_id) + self.assertEqual("default", new_datasource.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_datasource.owner_id) def test_publish_async(self) -> None: self.server.version = "3.0" @@ -307,75 +309,74 @@ def test_publish_async(self) -> None: response_xml = read_xml_asset(PUBLISH_XML_ASYNC) with requests_mock.mock() as m: m.post(baseurl, text=response_xml) - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'SampleDS') + new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS") publish_mode = self.server.PublishMode.CreateNew - new_job = self.server.datasources.publish(new_datasource, - asset('SampleDS.tds'), - mode=publish_mode, - as_job=True) + new_job = self.server.datasources.publish( + new_datasource, asset("SampleDS.tds"), mode=publish_mode, as_job=True + ) - self.assertEqual('9a373058-af5f-4f83-8662-98b3e0228a73', new_job.id) - self.assertEqual('PublishDatasource', new_job.type) - self.assertEqual('0', new_job.progress) - self.assertEqual('2018-06-30T00:54:54Z', format_datetime(new_job.created_at)) + self.assertEqual("9a373058-af5f-4f83-8662-98b3e0228a73", new_job.id) + self.assertEqual("PublishDatasource", new_job.type) + self.assertEqual("0", new_job.progress) + self.assertEqual("2018-06-30T00:54:54Z", format_datetime(new_job.created_at)) self.assertEqual(1, new_job.finish_code) def test_publish_unnamed_file_object(self) -> None: - new_datasource = TSC.DatasourceItem('test') + new_datasource = TSC.DatasourceItem("test") publish_mode = self.server.PublishMode.CreateNew - with open(asset('SampleDS.tds'), 'rb') as file_object: - self.assertRaises(ValueError, self.server.datasources.publish, - new_datasource, file_object, publish_mode - ) + with open(asset("SampleDS.tds"), "rb") as file_object: + self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, file_object, publish_mode) def test_refresh_id(self) -> None: - self.server.version = '2.8' + self.server.version = "2.8" self.baseurl = self.server.datasources.baseurl response_xml = read_xml_asset(REFRESH_XML) with requests_mock.mock() as m: - m.post(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh', - status_code=202, text=response_xml) - new_job = self.server.datasources.refresh('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb') + m.post(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh", status_code=202, text=response_xml) + new_job = self.server.datasources.refresh("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb") - self.assertEqual('7c3d599e-949f-44c3-94a1-f30ba85757e4', new_job.id) - self.assertEqual('RefreshExtract', new_job.type) + self.assertEqual("7c3d599e-949f-44c3-94a1-f30ba85757e4", new_job.id) + self.assertEqual("RefreshExtract", new_job.type) self.assertEqual(None, new_job.progress) - self.assertEqual('2020-03-05T22:05:32Z', format_datetime(new_job.created_at)) + self.assertEqual("2020-03-05T22:05:32Z", format_datetime(new_job.created_at)) self.assertEqual(-1, new_job.finish_code) def test_refresh_object(self) -> None: - self.server.version = '2.8' + self.server.version = "2.8" self.baseurl = self.server.datasources.baseurl - datasource = TSC.DatasourceItem('') - datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + datasource = TSC.DatasourceItem("") + datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" response_xml = read_xml_asset(REFRESH_XML) with requests_mock.mock() as m: - m.post(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh', - status_code=202, text=response_xml) + m.post(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh", status_code=202, text=response_xml) new_job = self.server.datasources.refresh(datasource) # We only check the `id`; remaining fields are already tested in `test_refresh_id` - self.assertEqual('7c3d599e-949f-44c3-94a1-f30ba85757e4', new_job.id) + self.assertEqual("7c3d599e-949f-44c3-94a1-f30ba85757e4", new_job.id) def test_update_hyper_data_datasource_object(self) -> None: """Calling `update_hyper_data` with a `DatasourceItem` should update that datasource""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl - datasource = TSC.DatasourceItem('') - datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + datasource = TSC.DatasourceItem("") + datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" response_xml = read_xml_asset(UPDATE_HYPER_DATA_XML) with requests_mock.mock() as m: - m.patch(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data', - status_code=202, headers={"requestid": "test_id"}, text=response_xml) + m.patch( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data", + status_code=202, + headers={"requestid": "test_id"}, + text=response_xml, + ) new_job = self.server.datasources.update_hyper_data(datasource, request_id="test_id", actions=[]) - self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) - self.assertEqual('UpdateUploadedFile', new_job.type) + self.assertEqual("5c0ba560-c959-424e-b08a-f32ef0bfb737", new_job.id) + self.assertEqual("UpdateUploadedFile", new_job.type) self.assertEqual(None, new_job.progress) - self.assertEqual('2021-09-18T09:40:12Z', format_datetime(new_job.created_at)) + self.assertEqual("2021-09-18T09:40:12Z", format_datetime(new_job.created_at)) self.assertEqual(-1, new_job.finish_code) def test_update_hyper_data_connection_object(self) -> None: @@ -384,71 +385,87 @@ def test_update_hyper_data_connection_object(self) -> None: self.baseurl = self.server.datasources.baseurl connection = TSC.ConnectionItem() - connection._datasource_id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' - connection._id = '7ecaccd8-39b0-4875-a77d-094f6e930019' + connection._datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" + connection._id = "7ecaccd8-39b0-4875-a77d-094f6e930019" response_xml = read_xml_asset(UPDATE_HYPER_DATA_XML) with requests_mock.mock() as m: - m.patch(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/7ecaccd8-39b0-4875-a77d-094f6e930019/data', - status_code=202, headers={"requestid": "test_id"}, text=response_xml) + m.patch( + self.baseurl + + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/7ecaccd8-39b0-4875-a77d-094f6e930019/data", + status_code=202, + headers={"requestid": "test_id"}, + text=response_xml, + ) new_job = self.server.datasources.update_hyper_data(connection, request_id="test_id", actions=[]) # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object` - self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) + self.assertEqual("5c0ba560-c959-424e-b08a-f32ef0bfb737", new_job.id) def test_update_hyper_data_datasource_string(self) -> None: """For convenience, calling `update_hyper_data` with a `str` should update the datasource with the corresponding UUID""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl - datasource_id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" response_xml = read_xml_asset(UPDATE_HYPER_DATA_XML) with requests_mock.mock() as m: - m.patch(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data', - status_code=202, headers={"requestid": "test_id"}, text=response_xml) + m.patch( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data", + status_code=202, + headers={"requestid": "test_id"}, + text=response_xml, + ) new_job = self.server.datasources.update_hyper_data(datasource_id, request_id="test_id", actions=[]) # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object` - self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) + self.assertEqual("5c0ba560-c959-424e-b08a-f32ef0bfb737", new_job.id) def test_update_hyper_data_datasource_payload_file(self) -> None: """If `payload` is present, we upload it and associate the job with it""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl - datasource_id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' - mock_upload_id = '10051:c3e56879876842d4b3600f20c1f79876-0:0' + datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" + mock_upload_id = "10051:c3e56879876842d4b3600f20c1f79876-0:0" response_xml = read_xml_asset(UPDATE_HYPER_DATA_XML) - with requests_mock.mock() as rm, \ - unittest.mock.patch.object(Fileuploads, "upload", return_value=mock_upload_id): - rm.patch(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data?uploadSessionId=' + mock_upload_id, - status_code=202, headers={"requestid": "test_id"}, text=response_xml) - new_job = self.server.datasources.update_hyper_data(datasource_id, request_id="test_id", - actions=[], payload=asset('World Indicators.hyper')) + with requests_mock.mock() as rm, unittest.mock.patch.object(Fileuploads, "upload", return_value=mock_upload_id): + rm.patch( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data?uploadSessionId=" + mock_upload_id, + status_code=202, + headers={"requestid": "test_id"}, + text=response_xml, + ) + new_job = self.server.datasources.update_hyper_data( + datasource_id, request_id="test_id", actions=[], payload=asset("World Indicators.hyper") + ) # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object` - self.assertEqual('5c0ba560-c959-424e-b08a-f32ef0bfb737', new_job.id) + self.assertEqual("5c0ba560-c959-424e-b08a-f32ef0bfb737", new_job.id) def test_update_hyper_data_datasource_invalid_payload_file(self) -> None: """If `payload` points to a non-existing file, we report an error""" self.server.version = "3.13" self.baseurl = self.server.datasources.baseurl - datasource_id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" with self.assertRaises(IOError) as cm: - self.server.datasources.update_hyper_data(datasource_id, request_id="test_id", - actions=[], payload='no/such/file.missing') + self.server.datasources.update_hyper_data( + datasource_id, request_id="test_id", actions=[], payload="no/such/file.missing" + ) exception = cm.exception self.assertEqual(str(exception), "File path does not lead to an existing file.") def test_delete(self) -> None: with requests_mock.mock() as m: - m.delete(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', status_code=204) - self.server.datasources.delete('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb') + m.delete(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", status_code=204) + self.server.datasources.delete("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb") def test_download(self) -> None: with requests_mock.mock() as m: - m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content', - headers={'Content-Disposition': 'name="tableau_datasource"; filename="Sample datasource.tds"'}) - file_path = self.server.datasources.download('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb') + m.get( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content", + headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'}, + ) + file_path = self.server.datasources.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb") self.assertTrue(os.path.exists(file_path)) os.remove(file_path) @@ -456,9 +473,11 @@ def test_download_sanitizes_name(self) -> None: filename = "Name,With,Commas.tds" disposition = 'name="tableau_workbook"; filename="{}"'.format(filename) with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/content', - headers={'Content-Disposition': disposition}) - file_path = self.server.datasources.download('1f951daf-4061-451a-9df1-69a8062664f2') + m.get( + self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content", + headers={"Content-Disposition": disposition}, + ) + file_path = self.server.datasources.download("1f951daf-4061-451a-9df1-69a8062664f2") self.assertEqual(os.path.basename(file_path), "NameWithCommas.tds") self.assertTrue(os.path.exists(file_path)) os.remove(file_path) @@ -469,117 +488,132 @@ def test_download_extract_only(self) -> None: self.baseurl = self.server.datasources.baseurl with requests_mock.mock() as m: - m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content?includeExtract=False', - headers={'Content-Disposition': 'name="tableau_datasource"; filename="Sample datasource.tds"'}, - complete_qs=True) - file_path = self.server.datasources.download('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', include_extract=False) + m.get( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content?includeExtract=False", + headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'}, + complete_qs=True, + ) + file_path = self.server.datasources.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", include_extract=False) self.assertTrue(os.path.exists(file_path)) os.remove(file_path) def test_update_missing_id(self) -> None: - single_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') + 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) -> None: - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') - self.assertRaises(IOError, self.server.datasources.publish, new_datasource, - '', self.server.PublishMode.CreateNew) + 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) -> None: - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') - self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, - asset('SampleDS.tds'), None) + 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) -> None: - 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) + 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) -> None: - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') - with open(asset('World Indicators.hyper'), 'rb') as file_object: - self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, - file_object, self.server.PublishMode.Append) + new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test") + with open(asset("World Indicators.hyper"), "rb") 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) -> None: - 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, 'rb') as file_object: - self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, - file_object, self.server.PublishMode.Append) + 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, "rb") 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) -> None: - new_datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'test') + new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test") with BytesIO() as file_object: - file_object.write(bytes.fromhex('89504E470D0A1A0A')) + file_object.write(bytes.fromhex("89504E470D0A1A0A")) file_object.seek(0) - self.assertRaises(ValueError, self.server.datasources.publish, new_datasource, - file_object, self.server.PublishMode.Append) + self.assertRaises( + ValueError, self.server.datasources.publish, new_datasource, file_object, self.server.PublishMode.Append + ) def test_publish_multi_connection(self) -> None: - new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem(name="Sample", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") connection1 = TSC.ConnectionItem() - connection1.server_address = 'mysql.test.com' - connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection1.server_address = "mysql.test.com" + connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True) connection2 = TSC.ConnectionItem() - connection2.server_address = 'pgsql.test.com' - connection2.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection2.server_address = "pgsql.test.com" + connection2.connection_credentials = TSC.ConnectionCredentials("test", "secret", True) response = RequestFactory.Datasource._generate_xml(new_datasource, connections=[connection1, connection2]) # Can't use ConnectionItem parser due to xml namespace problems - connection_results = ET.fromstring(response).findall('.//connection') + connection_results = ET.fromstring(response).findall(".//connection") - self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') - self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') # type: ignore[union-attr] - self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') - self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') # type: ignore[union-attr] + self.assertEqual(connection_results[0].get("serverAddress", None), "mysql.test.com") + self.assertEqual(connection_results[0].find("connectionCredentials").get("name", None), "test") # type: ignore[union-attr] + self.assertEqual(connection_results[1].get("serverAddress", None), "pgsql.test.com") + self.assertEqual(connection_results[1].find("connectionCredentials").get("password", None), "secret") # type: ignore[union-attr] def test_publish_single_connection(self) -> None: - new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + new_datasource = TSC.DatasourceItem(name="Sample", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") + connection_creds = TSC.ConnectionCredentials("test", "secret", True) response = RequestFactory.Datasource._generate_xml(new_datasource, connection_credentials=connection_creds) # Can't use ConnectionItem parser due to xml namespace problems - credentials = ET.fromstring(response).findall('.//connectionCredentials') + credentials = ET.fromstring(response).findall(".//connectionCredentials") self.assertEqual(len(credentials), 1) - self.assertEqual(credentials[0].get('name', None), 'test') - self.assertEqual(credentials[0].get('password', None), 'secret') - self.assertEqual(credentials[0].get('embed', None), 'true') + self.assertEqual(credentials[0].get("name", None), "test") + self.assertEqual(credentials[0].get("password", None), "secret") + self.assertEqual(credentials[0].get("embed", None), "true") def test_credentials_and_multi_connect_raises_exception(self) -> None: - new_datasource = TSC.DatasourceItem(name='Sample', project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_datasource = TSC.DatasourceItem(name="Sample", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") - connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + connection_creds = TSC.ConnectionCredentials("test", "secret", True) connection1 = TSC.ConnectionItem() - connection1.server_address = 'mysql.test.com' - connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection1.server_address = "mysql.test.com" + connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True) with self.assertRaises(RuntimeError): - response = RequestFactory.Datasource._generate_xml(new_datasource, - connection_credentials=connection_creds, - connections=[connection1]) + response = RequestFactory.Datasource._generate_xml( + new_datasource, connection_credentials=connection_creds, connections=[connection1] + ) def test_synchronous_publish_timeout_error(self) -> None: with requests_mock.mock() as m: - m.register_uri('POST', self.baseurl, status_code=504) + m.register_uri("POST", self.baseurl, status_code=504) - new_datasource = TSC.DatasourceItem(project_id='') + new_datasource = TSC.DatasourceItem(project_id="") publish_mode = self.server.PublishMode.CreateNew - self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts.', - self.server.datasources.publish, new_datasource, - asset('SampleDS.tds'), publish_mode) + self.assertRaisesRegex( + InternalServerError, + "Please use asynchronous publishing to avoid timeouts.", + self.server.datasources.publish, + new_datasource, + asset("SampleDS.tds"), + publish_mode, + ) def test_delete_extracts(self) -> None: self.server.version = "3.10" self.baseurl = self.server.datasources.baseurl with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract', status_code=200) - self.server.datasources.delete_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.post(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract", status_code=200) + self.server.datasources.delete_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42") def test_create_extracts(self) -> None: self.server.version = "3.10" @@ -587,9 +621,10 @@ def test_create_extracts(self) -> None: response_xml = read_xml_asset(PUBLISH_XML_ASYNC) with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract', - status_code=200, text=response_xml) - self.server.datasources.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.post( + self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract", status_code=200, text=response_xml + ) + self.server.datasources.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42") def test_create_extracts_encrypted(self) -> None: self.server.version = "3.10" @@ -597,6 +632,53 @@ def test_create_extracts_encrypted(self) -> None: response_xml = read_xml_asset(PUBLISH_XML_ASYNC) with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract', - status_code=200, text=response_xml) - self.server.datasources.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42', True) + m.post( + self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract", status_code=200, text=response_xml + ) + self.server.datasources.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42", True) + + def test_revisions(self) -> None: + datasource = TSC.DatasourceItem("project", "test") + datasource._id = "06b944d2-959d-4604-9305-12323c95e70e" + + response_xml = read_xml_asset(REVISION_XML) + with requests_mock.mock() as m: + m.get("{0}/{1}/revisions".format(self.baseurl, datasource.id), text=response_xml) + self.server.datasources.populate_revisions(datasource) + revisions = datasource.revisions + + self.assertEqual(len(revisions), 3) + self.assertEqual("2016-07-26T20:34:56Z", format_datetime(revisions[0].created_at)) + self.assertEqual("2016-07-27T20:34:56Z", format_datetime(revisions[1].created_at)) + self.assertEqual("2016-07-28T20:34:56Z", format_datetime(revisions[2].created_at)) + + self.assertEqual(False, revisions[0].deleted) + self.assertEqual(False, revisions[0].current) + self.assertEqual(False, revisions[1].deleted) + self.assertEqual(False, revisions[1].current) + self.assertEqual(False, revisions[2].deleted) + self.assertEqual(True, revisions[2].current) + + self.assertEqual("Cassie", revisions[0].user_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", revisions[0].user_id) + self.assertIsNone(revisions[1].user_name) + self.assertIsNone(revisions[1].user_id) + self.assertEqual("Cassie", revisions[2].user_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", revisions[2].user_id) + + def test_delete_revision(self) -> None: + datasource = TSC.DatasourceItem("project", "test") + datasource._id = "06b944d2-959d-4604-9305-12323c95e70e" + + with requests_mock.mock() as m: + m.delete("{0}/{1}/revisions/3".format(self.baseurl, datasource.id)) + self.server.datasources.delete_revision(datasource.id, "3") + + def test_download_revision(self) -> None: + with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td: + m.get( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/revisions/3/content", + headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'}, + ) + file_path = self.server.datasources.download_revision("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", "3", td) + self.assertTrue(os.path.exists(file_path)) diff --git a/test/test_exponential_backoff.py b/test/test_exponential_backoff.py index 57229d4ce..98618843c 100644 --- a/test/test_exponential_backoff.py +++ b/test/test_exponential_backoff.py @@ -21,7 +21,6 @@ def test_exponential(self): exponentialBackoff.sleep() self.assertAlmostEqual(mock_time(), 5.4728) - def test_exponential_saturation(self): with mocked_time() as mock_time: exponentialBackoff = ExponentialBackoffTimer() @@ -36,7 +35,6 @@ def test_exponential_saturation(self): slept = mock_time() - s self.assertAlmostEqual(slept, 30) - def test_timeout(self): with mocked_time() as mock_time: exponentialBackoff = ExponentialBackoffTimer(timeout=4.5) @@ -52,11 +50,10 @@ def test_timeout(self): with self.assertRaises(TimeoutError): exponentialBackoff.sleep() - def test_timeout_zero(self): with mocked_time() as mock_time: # The construction of the timer doesn't throw, yet - exponentialBackoff = ExponentialBackoffTimer(timeout = 0) + exponentialBackoff = ExponentialBackoffTimer(timeout=0) # But the first `sleep` immediately throws with self.assertRaises(TimeoutError): exponentialBackoff.sleep() diff --git a/test/test_favorites.py b/test/test_favorites.py index f76517b64..cd019fe75 100644 --- a/test/test_favorites.py +++ b/test/test_favorites.py @@ -8,122 +8,113 @@ from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset -GET_FAVORITES_XML = 'favorites_get.xml' -ADD_FAVORITE_WORKBOOK_XML = 'favorites_add_workbook.xml' -ADD_FAVORITE_VIEW_XML = 'favorites_add_view.xml' -ADD_FAVORITE_DATASOURCE_XML = 'favorites_add_datasource.xml' -ADD_FAVORITE_PROJECT_XML = 'favorites_add_project.xml' +GET_FAVORITES_XML = "favorites_get.xml" +ADD_FAVORITE_WORKBOOK_XML = "favorites_add_workbook.xml" +ADD_FAVORITE_VIEW_XML = "favorites_add_view.xml" +ADD_FAVORITE_DATASOURCE_XML = "favorites_add_datasource.xml" +ADD_FAVORITE_PROJECT_XML = "favorites_add_project.xml" class FavoritesTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') - self.server.version = '2.5' + self.server = TSC.Server("http://test") + self.server.version = "2.5" # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.favorites.baseurl - self.user = TSC.UserItem('alice', TSC.UserItem.Roles.Viewer) - self.user._id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' + self.user = TSC.UserItem("alice", TSC.UserItem.Roles.Viewer) + self.user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794" - def test_get(self): + def test_get(self) -> None: response_xml = read_xml_asset(GET_FAVORITES_XML) with requests_mock.mock() as m: - m.get('{0}/{1}'.format(self.baseurl, self.user.id), - text=response_xml) + m.get("{0}/{1}".format(self.baseurl, self.user.id), text=response_xml) self.server.favorites.get(self.user) self.assertIsNotNone(self.user._favorites) - self.assertEqual(len(self.user.favorites['workbooks']), 1) - self.assertEqual(len(self.user.favorites['views']), 1) - self.assertEqual(len(self.user.favorites['projects']), 1) - self.assertEqual(len(self.user.favorites['datasources']), 1) - - workbook = self.user.favorites['workbooks'][0] - view = self.user.favorites['views'][0] - datasource = self.user.favorites['datasources'][0] - project = self.user.favorites['projects'][0] - - self.assertEqual(workbook.id, '6d13b0ca-043d-4d42-8c9d-3f3313ea3a00') - self.assertEqual(view.id, 'd79634e1-6063-4ec9-95ff-50acbf609ff5') - self.assertEqual(datasource.id, 'e76a1461-3b1d-4588-bf1b-17551a879ad9') - self.assertEqual(project.id, '1d0304cd-3796-429f-b815-7258370b9b74') - - def test_add_favorite_workbook(self): + self.assertEqual(len(self.user.favorites["workbooks"]), 1) + self.assertEqual(len(self.user.favorites["views"]), 1) + self.assertEqual(len(self.user.favorites["projects"]), 1) + self.assertEqual(len(self.user.favorites["datasources"]), 1) + + workbook = self.user.favorites["workbooks"][0] + view = self.user.favorites["views"][0] + datasource = self.user.favorites["datasources"][0] + project = self.user.favorites["projects"][0] + + self.assertEqual(workbook.id, "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00") + self.assertEqual(view.id, "d79634e1-6063-4ec9-95ff-50acbf609ff5") + self.assertEqual(datasource.id, "e76a1461-3b1d-4588-bf1b-17551a879ad9") + self.assertEqual(project.id, "1d0304cd-3796-429f-b815-7258370b9b74") + + def test_add_favorite_workbook(self) -> None: response_xml = read_xml_asset(ADD_FAVORITE_WORKBOOK_XML) - workbook = TSC.WorkbookItem('') - workbook._id = '6d13b0ca-043d-4d42-8c9d-3f3313ea3a00' - workbook.name = 'Superstore' + workbook = TSC.WorkbookItem("") + workbook._id = "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" + workbook.name = "Superstore" with requests_mock.mock() as m: - m.put('{0}/{1}'.format(self.baseurl, self.user.id), - text=response_xml) + m.put("{0}/{1}".format(self.baseurl, self.user.id), text=response_xml) self.server.favorites.add_favorite_workbook(self.user, workbook) - def test_add_favorite_view(self): + def test_add_favorite_view(self) -> None: response_xml = read_xml_asset(ADD_FAVORITE_VIEW_XML) view = TSC.ViewItem() - view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' - view._name = 'ENDANGERED SAFARI' + view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" + view._name = "ENDANGERED SAFARI" with requests_mock.mock() as m: - m.put('{0}/{1}'.format(self.baseurl, self.user.id), - text=response_xml) + m.put("{0}/{1}".format(self.baseurl, self.user.id), text=response_xml) self.server.favorites.add_favorite_view(self.user, view) - def test_add_favorite_datasource(self): + def test_add_favorite_datasource(self) -> None: response_xml = read_xml_asset(ADD_FAVORITE_DATASOURCE_XML) - datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - datasource._id = 'e76a1461-3b1d-4588-bf1b-17551a879ad9' - datasource.name = 'SampleDS' + datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") + datasource._id = "e76a1461-3b1d-4588-bf1b-17551a879ad9" + datasource.name = "SampleDS" with requests_mock.mock() as m: - m.put('{0}/{1}'.format(self.baseurl, self.user.id), - text=response_xml) + m.put("{0}/{1}".format(self.baseurl, self.user.id), text=response_xml) self.server.favorites.add_favorite_datasource(self.user, datasource) - def test_add_favorite_project(self): - self.server.version = '3.1' + def test_add_favorite_project(self) -> None: + self.server.version = "3.1" baseurl = self.server.favorites.baseurl response_xml = read_xml_asset(ADD_FAVORITE_PROJECT_XML) - project = TSC.ProjectItem('Tableau') - project._id = '1d0304cd-3796-429f-b815-7258370b9b74' + project = TSC.ProjectItem("Tableau") + project._id = "1d0304cd-3796-429f-b815-7258370b9b74" with requests_mock.mock() as m: - m.put('{0}/{1}'.format(baseurl, self.user.id), - text=response_xml) + m.put("{0}/{1}".format(baseurl, self.user.id), text=response_xml) self.server.favorites.add_favorite_project(self.user, project) - def test_delete_favorite_workbook(self): - workbook = TSC.WorkbookItem('') - workbook._id = '6d13b0ca-043d-4d42-8c9d-3f3313ea3a00' - workbook.name = 'Superstore' + def test_delete_favorite_workbook(self) -> None: + workbook = TSC.WorkbookItem("") + workbook._id = "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" + workbook.name = "Superstore" with requests_mock.mock() as m: - m.delete('{0}/{1}/workbooks/{2}'.format(self.baseurl, self.user.id, - workbook.id)) + m.delete("{0}/{1}/workbooks/{2}".format(self.baseurl, self.user.id, workbook.id)) self.server.favorites.delete_favorite_workbook(self.user, workbook) - def test_delete_favorite_view(self): + def test_delete_favorite_view(self) -> None: view = TSC.ViewItem() - view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' - view._name = 'ENDANGERED SAFARI' + view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" + view._name = "ENDANGERED SAFARI" with requests_mock.mock() as m: - m.delete('{0}/{1}/views/{2}'.format(self.baseurl, self.user.id, - view.id)) + m.delete("{0}/{1}/views/{2}".format(self.baseurl, self.user.id, view.id)) self.server.favorites.delete_favorite_view(self.user, view) - def test_delete_favorite_datasource(self): - datasource = TSC.DatasourceItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - datasource._id = 'e76a1461-3b1d-4588-bf1b-17551a879ad9' - datasource.name = 'SampleDS' + def test_delete_favorite_datasource(self) -> None: + datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") + datasource._id = "e76a1461-3b1d-4588-bf1b-17551a879ad9" + datasource.name = "SampleDS" with requests_mock.mock() as m: - m.delete('{0}/{1}/datasources/{2}'.format(self.baseurl, self.user.id, - datasource.id)) + m.delete("{0}/{1}/datasources/{2}".format(self.baseurl, self.user.id, datasource.id)) self.server.favorites.delete_favorite_datasource(self.user, datasource) - def test_delete_favorite_project(self): - self.server.version = '3.1' + def test_delete_favorite_project(self) -> None: + self.server.version = "3.1" baseurl = self.server.favorites.baseurl - project = TSC.ProjectItem('Tableau') - project._id = '1d0304cd-3796-429f-b815-7258370b9b74' + project = TSC.ProjectItem("Tableau") + project._id = "1d0304cd-3796-429f-b815-7258370b9b74" with requests_mock.mock() as m: - m.delete('{0}/{1}/projects/{2}'.format(baseurl, self.user.id, - project.id)) + m.delete("{0}/{1}/projects/{2}".format(baseurl, self.user.id, project.id)) self.server.favorites.delete_favorite_project(self.user, project) diff --git a/test/test_filesys_helpers.py b/test/test_filesys_helpers.py index 82fce8476..e86e68847 100644 --- a/test/test_filesys_helpers.py +++ b/test/test_filesys_helpers.py @@ -9,7 +9,6 @@ class FilesysTests(unittest.TestCase): - def test_get_file_size_returns_correct_size(self): target_size = 1000 # bytes @@ -30,9 +29,9 @@ def test_get_file_size_returns_zero_for_empty_file(self): def test_get_file_size_coincides_with_built_in_method(self): - asset_path = asset('SampleWB.twbx') + asset_path = asset("SampleWB.twbx") target_size = os.path.getsize(asset_path) - with open(asset_path, 'rb') as f: + with open(asset_path, "rb") as f: file_size = get_file_object_size(f) self.assertEqual(file_size, target_size) @@ -40,61 +39,60 @@ def test_get_file_size_coincides_with_built_in_method(self): def test_get_file_type_identifies_a_zip_file(self): with BytesIO() as file_object: - with ZipFile(file_object, 'w') as zf: + with ZipFile(file_object, "w") as zf: with BytesIO() as stream: - stream.write('This is a zip file'.encode()) - zf.writestr('dummy_file', stream.getbuffer()) + stream.write("This is a zip file".encode()) + zf.writestr("dummy_file", stream.getbuffer()) file_object.seek(0) file_type = get_file_type(file_object) - self.assertEqual(file_type, 'zip') + self.assertEqual(file_type, "zip") def test_get_file_type_identifies_tdsx_as_zip_file(self): - with open(asset('World Indicators.tdsx'), 'rb') as file_object: + with open(asset("World Indicators.tdsx"), "rb") as file_object: file_type = get_file_type(file_object) - self.assertEqual(file_type, 'zip') + self.assertEqual(file_type, "zip") def test_get_file_type_identifies_twbx_as_zip_file(self): - with open(asset('SampleWB.twbx'), 'rb') as file_object: + with open(asset("SampleWB.twbx"), "rb") as file_object: file_type = get_file_type(file_object) - self.assertEqual(file_type, 'zip') + self.assertEqual(file_type, "zip") def test_get_file_type_identifies_xml_file(self): - root = ET.Element('root') - child = ET.SubElement(root, 'child') + root = ET.Element("root") + child = ET.SubElement(root, "child") child.text = "This is a child element" etree = ET.ElementTree(root) with BytesIO() as file_object: - etree.write(file_object, encoding='utf-8', - xml_declaration=True) + etree.write(file_object, encoding="utf-8", xml_declaration=True) file_object.seek(0) file_type = get_file_type(file_object) - self.assertEqual(file_type, 'xml') + self.assertEqual(file_type, "xml") def test_get_file_type_identifies_tds_as_xml_file(self): - with open(asset('World Indicators.tds'), 'rb') as file_object: + with open(asset("World Indicators.tds"), "rb") as file_object: file_type = get_file_type(file_object) - self.assertEqual(file_type, 'xml') + self.assertEqual(file_type, "xml") def test_get_file_type_identifies_twb_as_xml_file(self): - with open(asset('RESTAPISample.twb'), 'rb') as file_object: + with open(asset("RESTAPISample.twb"), "rb") as file_object: file_type = get_file_type(file_object) - self.assertEqual(file_type, 'xml') + self.assertEqual(file_type, "xml") def test_get_file_type_identifies_hyper_file(self): - with open(asset('World Indicators.hyper'), 'rb') as file_object: + with open(asset("World Indicators.hyper"), "rb") as file_object: file_type = get_file_type(file_object) - self.assertEqual(file_type, 'hyper') + self.assertEqual(file_type, "hyper") def test_get_file_type_identifies_tde_file(self): - asset_path = os.path.join(TEST_ASSET_DIR, 'Data', 'Tableau Samples', 'World Indicators.tde') - with open(asset_path, 'rb') as file_object: + asset_path = os.path.join(TEST_ASSET_DIR, "Data", "Tableau Samples", "World Indicators.tde") + with open(asset_path, "rb") as file_object: file_type = get_file_type(file_object) - self.assertEqual(file_type, 'tde') + self.assertEqual(file_type, "tde") def test_get_file_type_handles_unknown_file_type(self): diff --git a/test/test_fileuploads.py b/test/test_fileuploads.py index 51662e4a2..cd94c1ece 100644 --- a/test/test_fileuploads.py +++ b/test/test_fileuploads.py @@ -5,59 +5,59 @@ from ._utils import asset from tableauserverclient.server import Server -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') -FILEUPLOAD_INITIALIZE = os.path.join(TEST_ASSET_DIR, 'fileupload_initialize.xml') -FILEUPLOAD_APPEND = os.path.join(TEST_ASSET_DIR, 'fileupload_append.xml') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") +FILEUPLOAD_INITIALIZE = os.path.join(TEST_ASSET_DIR, "fileupload_initialize.xml") +FILEUPLOAD_APPEND = os.path.join(TEST_ASSET_DIR, "fileupload_append.xml") class FileuploadsTests(unittest.TestCase): def setUp(self): - self.server = Server('http://test') + self.server = Server("http://test") # Fake sign in - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" - self.baseurl = '{}/sites/{}/fileUploads'.format(self.server.baseurl, self.server.site_id) + self.baseurl = "{}/sites/{}/fileUploads".format(self.server.baseurl, self.server.site_id) def test_read_chunks_file_path(self): - file_path = asset('SampleWB.twbx') + file_path = asset("SampleWB.twbx") chunks = self.server.fileuploads._read_chunks(file_path) for chunk in chunks: self.assertIsNotNone(chunk) def test_read_chunks_file_object(self): - with open(asset('SampleWB.twbx'), 'rb') as f: + with open(asset("SampleWB.twbx"), "rb") as f: chunks = self.server.fileuploads._read_chunks(f) for chunk in chunks: self.assertIsNotNone(chunk) def test_upload_chunks_file_path(self): - file_path = asset('SampleWB.twbx') - upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0' + file_path = asset("SampleWB.twbx") + upload_id = "7720:170fe6b1c1c7422dadff20f944d58a52-1:0" - with open(FILEUPLOAD_INITIALIZE, 'rb') as f: - initialize_response_xml = f.read().decode('utf-8') - with open(FILEUPLOAD_APPEND, 'rb') as f: - append_response_xml = f.read().decode('utf-8') + with open(FILEUPLOAD_INITIALIZE, "rb") as f: + initialize_response_xml = f.read().decode("utf-8") + with open(FILEUPLOAD_APPEND, "rb") as f: + append_response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=initialize_response_xml) - m.put(self.baseurl + '/' + upload_id, text=append_response_xml) + m.put(self.baseurl + "/" + upload_id, text=append_response_xml) actual = self.server.fileuploads.upload(file_path) self.assertEqual(upload_id, actual) def test_upload_chunks_file_object(self): - upload_id = '7720:170fe6b1c1c7422dadff20f944d58a52-1:0' + upload_id = "7720:170fe6b1c1c7422dadff20f944d58a52-1:0" - with open(asset('SampleWB.twbx'), 'rb') as file_content: - with open(FILEUPLOAD_INITIALIZE, 'rb') as f: - initialize_response_xml = f.read().decode('utf-8') - with open(FILEUPLOAD_APPEND, 'rb') as f: - append_response_xml = f.read().decode('utf-8') + with open(asset("SampleWB.twbx"), "rb") as file_content: + with open(FILEUPLOAD_INITIALIZE, "rb") as f: + initialize_response_xml = f.read().decode("utf-8") + with open(FILEUPLOAD_APPEND, "rb") as f: + append_response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=initialize_response_xml) - m.put(self.baseurl + '/' + upload_id, text=append_response_xml) + m.put(self.baseurl + "/" + upload_id, text=append_response_xml) actual = self.server.fileuploads.upload(file_content) self.assertEqual(upload_id, actual) diff --git a/test/test_flow.py b/test/test_flow.py index f5c057c30..bced0884b 100644 --- a/test/test_flow.py +++ b/test/test_flow.py @@ -8,108 +8,130 @@ from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset -GET_XML = 'flow_get.xml' -POPULATE_CONNECTIONS_XML = 'flow_populate_connections.xml' -POPULATE_PERMISSIONS_XML = 'flow_populate_permissions.xml' -UPDATE_XML = 'flow_update.xml' +GET_XML = "flow_get.xml" +POPULATE_CONNECTIONS_XML = "flow_populate_connections.xml" +POPULATE_PERMISSIONS_XML = "flow_populate_permissions.xml" +UPDATE_XML = "flow_update.xml" +REFRESH_XML = "flow_refresh.xml" class FlowTests(unittest.TestCase): - def setUp(self): - self.server = TSC.Server('http://test') + def setUp(self) -> None: + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.server.version = "3.5" self.baseurl = self.server.flows.baseurl - def test_get(self): + def test_get(self) -> None: response_xml = read_xml_asset(GET_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_flows, pagination_item = self.server.flows.get() self.assertEqual(5, pagination_item.total_available) - self.assertEqual('587daa37-b84d-4400-a9a2-aa90e0be7837', all_flows[0].id) - self.assertEqual('http://tableauserver/#/flows/1', all_flows[0].webpage_url) - self.assertEqual('2019-06-16T21:43:28Z', format_datetime(all_flows[0].created_at)) - self.assertEqual('2019-06-16T21:43:28Z', format_datetime(all_flows[0].updated_at)) - self.assertEqual('Default', all_flows[0].project_name) - self.assertEqual('FlowOne', all_flows[0].name) - self.assertEqual('aa23f4ac-906f-11e9-86fb-3f0f71412e77', all_flows[0].project_id) - self.assertEqual('7ebb3f20-0fd2-4f27-a2f6-c539470999e2', all_flows[0].owner_id) - self.assertEqual({'i_love_tags'}, all_flows[0].tags) - self.assertEqual('Descriptive', all_flows[0].description) - - self.assertEqual('5c36be69-eb30-461b-b66e-3e2a8e27cc35', all_flows[1].id) - self.assertEqual('http://tableauserver/#/flows/4', all_flows[1].webpage_url) - self.assertEqual('2019-06-18T03:08:19Z', format_datetime(all_flows[1].created_at)) - self.assertEqual('2019-06-18T03:08:19Z', format_datetime(all_flows[1].updated_at)) - self.assertEqual('Default', all_flows[1].project_name) - self.assertEqual('FlowTwo', all_flows[1].name) - self.assertEqual('aa23f4ac-906f-11e9-86fb-3f0f71412e77', all_flows[1].project_id) - self.assertEqual('9127d03f-d996-405f-b392-631b25183a0f', all_flows[1].owner_id) - - def test_update(self): + self.assertEqual("587daa37-b84d-4400-a9a2-aa90e0be7837", all_flows[0].id) + self.assertEqual("http://tableauserver/#/flows/1", all_flows[0].webpage_url) + self.assertEqual("2019-06-16T21:43:28Z", format_datetime(all_flows[0].created_at)) + self.assertEqual("2019-06-16T21:43:28Z", format_datetime(all_flows[0].updated_at)) + self.assertEqual("Default", all_flows[0].project_name) + self.assertEqual("FlowOne", all_flows[0].name) + self.assertEqual("aa23f4ac-906f-11e9-86fb-3f0f71412e77", all_flows[0].project_id) + self.assertEqual("7ebb3f20-0fd2-4f27-a2f6-c539470999e2", all_flows[0].owner_id) + self.assertEqual({"i_love_tags"}, all_flows[0].tags) + self.assertEqual("Descriptive", all_flows[0].description) + + self.assertEqual("5c36be69-eb30-461b-b66e-3e2a8e27cc35", all_flows[1].id) + self.assertEqual("http://tableauserver/#/flows/4", all_flows[1].webpage_url) + self.assertEqual("2019-06-18T03:08:19Z", format_datetime(all_flows[1].created_at)) + self.assertEqual("2019-06-18T03:08:19Z", format_datetime(all_flows[1].updated_at)) + self.assertEqual("Default", all_flows[1].project_name) + self.assertEqual("FlowTwo", all_flows[1].name) + self.assertEqual("aa23f4ac-906f-11e9-86fb-3f0f71412e77", all_flows[1].project_id) + self.assertEqual("9127d03f-d996-405f-b392-631b25183a0f", all_flows[1].owner_id) + + def test_update(self) -> None: response_xml = read_xml_asset(UPDATE_XML) with requests_mock.mock() as m: - m.put(self.baseurl + '/587daa37-b84d-4400-a9a2-aa90e0be7837', text=response_xml) - single_datasource = TSC.FlowItem('test', 'aa23f4ac-906f-11e9-86fb-3f0f71412e77') - single_datasource.owner_id = '7ebb3f20-0fd2-4f27-a2f6-c539470999e2' - single_datasource._id = '587daa37-b84d-4400-a9a2-aa90e0be7837' + m.put(self.baseurl + "/587daa37-b84d-4400-a9a2-aa90e0be7837", text=response_xml) + single_datasource = TSC.FlowItem("test", "aa23f4ac-906f-11e9-86fb-3f0f71412e77") + single_datasource.owner_id = "7ebb3f20-0fd2-4f27-a2f6-c539470999e2" + single_datasource._id = "587daa37-b84d-4400-a9a2-aa90e0be7837" single_datasource.description = "So fun to see" single_datasource = self.server.flows.update(single_datasource) - self.assertEqual('587daa37-b84d-4400-a9a2-aa90e0be7837', single_datasource.id) - self.assertEqual('aa23f4ac-906f-11e9-86fb-3f0f71412e77', single_datasource.project_id) - self.assertEqual('7ebb3f20-0fd2-4f27-a2f6-c539470999e2', single_datasource.owner_id) + self.assertEqual("587daa37-b84d-4400-a9a2-aa90e0be7837", single_datasource.id) + self.assertEqual("aa23f4ac-906f-11e9-86fb-3f0f71412e77", single_datasource.project_id) + self.assertEqual("7ebb3f20-0fd2-4f27-a2f6-c539470999e2", single_datasource.owner_id) self.assertEqual("So fun to see", single_datasource.description) - def test_populate_connections(self): + def test_populate_connections(self) -> None: 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.FlowItem('test', 'aa23f4ac-906f-11e9-86fb-3f0f71412e77') - single_datasource.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' - single_datasource._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + m.get(self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml) + single_datasource = TSC.FlowItem("test", "aa23f4ac-906f-11e9-86fb-3f0f71412e77") + single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794" + single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" self.server.flows.populate_connections(single_datasource) - self.assertEqual('9dbd2263-16b5-46e1-9c43-a76bb8ab65fb', single_datasource.id) + self.assertEqual("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", single_datasource.id) connections = single_datasource.connections self.assertTrue(connections) conn1, conn2, conn3 = connections - self.assertEqual('405c1e4b-60c9-499f-9c47-a4ef1af69359', conn1.id) - self.assertEqual('excel-direct', conn1.connection_type) - self.assertEqual('', conn1.server_address) - self.assertEqual('', conn1.username) + self.assertEqual("405c1e4b-60c9-499f-9c47-a4ef1af69359", conn1.id) + self.assertEqual("excel-direct", conn1.connection_type) + self.assertEqual("", conn1.server_address) + self.assertEqual("", conn1.username) self.assertEqual(False, conn1.embed_password) - self.assertEqual('b47f41b1-2c47-41a3-8b17-a38ebe8b340c', conn2.id) - self.assertEqual('sqlserver', conn2.connection_type) - self.assertEqual('test.database.com', conn2.server_address) - self.assertEqual('bob', conn2.username) + self.assertEqual("b47f41b1-2c47-41a3-8b17-a38ebe8b340c", conn2.id) + self.assertEqual("sqlserver", conn2.connection_type) + self.assertEqual("test.database.com", conn2.server_address) + self.assertEqual("bob", conn2.username) self.assertEqual(False, conn2.embed_password) - self.assertEqual('4f4a3b78-0554-43a7-b327-9605e9df9dd2', conn3.id) - self.assertEqual('tableau-server-site', conn3.connection_type) - self.assertEqual('http://tableauserver', conn3.server_address) - self.assertEqual('sally', conn3.username) + self.assertEqual("4f4a3b78-0554-43a7-b327-9605e9df9dd2", conn3.id) + self.assertEqual("tableau-server-site", conn3.connection_type) + self.assertEqual("http://tableauserver", conn3.server_address) + self.assertEqual("sally", conn3.username) self.assertEqual(True, conn3.embed_password) - def test_populate_permissions(self): - with open(asset(POPULATE_PERMISSIONS_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + def test_populate_permissions(self) -> None: + with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: + 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.FlowItem('test') - single_datasource._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' + m.get(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml) + single_datasource = TSC.FlowItem("test") + single_datasource._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5" self.server.flows.populate_permissions(single_datasource) permissions = single_datasource.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, 'aa42f384-906f-11e9-86fc-bb24278874b9') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "aa42f384-906f-11e9-86fc-bb24278874b9") + self.assertDictEqual( + permissions[0].capabilities, + { + TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + }, + ) + + def test_refresh(self): + with open(asset(REFRESH_XML), "rb") as f: + response_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.post(self.baseurl + "/92967d2d-c7e2-46d0-8847-4802df58f484/run", text=response_xml) + flow_item = TSC.FlowItem("test") + flow_item._id = "92967d2d-c7e2-46d0-8847-4802df58f484" + refresh_job = self.server.flows.refresh(flow_item) + + self.assertEqual(refresh_job.id, "d1b2ccd0-6dfa-444a-aee4-723dbd6b7c9d") + self.assertEqual(refresh_job.mode, "Asynchronous") + self.assertEqual(refresh_job.type, "RunFlow") + self.assertEqual(format_datetime(refresh_job.created_at), "2018-05-22T13:00:29Z") + self.assertIsInstance(refresh_job.flow_run, TSC.FlowRunItem) + self.assertEqual(refresh_job.flow_run.id, "e0c3067f-2333-4eee-8028-e0a56ca496f6") + self.assertEqual(refresh_job.flow_run.flow_id, "92967d2d-c7e2-46d0-8847-4802df58f484") + self.assertEqual(format_datetime(refresh_job.flow_run.started_at), "2018-05-22T13:00:29Z") diff --git a/test/test_flowruns.py b/test/test_flowruns.py new file mode 100644 index 000000000..239d46687 --- /dev/null +++ b/test/test_flowruns.py @@ -0,0 +1,101 @@ +import unittest +import os +import requests_mock +import xml.etree.ElementTree as ET +import tableauserverclient as TSC +from tableauserverclient.datetime_helpers import format_datetime +from tableauserverclient.server.endpoint.exceptions import FlowRunFailedException +from tableauserverclient.server.request_factory import RequestFactory +from ._utils import read_xml_asset, mocked_time + +GET_XML = "flow_runs_get.xml" +GET_BY_ID_XML = "flow_runs_get_by_id.xml" +GET_BY_ID_FAILED_XML = "flow_runs_get_by_id_failed.xml" +GET_BY_ID_INPROGRESS_XML = "flow_runs_get_by_id_inprogress.xml" + + +class FlowRunTests(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" + self.server.version = "3.10" + + self.baseurl = self.server.flow_runs.baseurl + + def test_get(self): + response_xml = read_xml_asset(GET_XML) + with requests_mock.mock() as m: + m.get(self.baseurl, text=response_xml) + all_flow_runs, pagination_item = self.server.flow_runs.get() + + self.assertEqual(2, pagination_item.total_available) + self.assertEqual("cc2e652d-4a9b-4476-8c93-b238c45db968", all_flow_runs[0].id) + self.assertEqual("2021-02-11T01:42:55Z", format_datetime(all_flow_runs[0].started_at)) + self.assertEqual("2021-02-11T01:57:38Z", format_datetime(all_flow_runs[0].completed_at)) + self.assertEqual("Success", all_flow_runs[0].status) + self.assertEqual("100", all_flow_runs[0].progress) + self.assertEqual("aa23f4ac-906f-11e9-86fb-3f0f71412e77", all_flow_runs[0].background_job_id) + + self.assertEqual("a3104526-c0c6-4ea5-8362-e03fc7cbd7ee", all_flow_runs[1].id) + self.assertEqual("2021-02-13T04:05:30Z", format_datetime(all_flow_runs[1].started_at)) + self.assertEqual("2021-02-13T04:05:35Z", format_datetime(all_flow_runs[1].completed_at)) + self.assertEqual("Failed", all_flow_runs[1].status) + self.assertEqual("100", all_flow_runs[1].progress) + self.assertEqual("1ad21a9d-2530-4fbf-9064-efd3c736e023", all_flow_runs[1].background_job_id) + + def test_get_by_id(self): + response_xml = read_xml_asset(GET_BY_ID_XML) + with requests_mock.mock() as m: + m.get(self.baseurl + "/cc2e652d-4a9b-4476-8c93-b238c45db968", text=response_xml) + flow_run = self.server.flow_runs.get_by_id("cc2e652d-4a9b-4476-8c93-b238c45db968") + + self.assertEqual("cc2e652d-4a9b-4476-8c93-b238c45db968", flow_run.id) + self.assertEqual("2021-02-11T01:42:55Z", format_datetime(flow_run.started_at)) + self.assertEqual("2021-02-11T01:57:38Z", format_datetime(flow_run.completed_at)) + self.assertEqual("Success", flow_run.status) + self.assertEqual("100", flow_run.progress) + self.assertEqual("1ad21a9d-2530-4fbf-9064-efd3c736e023", flow_run.background_job_id) + + def test_cancel_id(self): + with requests_mock.mock() as m: + m.put(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204) + self.server.flow_runs.cancel("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") + + def test_cancel_item(self): + run = TSC.FlowRunItem() + run._id = "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + with requests_mock.mock() as m: + m.put(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204) + self.server.flow_runs.cancel(run) + + def test_wait_for_job_finished(self): + # Waiting for an already finished job, directly returns that job's info + response_xml = read_xml_asset(GET_BY_ID_XML) + flow_run_id = "cc2e652d-4a9b-4476-8c93-b238c45db968" + with mocked_time(), requests_mock.mock() as m: + m.get("{0}/{1}".format(self.baseurl, flow_run_id), text=response_xml) + flow_run = self.server.flow_runs.wait_for_job(flow_run_id) + + self.assertEqual(flow_run_id, flow_run.id) + self.assertEqual(flow_run.progress, "100") + + def test_wait_for_job_failed(self): + # Waiting for a failed job raises an exception + response_xml = read_xml_asset(GET_BY_ID_FAILED_XML) + flow_run_id = "c2b35d5a-e130-471a-aec8-7bc5435fe0e7" + with mocked_time(), requests_mock.mock() as m: + m.get("{0}/{1}".format(self.baseurl, flow_run_id), text=response_xml) + with self.assertRaises(FlowRunFailedException): + self.server.flow_runs.wait_for_job(flow_run_id) + + def test_wait_for_job_timeout(self): + # Waiting for a job which doesn't terminate will throw an exception + response_xml = read_xml_asset(GET_BY_ID_INPROGRESS_XML) + flow_run_id = "71afc22c-9c06-40be-8d0f-4c4166d29e6c" + with mocked_time(), requests_mock.mock() as m: + m.get("{0}/{1}".format(self.baseurl, flow_run_id), text=response_xml) + with self.assertRaises(TimeoutError): + self.server.flow_runs.wait_for_job(flow_run_id, timeout=30) diff --git a/test/test_group.py b/test/test_group.py index 082a63ba3..63155c6ea 100644 --- a/test/test_group.py +++ b/test/test_group.py @@ -5,230 +5,245 @@ import tableauserverclient as TSC from tableauserverclient.datetime_helpers import format_datetime -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -GET_XML = os.path.join(TEST_ASSET_DIR, 'group_get.xml') -POPULATE_USERS = os.path.join(TEST_ASSET_DIR, 'group_populate_users.xml') -POPULATE_USERS_EMPTY = os.path.join(TEST_ASSET_DIR, 'group_populate_users_empty.xml') -ADD_USER = os.path.join(TEST_ASSET_DIR, 'group_add_user.xml') -ADD_USER_POPULATE = os.path.join(TEST_ASSET_DIR, 'group_users_added.xml') -CREATE_GROUP = os.path.join(TEST_ASSET_DIR, 'group_create.xml') -CREATE_GROUP_AD = os.path.join(TEST_ASSET_DIR, 'group_create_ad.xml') -CREATE_GROUP_ASYNC = os.path.join(TEST_ASSET_DIR, 'group_create_async.xml') -UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'group_update.xml') +GET_XML = os.path.join(TEST_ASSET_DIR, "group_get.xml") +POPULATE_USERS = os.path.join(TEST_ASSET_DIR, "group_populate_users.xml") +POPULATE_USERS_EMPTY = os.path.join(TEST_ASSET_DIR, "group_populate_users_empty.xml") +ADD_USER = os.path.join(TEST_ASSET_DIR, "group_add_user.xml") +ADD_USER_POPULATE = os.path.join(TEST_ASSET_DIR, "group_users_added.xml") +CREATE_GROUP = os.path.join(TEST_ASSET_DIR, "group_create.xml") +CREATE_GROUP_AD = os.path.join(TEST_ASSET_DIR, "group_create_ad.xml") +CREATE_GROUP_ASYNC = os.path.join(TEST_ASSET_DIR, "group_create_async.xml") +UPDATE_XML = os.path.join(TEST_ASSET_DIR, "group_update.xml") class GroupTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.groups.baseurl def test_get(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_groups, pagination_item = self.server.groups.get() self.assertEqual(3, pagination_item.total_available) - self.assertEqual('ef8b19c0-43b6-11e6-af50-63f5805dbe3c', all_groups[0].id) - self.assertEqual('All Users', all_groups[0].name) - self.assertEqual('local', all_groups[0].domain_name) + self.assertEqual("ef8b19c0-43b6-11e6-af50-63f5805dbe3c", all_groups[0].id) + self.assertEqual("All Users", all_groups[0].name) + self.assertEqual("local", all_groups[0].domain_name) - self.assertEqual('e7833b48-c6f7-47b5-a2a7-36e7dd232758', all_groups[1].id) - self.assertEqual('Another group', all_groups[1].name) - self.assertEqual('local', all_groups[1].domain_name) + self.assertEqual("e7833b48-c6f7-47b5-a2a7-36e7dd232758", all_groups[1].id) + self.assertEqual("Another group", all_groups[1].name) + self.assertEqual("local", all_groups[1].domain_name) - self.assertEqual('86a66d40-f289-472a-83d0-927b0f954dc8', all_groups[2].id) - self.assertEqual('TableauExample', all_groups[2].name) - self.assertEqual('local', all_groups[2].domain_name) + self.assertEqual("86a66d40-f289-472a-83d0-927b0f954dc8", all_groups[2].id) + self.assertEqual("TableauExample", all_groups[2].name) + self.assertEqual("local", all_groups[2].domain_name) def test_get_before_signin(self): self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.groups.get) def test_populate_users(self): - with open(POPULATE_USERS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_USERS, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users?pageNumber=1&pageSize=100', - text=response_xml, complete_qs=True) - single_group = TSC.GroupItem(name='Test Group') - single_group._id = 'e7833b48-c6f7-47b5-a2a7-36e7dd232758' + m.get( + self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users?pageNumber=1&pageSize=100", + text=response_xml, + complete_qs=True, + ) + single_group = TSC.GroupItem(name="Test Group") + single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758" self.server.groups.populate_users(single_group) self.assertEqual(1, len(list(single_group.users))) user = list(single_group.users).pop() - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', user.id) - self.assertEqual('alice', user.name) - self.assertEqual('Publisher', user.site_role) - self.assertEqual('2016-08-16T23:17:06Z', format_datetime(user.last_login)) + self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", user.id) + self.assertEqual("alice", user.name) + self.assertEqual("Publisher", user.site_role) + self.assertEqual("2016-08-16T23:17:06Z", format_datetime(user.last_login)) def test_delete(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758', status_code=204) - self.server.groups.delete('e7833b48-c6f7-47b5-a2a7-36e7dd232758') + m.delete(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758", status_code=204) + self.server.groups.delete("e7833b48-c6f7-47b5-a2a7-36e7dd232758") def test_remove_user(self): - with open(POPULATE_USERS, 'rb') as f: - response_xml_populate = f.read().decode('utf-8') + with open(POPULATE_USERS, "rb") as f: + response_xml_populate = f.read().decode("utf-8") - with open(POPULATE_USERS_EMPTY, 'rb') as f: - response_xml_empty = f.read().decode('utf-8') + with open(POPULATE_USERS_EMPTY, "rb") as f: + response_xml_empty = f.read().decode("utf-8") with requests_mock.mock() as m: - url = self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users' \ - '/dd2239f6-ddf1-4107-981a-4cf94e415794' + url = self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users" "/dd2239f6-ddf1-4107-981a-4cf94e415794" m.delete(url, status_code=204) # We register the get endpoint twice. The first time we have 1 user, the second we have 'removed' them. - m.get(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users', text=response_xml_populate) + m.get(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_populate) - single_group = TSC.GroupItem('test') - single_group._id = 'e7833b48-c6f7-47b5-a2a7-36e7dd232758' + single_group = TSC.GroupItem("test") + single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758" self.server.groups.populate_users(single_group) self.assertEqual(1, len(list(single_group.users))) - self.server.groups.remove_user(single_group, 'dd2239f6-ddf1-4107-981a-4cf94e415794') + self.server.groups.remove_user(single_group, "dd2239f6-ddf1-4107-981a-4cf94e415794") - m.get(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users', text=response_xml_empty) + m.get(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_empty) self.assertEqual(0, len(list(single_group.users))) def test_add_user(self): - with open(ADD_USER, 'rb') as f: - response_xml_add = f.read().decode('utf-8') - with open(ADD_USER_POPULATE, 'rb') as f: - response_xml_populate = f.read().decode('utf-8') + with open(ADD_USER, "rb") as f: + response_xml_add = f.read().decode("utf-8") + with open(ADD_USER_POPULATE, "rb") as f: + response_xml_populate = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users', text=response_xml_add) - m.get(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users', text=response_xml_populate) - single_group = TSC.GroupItem('test') - single_group._id = 'e7833b48-c6f7-47b5-a2a7-36e7dd232758' + m.post(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_add) + m.get(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_populate) + single_group = TSC.GroupItem("test") + single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758" - self.server.groups.add_user(single_group, '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7') + self.server.groups.add_user(single_group, "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7") self.server.groups.populate_users(single_group) self.assertEqual(1, len(list(single_group.users))) user = list(single_group.users).pop() - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', user.id) - self.assertEqual('testuser', user.name) - self.assertEqual('ServerAdministrator', user.site_role) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", user.id) + self.assertEqual("testuser", user.name) + self.assertEqual("ServerAdministrator", user.site_role) def test_add_user_before_populating(self): - with open(GET_XML, 'rb') as f: - get_xml_response = f.read().decode('utf-8') - with open(ADD_USER, 'rb') as f: - add_user_response = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + get_xml_response = f.read().decode("utf-8") + with open(ADD_USER, "rb") as f: + add_user_response = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=get_xml_response) - m.post('http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/groups/ef8b19c0-43b6-11e6-af50' - '-63f5805dbe3c/users', text=add_user_response) + m.post( + "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/groups/ef8b19c0-43b6-11e6-af50" + "-63f5805dbe3c/users", + text=add_user_response, + ) all_groups, pagination_item = self.server.groups.get() single_group = all_groups[0] - self.server.groups.add_user(single_group, '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7') + self.server.groups.add_user(single_group, "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7") def test_add_user_missing_user_id(self): - with open(POPULATE_USERS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_USERS, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users', text=response_xml) - single_group = TSC.GroupItem(name='Test Group') - single_group._id = 'e7833b48-c6f7-47b5-a2a7-36e7dd232758' + m.get(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml) + single_group = TSC.GroupItem(name="Test Group") + single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758" self.server.groups.populate_users(single_group) - self.assertRaises(ValueError, self.server.groups.add_user, single_group, '') + self.assertRaises(ValueError, self.server.groups.add_user, single_group, "") def test_add_user_missing_group_id(self): - single_group = TSC.GroupItem('test') + single_group = TSC.GroupItem("test") single_group._users = [] - self.assertRaises(TSC.MissingRequiredFieldError, self.server.groups.add_user, single_group, - '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7') + self.assertRaises( + TSC.MissingRequiredFieldError, + self.server.groups.add_user, + single_group, + "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", + ) def test_remove_user_before_populating(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) - m.delete('http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/groups/ef8b19c0-43b6-11e6-af50' - '-63f5805dbe3c/users/5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', - text='ok') + m.delete( + "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/groups/ef8b19c0-43b6-11e6-af50" + "-63f5805dbe3c/users/5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", + text="ok", + ) all_groups, pagination_item = self.server.groups.get() single_group = all_groups[0] - self.server.groups.remove_user(single_group, '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7') + self.server.groups.remove_user(single_group, "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7") def test_remove_user_missing_user_id(self): - with open(POPULATE_USERS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_USERS, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users', text=response_xml) - single_group = TSC.GroupItem(name='Test Group') - single_group._id = 'e7833b48-c6f7-47b5-a2a7-36e7dd232758' + m.get(self.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml) + single_group = TSC.GroupItem(name="Test Group") + single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758" self.server.groups.populate_users(single_group) - self.assertRaises(ValueError, self.server.groups.remove_user, single_group, '') + self.assertRaises(ValueError, self.server.groups.remove_user, single_group, "") def test_remove_user_missing_group_id(self): - single_group = TSC.GroupItem('test') + single_group = TSC.GroupItem("test") single_group._users = [] - self.assertRaises(TSC.MissingRequiredFieldError, self.server.groups.remove_user, single_group, - '5de011f8-5aa9-4d5b-b991-f462c8dd6bb7') + self.assertRaises( + TSC.MissingRequiredFieldError, + self.server.groups.remove_user, + single_group, + "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", + ) def test_create_group(self): - with open(CREATE_GROUP, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_GROUP, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - group_to_create = TSC.GroupItem(u'試供品') + group_to_create = TSC.GroupItem(u"試供品") group = self.server.groups.create(group_to_create) - self.assertEqual(group.name, u'試供品') - self.assertEqual(group.id, '3e4a9ea0-a07a-4fe6-b50f-c345c8c81034') + self.assertEqual(group.name, u"試供品") + self.assertEqual(group.id, "3e4a9ea0-a07a-4fe6-b50f-c345c8c81034") def test_create_ad_group(self): - with open(CREATE_GROUP_AD, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_GROUP_AD, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - group_to_create = TSC.GroupItem(u'試供品') - group_to_create.domain_name = 'just-has-to-exist' + group_to_create = TSC.GroupItem(u"試供品") + group_to_create.domain_name = "just-has-to-exist" group = self.server.groups.create_AD_group(group_to_create, False) - self.assertEqual(group.name, u'試供品') - self.assertEqual(group.license_mode, 'onLogin') - self.assertEqual(group.minimum_site_role, 'Creator') - self.assertEqual(group.domain_name, 'active-directory-domain-name') + self.assertEqual(group.name, u"試供品") + self.assertEqual(group.license_mode, "onLogin") + self.assertEqual(group.minimum_site_role, "Creator") + self.assertEqual(group.domain_name, "active-directory-domain-name") def test_create_group_async(self): - with open(CREATE_GROUP_ASYNC, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_GROUP_ASYNC, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - group_to_create = TSC.GroupItem(u'試供品') - group_to_create.domain_name = 'woohoo' + group_to_create = TSC.GroupItem(u"試供品") + group_to_create.domain_name = "woohoo" job = self.server.groups.create_AD_group(group_to_create, True) - self.assertEqual(job.mode, 'Asynchronous') - self.assertEqual(job.type, 'GroupImport') + self.assertEqual(job.mode, "Asynchronous") + self.assertEqual(job.type, "GroupImport") def test_update(self): - with open(UPDATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(UPDATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.put(self.baseurl + '/ef8b19c0-43b6-11e6-af50-63f5805dbe3c', text=response_xml) - group = TSC.GroupItem(name='Test Group') - group._domain_name = 'local' - group._id = 'ef8b19c0-43b6-11e6-af50-63f5805dbe3c' + m.put(self.baseurl + "/ef8b19c0-43b6-11e6-af50-63f5805dbe3c", text=response_xml) + group = TSC.GroupItem(name="Test Group") + group._domain_name = "local" + group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c" group = self.server.groups.update(group) - self.assertEqual('ef8b19c0-43b6-11e6-af50-63f5805dbe3c', group.id) - self.assertEqual('Group updated name', group.name) - self.assertEqual('ExplorerCanPublish', group.minimum_site_role) - self.assertEqual('onLogin', group.license_mode) + self.assertEqual("ef8b19c0-43b6-11e6-af50-63f5805dbe3c", group.id) + self.assertEqual("Group updated name", group.name) + self.assertEqual("ExplorerCanPublish", group.minimum_site_role) + self.assertEqual("onLogin", group.license_mode) # async update is not supported for local groups def test_update_local_async(self): group = TSC.GroupItem("myGroup") - group._id = 'ef8b19c0-43b6-11e6-af50-63f5805dbe3c' + group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c" self.assertRaises(ValueError, self.server.groups.update, group, as_job=True) # mimic group returned from server where domain name is set to 'local' diff --git a/test/test_job.py b/test/test_job.py index 70bca996c..71c48d18d 100644 --- a/test/test_job.py +++ b/test/test_job.py @@ -7,27 +7,27 @@ from tableauserverclient.server.endpoint.exceptions import JobFailedException from ._utils import read_xml_asset, mocked_time -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -GET_XML = 'job_get.xml' -GET_BY_ID_XML = 'job_get_by_id.xml' -GET_BY_ID_FAILED_XML = 'job_get_by_id_failed.xml' -GET_BY_ID_CANCELLED_XML = 'job_get_by_id_cancelled.xml' -GET_BY_ID_INPROGRESS_XML = 'job_get_by_id_inprogress.xml' +GET_XML = "job_get.xml" +GET_BY_ID_XML = "job_get_by_id.xml" +GET_BY_ID_FAILED_XML = "job_get_by_id_failed.xml" +GET_BY_ID_CANCELLED_XML = "job_get_by_id_cancelled.xml" +GET_BY_ID_INPROGRESS_XML = "job_get_by_id_inprogress.xml" class JobTests(unittest.TestCase): - def setUp(self): - self.server = TSC.Server('http://test') - self.server.version = '3.1' + def setUp(self) -> None: + self.server = TSC.Server("http://test") + self.server.version = "3.1" # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.jobs.baseurl - def test_get(self): + def test_get(self) -> None: response_xml = read_xml_asset(GET_XML) with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) @@ -38,70 +38,66 @@ def test_get(self): ended_at = datetime(2018, 5, 22, 13, 0, 45, tzinfo=utc) self.assertEqual(1, pagination_item.total_available) - self.assertEqual('2eef4225-aa0c-41c4-8662-a76d89ed7336', job.id) - self.assertEqual('Success', job.status) - self.assertEqual('50', job.priority) - self.assertEqual('single_subscription_notify', job.type) + self.assertEqual("2eef4225-aa0c-41c4-8662-a76d89ed7336", job.id) + self.assertEqual("Success", job.status) + self.assertEqual("50", job.priority) + self.assertEqual("single_subscription_notify", job.type) self.assertEqual(created_at, job.created_at) self.assertEqual(started_at, job.started_at) self.assertEqual(ended_at, job.ended_at) - def test_get_by_id(self): + def test_get_by_id(self) -> None: response_xml = read_xml_asset(GET_BY_ID_XML) - job_id = '2eef4225-aa0c-41c4-8662-a76d89ed7336' + job_id = "2eef4225-aa0c-41c4-8662-a76d89ed7336" with requests_mock.mock() as m: - m.get('{0}/{1}'.format(self.baseurl, job_id), text=response_xml) + m.get("{0}/{1}".format(self.baseurl, job_id), text=response_xml) job = self.server.jobs.get_by_id(job_id) self.assertEqual(job_id, job.id) - self.assertListEqual(job.notes, ['Job detail notes']) + self.assertListEqual(job.notes, ["Job detail notes"]) - def test_get_before_signin(self): + def test_get_before_signin(self) -> None: self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.jobs.get) - def test_cancel_id(self): + def test_cancel_id(self) -> None: with requests_mock.mock() as m: - m.put(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) - self.server.jobs.cancel('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + m.put(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204) + self.server.jobs.cancel("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") - def test_cancel_item(self): + def test_cancel_item(self) -> None: created_at = datetime(2018, 5, 22, 13, 0, 29, tzinfo=utc) started_at = datetime(2018, 5, 22, 13, 0, 37, tzinfo=utc) - job = TSC.JobItem('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', 'backgroundJob', - 0, created_at, started_at, None, 0) + job = TSC.JobItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "backgroundJob", "0", created_at, started_at, None, 0) with requests_mock.mock() as m: - m.put(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) + m.put(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204) self.server.jobs.cancel(job) - - def test_wait_for_job_finished(self): + def test_wait_for_job_finished(self) -> None: # Waiting for an already finished job, directly returns that job's info response_xml = read_xml_asset(GET_BY_ID_XML) - job_id = '2eef4225-aa0c-41c4-8662-a76d89ed7336' + job_id = "2eef4225-aa0c-41c4-8662-a76d89ed7336" with mocked_time(), requests_mock.mock() as m: - m.get('{0}/{1}'.format(self.baseurl, job_id), text=response_xml) + m.get("{0}/{1}".format(self.baseurl, job_id), text=response_xml) job = self.server.jobs.wait_for_job(job_id) self.assertEqual(job_id, job.id) - self.assertListEqual(job.notes, ['Job detail notes']) - + self.assertListEqual(job.notes, ["Job detail notes"]) - def test_wait_for_job_failed(self): + def test_wait_for_job_failed(self) -> None: # Waiting for a failed job raises an exception response_xml = read_xml_asset(GET_BY_ID_FAILED_XML) - job_id = '77d5e57a-2517-479f-9a3c-a32025f2b64d' + job_id = "77d5e57a-2517-479f-9a3c-a32025f2b64d" with mocked_time(), requests_mock.mock() as m: - m.get('{0}/{1}'.format(self.baseurl, job_id), text=response_xml) + m.get("{0}/{1}".format(self.baseurl, job_id), text=response_xml) with self.assertRaises(JobFailedException): self.server.jobs.wait_for_job(job_id) - - def test_wait_for_job_timeout(self): + def test_wait_for_job_timeout(self) -> None: # Waiting for a job which doesn't terminate will throw an exception response_xml = read_xml_asset(GET_BY_ID_INPROGRESS_XML) - job_id = '77d5e57a-2517-479f-9a3c-a32025f2b64d' + job_id = "77d5e57a-2517-479f-9a3c-a32025f2b64d" with mocked_time(), requests_mock.mock() as m: - m.get('{0}/{1}'.format(self.baseurl, job_id), text=response_xml) + m.get("{0}/{1}".format(self.baseurl, job_id), text=response_xml) with self.assertRaises(TimeoutError): self.server.jobs.wait_for_job(job_id, timeout=30) diff --git a/test/test_metadata.py b/test/test_metadata.py index 1c0846d73..3510128c8 100644 --- a/test/test_metadata.py +++ b/test/test_metadata.py @@ -6,93 +6,96 @@ from tableauserverclient.server.endpoint.exceptions import GraphQLError -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -METADATA_QUERY_SUCCESS = os.path.join(TEST_ASSET_DIR, 'metadata_query_success.json') -METADATA_QUERY_ERROR = os.path.join(TEST_ASSET_DIR, 'metadata_query_error.json') -EXPECTED_PAGED_DICT = os.path.join(TEST_ASSET_DIR, 'metadata_query_expected_dict.dict') +METADATA_QUERY_SUCCESS = os.path.join(TEST_ASSET_DIR, "metadata_query_success.json") +METADATA_QUERY_ERROR = os.path.join(TEST_ASSET_DIR, "metadata_query_error.json") +EXPECTED_PAGED_DICT = os.path.join(TEST_ASSET_DIR, "metadata_query_expected_dict.dict") -METADATA_PAGE_1 = os.path.join(TEST_ASSET_DIR, 'metadata_paged_1.json') -METADATA_PAGE_2 = os.path.join(TEST_ASSET_DIR, 'metadata_paged_2.json') -METADATA_PAGE_3 = os.path.join(TEST_ASSET_DIR, 'metadata_paged_3.json') +METADATA_PAGE_1 = os.path.join(TEST_ASSET_DIR, "metadata_paged_1.json") +METADATA_PAGE_2 = os.path.join(TEST_ASSET_DIR, "metadata_paged_2.json") +METADATA_PAGE_3 = os.path.join(TEST_ASSET_DIR, "metadata_paged_3.json") -EXPECTED_DICT = {'publishedDatasources': - [{'id': '01cf92b2-2d17-b656-fc48-5c25ef6d5352', 'name': 'Batters (TestV1)'}, - {'id': '020ae1cd-c356-f1ad-a846-b0094850d22a', 'name': 'SharePoint_List_sharepoint2010.test.tsi.lan'}, - {'id': '061493a0-c3b2-6f39-d08c-bc3f842b44af', 'name': 'Batters_mongodb'}, - {'id': '089fe515-ad2f-89bc-94bd-69f55f69a9c2', 'name': 'Sample - Superstore'}]} +EXPECTED_DICT = { + "publishedDatasources": [ + {"id": "01cf92b2-2d17-b656-fc48-5c25ef6d5352", "name": "Batters (TestV1)"}, + {"id": "020ae1cd-c356-f1ad-a846-b0094850d22a", "name": "SharePoint_List_sharepoint2010.test.tsi.lan"}, + {"id": "061493a0-c3b2-6f39-d08c-bc3f842b44af", "name": "Batters_mongodb"}, + {"id": "089fe515-ad2f-89bc-94bd-69f55f69a9c2", "name": "Sample - Superstore"}, + ] +} -EXPECTED_DICT_ERROR = [ - { - "message": "Reached time limit of PT5S for query execution.", - "path": None, - "extensions": None - } -] +EXPECTED_DICT_ERROR = [{"message": "Reached time limit of PT5S for query execution.", "path": None, "extensions": None}] class MetadataTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") self.baseurl = self.server.metadata.baseurl self.server.version = "3.5" - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" def test_metadata_query(self): - with open(METADATA_QUERY_SUCCESS, 'rb') as f: + with open(METADATA_QUERY_SUCCESS, "rb") as f: response_json = json.loads(f.read().decode()) with requests_mock.mock() as m: m.post(self.baseurl, json=response_json) - actual = self.server.metadata.query('fake query') + actual = self.server.metadata.query("fake query") - datasources = actual['data'] + datasources = actual["data"] self.assertDictEqual(EXPECTED_DICT, datasources) def test_paged_metadata_query(self): - with open(EXPECTED_PAGED_DICT, 'rb') as f: + with open(EXPECTED_PAGED_DICT, "rb") as f: expected = eval(f.read()) # prepare the 3 pages of results - with open(METADATA_PAGE_1, 'rb') as f: + with open(METADATA_PAGE_1, "rb") as f: result_1 = f.read().decode() - with open(METADATA_PAGE_2, 'rb') as f: + with open(METADATA_PAGE_2, "rb") as f: result_2 = f.read().decode() - with open(METADATA_PAGE_3, 'rb') as f: + with open(METADATA_PAGE_3, "rb") as f: result_3 = f.read().decode() with requests_mock.mock() as m: - m.post(self.baseurl, [{'text': result_1, 'status_code': 200}, - {'text': result_2, 'status_code': 200}, - {'text': result_3, 'status_code': 200}]) + m.post( + self.baseurl, + [ + {"text": result_1, "status_code": 200}, + {"text": result_2, "status_code": 200}, + {"text": result_3, "status_code": 200}, + ], + ) # validation checks for endCursor and hasNextPage, # but the query text doesn't matter for the test - actual = self.server.metadata.paginated_query('fake query endCursor hasNextPage', - variables={'first': 1, 'afterToken': None}) + actual = self.server.metadata.paginated_query( + "fake query endCursor hasNextPage", variables={"first": 1, "afterToken": None} + ) self.assertDictEqual(expected, actual) def test_metadata_query_ignore_error(self): - with open(METADATA_QUERY_ERROR, 'rb') as f: + with open(METADATA_QUERY_ERROR, "rb") as f: response_json = json.loads(f.read().decode()) with requests_mock.mock() as m: m.post(self.baseurl, json=response_json) - actual = self.server.metadata.query('fake query') - datasources = actual['data'] + actual = self.server.metadata.query("fake query") + datasources = actual["data"] - self.assertNotEqual(actual.get('errors', None), None) - self.assertListEqual(EXPECTED_DICT_ERROR, actual['errors']) + self.assertNotEqual(actual.get("errors", None), None) + self.assertListEqual(EXPECTED_DICT_ERROR, actual["errors"]) self.assertDictEqual(EXPECTED_DICT, datasources) def test_metadata_query_abort_on_error(self): - with open(METADATA_QUERY_ERROR, 'rb') as f: + with open(METADATA_QUERY_ERROR, "rb") as f: response_json = json.loads(f.read().decode()) with requests_mock.mock() as m: m.post(self.baseurl, json=response_json) with self.assertRaises(GraphQLError) as e: - self.server.metadata.query('fake query', abort_on_error=True) + self.server.metadata.query("fake query", abort_on_error=True) self.assertListEqual(e.error, EXPECTED_DICT_ERROR) diff --git a/test/test_pager.py b/test/test_pager.py index 52089180d..21b49bcf0 100644 --- a/test/test_pager.py +++ b/test/test_pager.py @@ -3,30 +3,30 @@ import requests_mock import tableauserverclient as TSC -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -GET_XML_PAGE1 = os.path.join(TEST_ASSET_DIR, 'workbook_get_page_1.xml') -GET_XML_PAGE2 = os.path.join(TEST_ASSET_DIR, 'workbook_get_page_2.xml') -GET_XML_PAGE3 = os.path.join(TEST_ASSET_DIR, 'workbook_get_page_3.xml') +GET_XML_PAGE1 = os.path.join(TEST_ASSET_DIR, "workbook_get_page_1.xml") +GET_XML_PAGE2 = os.path.join(TEST_ASSET_DIR, "workbook_get_page_2.xml") +GET_XML_PAGE3 = os.path.join(TEST_ASSET_DIR, "workbook_get_page_3.xml") class PagerTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake sign in - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.workbooks.baseurl def test_pager_with_no_options(self): - with open(GET_XML_PAGE1, 'rb') as f: - page_1 = f.read().decode('utf-8') - with open(GET_XML_PAGE2, 'rb') as f: - page_2 = f.read().decode('utf-8') - with open(GET_XML_PAGE3, 'rb') as f: - page_3 = f.read().decode('utf-8') + with open(GET_XML_PAGE1, "rb") as f: + page_1 = f.read().decode("utf-8") + with open(GET_XML_PAGE2, "rb") as f: + page_2 = f.read().decode("utf-8") + with open(GET_XML_PAGE3, "rb") as f: + page_3 = f.read().decode("utf-8") with requests_mock.mock() as m: # Register Pager with default request options m.get(self.baseurl, text=page_1) @@ -42,17 +42,17 @@ def test_pager_with_no_options(self): # Let's check that workbook items aren't duplicates wb1, wb2, wb3 = workbooks - self.assertEqual(wb1.name, 'Page1Workbook') - self.assertEqual(wb2.name, 'Page2Workbook') - self.assertEqual(wb3.name, 'Page3Workbook') + self.assertEqual(wb1.name, "Page1Workbook") + self.assertEqual(wb2.name, "Page2Workbook") + self.assertEqual(wb3.name, "Page3Workbook") def test_pager_with_options(self): - with open(GET_XML_PAGE1, 'rb') as f: - page_1 = f.read().decode('utf-8') - with open(GET_XML_PAGE2, 'rb') as f: - page_2 = f.read().decode('utf-8') - with open(GET_XML_PAGE3, 'rb') as f: - page_3 = f.read().decode('utf-8') + with open(GET_XML_PAGE1, "rb") as f: + page_1 = f.read().decode("utf-8") + with open(GET_XML_PAGE2, "rb") as f: + page_2 = f.read().decode("utf-8") + with open(GET_XML_PAGE3, "rb") as f: + page_3 = f.read().decode("utf-8") with requests_mock.mock() as m: # Register Pager with some pages m.get(self.baseurl + "?pageNumber=1&pageSize=1", complete_qs=True, text=page_1) @@ -67,17 +67,17 @@ def test_pager_with_options(self): # Check that the workbooks are the 2 we think they should be wb2, wb3 = workbooks - self.assertEqual(wb2.name, 'Page2Workbook') - self.assertEqual(wb3.name, 'Page3Workbook') + self.assertEqual(wb2.name, "Page2Workbook") + self.assertEqual(wb3.name, "Page3Workbook") # Starting on 1 with pagesize of 3 should get all 3 opts = TSC.RequestOptions(1, 3) workbooks = list(TSC.Pager(self.server.workbooks, opts)) self.assertTrue(len(workbooks) == 3) wb1, wb2, wb3 = workbooks - self.assertEqual(wb1.name, 'Page1Workbook') - self.assertEqual(wb2.name, 'Page2Workbook') - self.assertEqual(wb3.name, 'Page3Workbook') + self.assertEqual(wb1.name, "Page1Workbook") + self.assertEqual(wb2.name, "Page2Workbook") + self.assertEqual(wb3.name, "Page3Workbook") # Starting on 3 with pagesize of 1 should get the last item opts = TSC.RequestOptions(3, 1) @@ -85,4 +85,4 @@ def test_pager_with_options(self): self.assertTrue(len(workbooks) == 1) # Should have the last workbook wb3 = workbooks.pop() - self.assertEqual(wb3.name, 'Page3Workbook') + self.assertEqual(wb3.name, "Page3Workbook") diff --git a/test/test_project.py b/test/test_project.py index be43b063e..c8812e399 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -5,54 +5,53 @@ from ._utils import read_xml_asset, read_xml_assets, asset -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -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' -UPDATE_DATASOURCE_DEFAULT_PERMISSIONS_XML = 'project_update_datasource_default_permissions.xml' +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" +UPDATE_DATASOURCE_DEFAULT_PERMISSIONS_XML = "project_update_datasource_default_permissions.xml" class ProjectTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.projects.baseurl def test_get(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_projects, pagination_item = self.server.projects.get() self.assertEqual(3, pagination_item.total_available) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', all_projects[0].id) - self.assertEqual('default', all_projects[0].name) - self.assertEqual('The default project that was automatically created by Tableau.', - all_projects[0].description) - self.assertEqual('ManagedByOwner', all_projects[0].content_permissions) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", all_projects[0].id) + self.assertEqual("default", all_projects[0].name) + self.assertEqual("The default project that was automatically created by Tableau.", all_projects[0].description) + self.assertEqual("ManagedByOwner", all_projects[0].content_permissions) self.assertEqual(None, all_projects[0].parent_id) - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', all_projects[0].owner_id) + self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", all_projects[0].owner_id) - self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', all_projects[1].id) - self.assertEqual('Tableau', all_projects[1].name) - self.assertEqual('ManagedByOwner', all_projects[1].content_permissions) + self.assertEqual("1d0304cd-3796-429f-b815-7258370b9b74", all_projects[1].id) + self.assertEqual("Tableau", all_projects[1].name) + self.assertEqual("ManagedByOwner", all_projects[1].content_permissions) self.assertEqual(None, all_projects[1].parent_id) - self.assertEqual('2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3', all_projects[1].owner_id) + self.assertEqual("2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3", all_projects[1].owner_id) - self.assertEqual('4cc52973-5e3a-4d1f-a4fb-5b5f73796edf', all_projects[2].id) - self.assertEqual('Tableau > Child 1', all_projects[2].name) - self.assertEqual('ManagedByOwner', all_projects[2].content_permissions) - self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', all_projects[2].parent_id) - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', all_projects[2].owner_id) + self.assertEqual("4cc52973-5e3a-4d1f-a4fb-5b5f73796edf", all_projects[2].id) + self.assertEqual("Tableau > Child 1", all_projects[2].name) + self.assertEqual("ManagedByOwner", all_projects[2].content_permissions) + self.assertEqual("1d0304cd-3796-429f-b815-7258370b9b74", all_projects[2].parent_id) + self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", all_projects[2].owner_id) def test_get_before_signin(self): self.server._auth_token = None @@ -60,161 +59,171 @@ def test_get_before_signin(self): def test_delete(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) - self.server.projects.delete('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + m.delete(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204) + self.server.projects.delete("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") def test_delete_missing_id(self): - self.assertRaises(ValueError, self.server.projects.delete, '') + self.assertRaises(ValueError, self.server.projects.delete, "") def test_update(self): - with open(UPDATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(UPDATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.put(self.baseurl + '/1d0304cd-3796-429f-b815-7258370b9b74', text=response_xml) - single_project = TSC.ProjectItem(name='Test Project', - content_permissions='LockedToProject', - description='Project created for testing', - parent_id='9a8f2265-70f3-4494-96c5-e5949d7a1120') - single_project._id = '1d0304cd-3796-429f-b815-7258370b9b74' + m.put(self.baseurl + "/1d0304cd-3796-429f-b815-7258370b9b74", text=response_xml) + single_project = TSC.ProjectItem( + name="Test Project", + content_permissions="LockedToProject", + description="Project created for testing", + parent_id="9a8f2265-70f3-4494-96c5-e5949d7a1120", + ) + single_project._id = "1d0304cd-3796-429f-b815-7258370b9b74" single_project = self.server.projects.update(single_project) - self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', single_project.id) - self.assertEqual('Test Project', single_project.name) - self.assertEqual('Project created for testing', single_project.description) - self.assertEqual('LockedToProject', single_project.content_permissions) - self.assertEqual('9a8f2265-70f3-4494-96c5-e5949d7a1120', single_project.parent_id) + self.assertEqual("1d0304cd-3796-429f-b815-7258370b9b74", single_project.id) + self.assertEqual("Test Project", single_project.name) + self.assertEqual("Project created for testing", single_project.description) + 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 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' + 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) + 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: - m.put(self.baseurl + '/b4065286-80f0-11ea-af1b-cb7191f48e45/default-permissions/datasources', - text=response_xml) - project = TSC.ProjectItem('test-project') - project._id = 'b4065286-80f0-11ea-af1b-cb7191f48e45' + m.put( + self.baseurl + "/b4065286-80f0-11ea-af1b-cb7191f48e45/default-permissions/datasources", + text=response_xml, + ) + project = TSC.ProjectItem("test-project") + project._id = "b4065286-80f0-11ea-af1b-cb7191f48e45" - group = TSC.GroupItem('test-group') - group._id = 'b4488bce-80f0-11ea-af1c-976d0c1dab39' + group = TSC.GroupItem("test-group") + group._id = "b4488bce-80f0-11ea-af1c-976d0c1dab39" capabilities = {TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny} - rules = [TSC.PermissionsRule( - grantee=group, - capabilities=capabilities - )] + rules = [TSC.PermissionsRule(grantee=group, capabilities=capabilities)] new_rules = self.server.projects.update_datasource_default_permissions(project, rules) - self.assertEqual('b4488bce-80f0-11ea-af1c-976d0c1dab39', new_rules[0].grantee.id) + self.assertEqual("b4488bce-80f0-11ea-af1c-976d0c1dab39", new_rules[0].grantee.id) updated_capabilities = new_rules[0].capabilities self.assertEqual(4, len(updated_capabilities)) - self.assertEqual('Deny', updated_capabilities['ExportXml']) - self.assertEqual('Allow', updated_capabilities['Read']) - self.assertEqual('Allow', updated_capabilities['Write']) - self.assertEqual('Allow', updated_capabilities['Connect']) + self.assertEqual("Deny", updated_capabilities["ExportXml"]) + self.assertEqual("Allow", updated_capabilities["Read"]) + self.assertEqual("Allow", updated_capabilities["Write"]) + self.assertEqual("Allow", updated_capabilities["Connect"]) def test_update_missing_id(self): - single_project = TSC.ProjectItem('test') + single_project = TSC.ProjectItem("test") self.assertRaises(TSC.MissingRequiredFieldError, self.server.projects.update, single_project) def test_create(self): - with open(CREATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_project = TSC.ProjectItem(name='Test Project', description='Project created for testing') - new_project.content_permissions = 'ManagedByOwner' - new_project.parent_id = '9a8f2265-70f3-4494-96c5-e5949d7a1120' + new_project = TSC.ProjectItem(name="Test Project", description="Project created for testing") + new_project.content_permissions = "ManagedByOwner" + new_project.parent_id = "9a8f2265-70f3-4494-96c5-e5949d7a1120" new_project = self.server.projects.create(new_project) - self.assertEqual('ccbea03f-77c4-4209-8774-f67bc59c3cef', new_project.id) - self.assertEqual('Test Project', new_project.name) - self.assertEqual('Project created for testing', new_project.description) - self.assertEqual('ManagedByOwner', new_project.content_permissions) - self.assertEqual('9a8f2265-70f3-4494-96c5-e5949d7a1120', new_project.parent_id) + self.assertEqual("ccbea03f-77c4-4209-8774-f67bc59c3cef", new_project.id) + self.assertEqual("Test Project", new_project.name) + self.assertEqual("Project created for testing", new_project.description) + self.assertEqual("ManagedByOwner", new_project.content_permissions) + self.assertEqual("9a8f2265-70f3-4494-96c5-e5949d7a1120", new_project.parent_id) def test_create_missing_name(self): - self.assertRaises(ValueError, TSC.ProjectItem, '') + self.assertRaises(ValueError, TSC.ProjectItem, "") def test_populate_permissions(self): - with open(asset(POPULATE_PERMISSIONS_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: + 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_project = TSC.ProjectItem('Project3') - single_project._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' + m.get(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml) + single_project = TSC.ProjectItem("Project3") + single_project._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5" self.server.projects.populate_permissions(single_project) permissions = single_project.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, 'c8f2773a-c83a-11e8-8c8f-33e6d787b506') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "c8f2773a-c83a-11e8-8c8f-33e6d787b506") + self.assertDictEqual( + permissions[0].capabilities, + { + TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + }, + ) def test_populate_workbooks(self): response_xml = read_xml_asset(POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML) with requests_mock.mock() as m: - m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/workbooks', - text=response_xml) - single_project = TSC.ProjectItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') - single_project._owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' - single_project._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + m.get( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/workbooks", text=response_xml + ) + single_project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74") + single_project._owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794" + single_project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" self.server.projects.populate_workbook_default_permissions(single_project) permissions = single_project.default_workbook_permissions rule1 = permissions.pop() - self.assertEqual('c8f2773a-c83a-11e8-8c8f-33e6d787b506', rule1.grantee.id) - self.assertEqual('group', rule1.grantee.tag_name) - self.assertDictEqual(rule1.capabilities, { - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ViewUnderlyingData: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow, - }) + self.assertEqual("c8f2773a-c83a-11e8-8c8f-33e6d787b506", rule1.grantee.id) + self.assertEqual("group", rule1.grantee.tag_name) + self.assertDictEqual( + rule1.capabilities, + { + TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny, + TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ViewUnderlyingData: TSC.Permission.Mode.Deny, + TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow, + }, + ) def test_delete_permission(self): - with open(asset(POPULATE_PERMISSIONS_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(asset(POPULATE_PERMISSIONS_XML), "rb") as f: + 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) + m.get(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml) - single_group = TSC.GroupItem('Group1') - single_group._id = 'c8f2773a-c83a-11e8-8c8f-33e6d787b506' + single_group = TSC.GroupItem("Group1") + single_group._id = "c8f2773a-c83a-11e8-8c8f-33e6d787b506" - single_project = TSC.ProjectItem('Project3') - single_project._id = '0448d2ed-590d-4fa0-b272-a2a8a24555b5' + single_project = TSC.ProjectItem("Project3") + single_project._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5" self.server.projects.populate_permissions(single_project) permissions = single_project.permissions @@ -226,30 +235,28 @@ def test_delete_permission(self): if permission.grantee.id == single_group._id: capabilities = permission.capabilities - rules = TSC.PermissionsRule( - grantee=single_group, - capabilities=capabilities - ) + rules = TSC.PermissionsRule(grantee=single_group, capabilities=capabilities) - endpoint = '{}/permissions/groups/{}'.format(single_project._id, single_group._id) - m.delete('{}/{}/Read/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/Write/Allow'.format(self.baseurl, endpoint), status_code=204) + endpoint = "{}/permissions/groups/{}".format(single_project._id, single_group._id) + m.delete("{}/{}/Read/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/Write/Allow".format(self.baseurl, endpoint), status_code=204) self.server.projects.delete_permission(item=single_project, rules=rules) def test_delete_workbook_default_permission(self): - with open(asset(POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML), 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(asset(POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML), "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/workbooks', - text=response_xml) + m.get( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/workbooks", text=response_xml + ) - single_group = TSC.GroupItem('Group1') - single_group._id = 'c8f2773a-c83a-11e8-8c8f-33e6d787b506' + single_group = TSC.GroupItem("Group1") + single_group._id = "c8f2773a-c83a-11e8-8c8f-33e6d787b506" - single_project = TSC.ProjectItem('test', '1d0304cd-3796-429f-b815-7258370b9b74') - single_project._owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' - single_project._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' + single_project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74") + single_project._owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794" + single_project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" self.server.projects.populate_workbook_default_permissions(single_project) permissions = single_project.default_workbook_permissions @@ -261,39 +268,34 @@ def test_delete_workbook_default_permission(self): TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow, TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow, TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow, - # Interact/Edit TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow, TSC.Permission.Capability.ViewUnderlyingData: TSC.Permission.Mode.Deny, TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow, TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow, - # Edit TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow, TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Allow, TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow, TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Allow + TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Allow, } - rules = TSC.PermissionsRule( - grantee=single_group, - capabilities=capabilities - ) - - endpoint = '{}/default-permissions/workbooks/groups/{}'.format(single_project._id, single_group._id) - m.delete('{}/{}/Read/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ExportImage/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ExportData/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ViewComments/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/AddComment/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/Filter/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ViewUnderlyingData/Deny'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ShareView/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/WebAuthoring/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/Write/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ExportXml/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ChangeHierarchy/Allow'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/Delete/Deny'.format(self.baseurl, endpoint), status_code=204) - m.delete('{}/{}/ChangePermissions/Allow'.format(self.baseurl, endpoint), status_code=204) + rules = TSC.PermissionsRule(grantee=single_group, capabilities=capabilities) + + endpoint = "{}/default-permissions/workbooks/groups/{}".format(single_project._id, single_group._id) + m.delete("{}/{}/Read/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ExportImage/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ExportData/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ViewComments/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/AddComment/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/Filter/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ViewUnderlyingData/Deny".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ShareView/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/WebAuthoring/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/Write/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ExportXml/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ChangeHierarchy/Allow".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/Delete/Deny".format(self.baseurl, endpoint), status_code=204) + m.delete("{}/{}/ChangePermissions/Allow".format(self.baseurl, endpoint), status_code=204) self.server.projects.delete_workbook_default_permissions(item=single_project, rule=rules) diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py index 52ea03b92..76803c843 100644 --- a/test/test_regression_tests.py +++ b/test/test_regression_tests.py @@ -13,51 +13,50 @@ class BugFix257(unittest.TestCase): def test_empty_request_works(self): result = factory.EmptyRequest().empty_req() - self.assertEqual(b'', result) + self.assertEqual(b"", result) class BugFix273(unittest.TestCase): def test_binary_log_truncated(self): - class FakeResponse(object): - headers = {'Content-Type': 'application/octet-stream'} - content = b'\x1337' * 1000 + headers = {"Content-Type": "application/octet-stream"} + content = b"\x1337" * 1000 status_code = 200 server_response = FakeResponse() - self.assertEqual(Endpoint._safe_to_log(server_response), '[Truncated File Contents]') + self.assertEqual(Endpoint._safe_to_log(server_response), "[Truncated File Contents]") class FileSysHelpers(unittest.TestCase): def test_to_filename(self): invalid = [ "23brhafbjrjhkbbea.txt", - 'a_b_C.txt', - 'windows space.txt', - 'abc#def.txt', - 't@bL3A()', + "a_b_C.txt", + "windows space.txt", + "abc#def.txt", + "t@bL3A()", ] valid = [ "23brhafbjrjhkbbea.txt", - 'a_b_C.txt', - 'windows space.txt', - 'abcdef.txt', - 'tbL3A', + "a_b_C.txt", + "windows space.txt", + "abcdef.txt", + "tbL3A", ] self.assertTrue(all([(to_filename(i) == v) for i, v in zip(invalid, valid)])) def test_make_download_path(self): - no_file_path = (None, 'file.ext') - has_file_path_folder = ('/root/folder/', 'file.ext') - has_file_path_file = ('out', 'file.ext') + no_file_path = (None, "file.ext") + has_file_path_folder = ("/root/folder/", "file.ext") + has_file_path_file = ("out", "file.ext") - self.assertEqual('file.ext', make_download_path(*no_file_path)) - self.assertEqual('out.ext', make_download_path(*has_file_path_file)) + self.assertEqual("file.ext", make_download_path(*no_file_path)) + self.assertEqual("out.ext", make_download_path(*has_file_path_file)) - with mock.patch('os.path.isdir') as mocked_isdir: + with mock.patch("os.path.isdir") as mocked_isdir: mocked_isdir.return_value = True - self.assertEqual('/root/folder/file.ext', make_download_path(*has_file_path_folder)) + self.assertEqual("/root/folder/file.ext", make_download_path(*has_file_path_folder)) diff --git a/test/test_request_option.py b/test/test_request_option.py index 37b4fc945..fba1e0f68 100644 --- a/test/test_request_option.py +++ b/test/test_request_option.py @@ -5,32 +5,32 @@ import requests_mock import tableauserverclient as TSC -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -PAGINATION_XML = os.path.join(TEST_ASSET_DIR, 'request_option_pagination.xml') -PAGE_NUMBER_XML = os.path.join(TEST_ASSET_DIR, 'request_option_page_number.xml') -PAGE_SIZE_XML = os.path.join(TEST_ASSET_DIR, 'request_option_page_size.xml') -FILTER_EQUALS = os.path.join(TEST_ASSET_DIR, 'request_option_filter_equals.xml') -FILTER_TAGS_IN = os.path.join(TEST_ASSET_DIR, 'request_option_filter_tags_in.xml') -FILTER_MULTIPLE = os.path.join(TEST_ASSET_DIR, 'request_option_filter_tags_in.xml') +PAGINATION_XML = os.path.join(TEST_ASSET_DIR, "request_option_pagination.xml") +PAGE_NUMBER_XML = os.path.join(TEST_ASSET_DIR, "request_option_page_number.xml") +PAGE_SIZE_XML = os.path.join(TEST_ASSET_DIR, "request_option_page_size.xml") +FILTER_EQUALS = os.path.join(TEST_ASSET_DIR, "request_option_filter_equals.xml") +FILTER_TAGS_IN = os.path.join(TEST_ASSET_DIR, "request_option_filter_tags_in.xml") +FILTER_MULTIPLE = os.path.join(TEST_ASSET_DIR, "request_option_filter_tags_in.xml") class RequestOptionTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin self.server.version = "3.10" - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" - self.baseurl = '{0}/{1}'.format(self.server.sites.baseurl, self.server._site_id) + self.baseurl = "{0}/{1}".format(self.server.sites.baseurl, self.server._site_id) def test_pagination(self): - with open(PAGINATION_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PAGINATION_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/views?pageNumber=1&pageSize=10', text=response_xml) + m.get(self.baseurl + "/views?pageNumber=1&pageSize=10", text=response_xml) req_option = TSC.RequestOptions().page_size(10) all_views, pagination_item = self.server.views.get(req_option) @@ -40,10 +40,10 @@ def test_pagination(self): self.assertEqual(10, len(all_views)) def test_page_number(self): - with open(PAGE_NUMBER_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PAGE_NUMBER_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/views?pageNumber=3', text=response_xml) + m.get(self.baseurl + "/views?pageNumber=3", text=response_xml) req_option = TSC.RequestOptions().page_number(3) all_views, pagination_item = self.server.views.get(req_option) @@ -53,10 +53,10 @@ def test_page_number(self): self.assertEqual(10, len(all_views)) def test_page_size(self): - with open(PAGE_SIZE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PAGE_SIZE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/views?pageSize=5', text=response_xml) + m.get(self.baseurl + "/views?pageSize=5", text=response_xml) req_option = TSC.RequestOptions().page_size(5) all_views, pagination_item = self.server.views.get(req_option) @@ -66,74 +66,86 @@ def test_page_size(self): self.assertEqual(5, len(all_views)) def test_filter_equals(self): - with open(FILTER_EQUALS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(FILTER_EQUALS, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/workbooks?filter=name:eq:RESTAPISample', text=response_xml) + m.get(self.baseurl + "/workbooks?filter=name:eq:RESTAPISample", text=response_xml) req_option = TSC.RequestOptions() - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, 'RESTAPISample')) + req_option.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "RESTAPISample") + ) matching_workbooks, pagination_item = self.server.workbooks.get(req_option) self.assertEqual(2, pagination_item.total_available) - self.assertEqual('RESTAPISample', matching_workbooks[0].name) - self.assertEqual('RESTAPISample', matching_workbooks[1].name) + self.assertEqual("RESTAPISample", matching_workbooks[0].name) + self.assertEqual("RESTAPISample", matching_workbooks[1].name) def test_filter_equals_shorthand(self): - with open(FILTER_EQUALS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(FILTER_EQUALS, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/workbooks?filter=name:eq:RESTAPISample', text=response_xml) - matching_workbooks = self.server.workbooks.filter(name='RESTAPISample').order_by("name") + m.get(self.baseurl + "/workbooks?filter=name:eq:RESTAPISample", text=response_xml) + matching_workbooks = self.server.workbooks.filter(name="RESTAPISample").order_by("name") self.assertEqual(2, matching_workbooks.total_available) - self.assertEqual('RESTAPISample', matching_workbooks[0].name) - self.assertEqual('RESTAPISample', matching_workbooks[1].name) + self.assertEqual("RESTAPISample", matching_workbooks[0].name) + self.assertEqual("RESTAPISample", matching_workbooks[1].name) def test_filter_tags_in(self): - with open(FILTER_TAGS_IN, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(FILTER_TAGS_IN, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/workbooks?filter=tags:in:[sample,safari,weather]', text=response_xml) + m.get(self.baseurl + "/workbooks?filter=tags:in:[sample,safari,weather]", text=response_xml) req_option = TSC.RequestOptions() - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, - ['sample', 'safari', 'weather'])) + req_option.filter.add( + TSC.Filter( + TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["sample", "safari", "weather"] + ) + ) matching_workbooks, pagination_item = self.server.workbooks.get(req_option) self.assertEqual(3, pagination_item.total_available) - self.assertEqual(set(['weather']), matching_workbooks[0].tags) - self.assertEqual(set(['safari']), matching_workbooks[1].tags) - self.assertEqual(set(['sample']), matching_workbooks[2].tags) + self.assertEqual(set(["weather"]), matching_workbooks[0].tags) + self.assertEqual(set(["safari"]), matching_workbooks[1].tags) + self.assertEqual(set(["sample"]), matching_workbooks[2].tags) def test_filter_tags_in_shorthand(self): - with open(FILTER_TAGS_IN, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(FILTER_TAGS_IN, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/workbooks?filter=tags:in:[sample,safari,weather]', text=response_xml) - matching_workbooks = self.server.workbooks.filter(tags__in=['sample', 'safari', 'weather']) + m.get(self.baseurl + "/workbooks?filter=tags:in:[sample,safari,weather]", text=response_xml) + matching_workbooks = self.server.workbooks.filter(tags__in=["sample", "safari", "weather"]) self.assertEqual(3, matching_workbooks.total_available) - self.assertEqual(set(['weather']), matching_workbooks[0].tags) - self.assertEqual(set(['safari']), matching_workbooks[1].tags) - self.assertEqual(set(['sample']), matching_workbooks[2].tags) + self.assertEqual(set(["weather"]), matching_workbooks[0].tags) + self.assertEqual(set(["safari"]), matching_workbooks[1].tags) + self.assertEqual(set(["sample"]), matching_workbooks[2].tags) def test_invalid_shorthand_option(self): with self.assertRaises(ValueError): - self.server.workbooks.filter(nonexistant__in=['sample', 'safari']) + self.server.workbooks.filter(nonexistant__in=["sample", "safari"]) def test_multiple_filter_options(self): - with open(FILTER_MULTIPLE, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(FILTER_MULTIPLE, "rb") as f: + response_xml = f.read().decode("utf-8") # To ensure that this is deterministic, run this a few times with requests_mock.mock() as m: # Sometimes pep8 requires you to do things you might not otherwise do - url = ''.join((self.baseurl, '/workbooks?pageNumber=1&pageSize=100&', - 'filter=name:eq:foo,tags:in:[sample,safari,weather]')) + url = "".join( + ( + self.baseurl, + "/workbooks?pageNumber=1&pageSize=100&", + "filter=name:eq:foo,tags:in:[sample,safari,weather]", + ) + ) m.get(url, text=response_xml) req_option = TSC.RequestOptions() - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, - ['sample', 'safari', 'weather'])) - req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, 'foo')) + req_option.filter.add( + TSC.Filter( + TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["sample", "safari", "weather"] + ) + ) + req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "foo")) for _ in range(100): matching_workbooks, pagination_item = self.server.workbooks.get(req_option) self.assertEqual(3, pagination_item.total_available) @@ -145,16 +157,15 @@ def test_double_query_params(self): url = self.baseurl + "/views?queryParamExists=true" opts = TSC.RequestOptions() - opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, - TSC.RequestOptions.Operator.In, - ['stocks', 'market'])) - opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Direction.Asc)) + opts.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["stocks", "market"]) + ) + opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Asc)) resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('queryparamexists=true', resp.request.query)) - self.assertTrue(re.search('filter=tags%3ain%3a%5bstocks%2cmarket%5d', resp.request.query)) - self.assertTrue(re.search('sort=name%3aasc', resp.request.query)) + self.assertTrue(re.search("queryparamexists=true", resp.request.query)) + self.assertTrue(re.search("filter=tags%3ain%3a%5bstocks%2cmarket%5d", resp.request.query)) + self.assertTrue(re.search("sort=name%3aasc", resp.request.query)) # Test req_options for versions below 3.7 def test_filter_sort_legacy(self): @@ -164,16 +175,15 @@ def test_filter_sort_legacy(self): url = self.baseurl + "/views?queryParamExists=true" opts = TSC.RequestOptions() - opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, - TSC.RequestOptions.Operator.In, - ['stocks', 'market'])) - opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Direction.Asc)) + opts.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["stocks", "market"]) + ) + opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Asc)) resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('queryparamexists=true', resp.request.query)) - self.assertTrue(re.search('filter=tags:in:%5bstocks,market%5d', resp.request.query)) - self.assertTrue(re.search('sort=name:asc', resp.request.query)) + self.assertTrue(re.search("queryparamexists=true", resp.request.query)) + self.assertTrue(re.search("filter=tags:in:%5bstocks,market%5d", resp.request.query)) + self.assertTrue(re.search("sort=name:asc", resp.request.query)) def test_vf(self): with requests_mock.mock() as m: @@ -185,9 +195,9 @@ def test_vf(self): opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('vf_name1%23=value1', resp.request.query)) - self.assertTrue(re.search('vf_name2%24=value2', resp.request.query)) - self.assertTrue(re.search('type=tabloid', resp.request.query)) + self.assertTrue(re.search("vf_name1%23=value1", resp.request.query)) + self.assertTrue(re.search("vf_name2%24=value2", resp.request.query)) + self.assertTrue(re.search("type=tabloid", resp.request.query)) # Test req_options for versions beloe 3.7 def test_vf_legacy(self): @@ -201,9 +211,9 @@ def test_vf_legacy(self): opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('vf_name1@=value1', resp.request.query)) - self.assertTrue(re.search('vf_name2\\$=value2', resp.request.query)) - self.assertTrue(re.search('type=tabloid', resp.request.query)) + self.assertTrue(re.search("vf_name1@=value1", resp.request.query)) + self.assertTrue(re.search("vf_name2\\$=value2", resp.request.query)) + self.assertTrue(re.search("type=tabloid", resp.request.query)) def test_all_fields(self): with requests_mock.mock() as m: @@ -213,20 +223,23 @@ def test_all_fields(self): opts._all_fields = True resp = self.server.users.get_request(url, request_object=opts) - self.assertTrue(re.search('fields=_all_', resp.request.query)) + self.assertTrue(re.search("fields=_all_", resp.request.query)) def test_multiple_filter_options_shorthand(self): - with open(FILTER_MULTIPLE, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(FILTER_MULTIPLE, "rb") as f: + response_xml = f.read().decode("utf-8") # To ensure that this is deterministic, run this a few times with requests_mock.mock() as m: # Sometimes pep8 requires you to do things you might not otherwise do - url = ''.join((self.baseurl, '/workbooks?pageNumber=1&pageSize=100&', - 'filter=name:eq:foo,tags:in:[sample,safari,weather]')) + url = "".join( + ( + self.baseurl, + "/workbooks?pageNumber=1&pageSize=100&", + "filter=name:eq:foo,tags:in:[sample,safari,weather]", + ) + ) m.get(url, text=response_xml) for _ in range(100): - matching_workbooks = self.server.workbooks.filter( - tags__in=['sample', 'safari', 'weather'], name='foo' - ) + matching_workbooks = self.server.workbooks.filter(tags__in=["sample", "safari", "weather"], name="foo") self.assertEqual(3, matching_workbooks.total_available) diff --git a/test/test_requests.py b/test/test_requests.py index 2976e8f3e..d9dfc3ea2 100644 --- a/test/test_requests.py +++ b/test/test_requests.py @@ -10,11 +10,11 @@ class RequestTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake sign in - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.workbooks.baseurl @@ -25,26 +25,30 @@ def test_make_get_request(self): opts = TSC.RequestOptions(pagesize=13, pagenumber=15) resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('pagesize=13', resp.request.query)) - self.assertTrue(re.search('pagenumber=15', resp.request.query)) + self.assertTrue(re.search("pagesize=13", resp.request.query)) + self.assertTrue(re.search("pagenumber=15", resp.request.query)) def test_make_post_request(self): with requests_mock.mock() as m: m.post(requests_mock.ANY) url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks" - resp = self.server.workbooks._make_request(requests.post, url, content=b'1337', - auth_token='j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM', - content_type='multipart/mixed') - self.assertEqual(resp.request.headers['x-tableau-auth'], 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM') - self.assertEqual(resp.request.headers['content-type'], 'multipart/mixed') - self.assertEqual(resp.request.body, b'1337') + resp = self.server.workbooks._make_request( + requests.post, + url, + content=b"1337", + auth_token="j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM", + content_type="multipart/mixed", + ) + self.assertEqual(resp.request.headers["x-tableau-auth"], "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM") + self.assertEqual(resp.request.headers["content-type"], "multipart/mixed") + self.assertEqual(resp.request.body, b"1337") # Test that 500 server errors are handled properly def test_internal_server_error(self): self.server.version = "3.2" server_response = "500: Internal Server Error" with requests_mock.mock() as m: - m.register_uri('GET', self.server.server_info.baseurl, status_code=500, text=server_response) + m.register_uri("GET", self.server.server_info.baseurl, status_code=500, text=server_response) self.assertRaisesRegex(InternalServerError, server_response, self.server.server_info.get) # Test that non-xml server errors are handled properly @@ -52,5 +56,5 @@ def test_non_xml_error(self): self.server.version = "3.2" server_response = "this is not xml" with requests_mock.mock() as m: - m.register_uri('GET', self.server.server_info.baseurl, status_code=499, text=server_response) + m.register_uri("GET", self.server.server_info.baseurl, status_code=499, text=server_response) self.assertRaisesRegex(NonXMLResponseError, server_response, self.server.server_info.get) diff --git a/test/test_schedule.py b/test/test_schedule.py index 3a84caeb9..33c61710f 100644 --- a/test/test_schedule.py +++ b/test/test_schedule.py @@ -18,8 +18,8 @@ ADD_WORKBOOK_TO_SCHEDULE_WITH_WARNINGS = os.path.join(TEST_ASSET_DIR, "schedule_add_workbook_with_warnings.xml") ADD_DATASOURCE_TO_SCHEDULE = os.path.join(TEST_ASSET_DIR, "schedule_add_datasource.xml") -WORKBOOK_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, 'workbook_get_by_id.xml') -DATASOURCE_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, 'datasource_get_by_id.xml') +WORKBOOK_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_by_id.xml") +DATASOURCE_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "datasource_get_by_id.xml") class ScheduleTests(unittest.TestCase): @@ -91,11 +91,14 @@ def test_create_hourly(self): response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - hourly_interval = TSC.HourlyInterval(start_time=time(2, 30), - end_time=time(23, 0), - interval_value=2) - new_schedule = TSC.ScheduleItem("hourly-schedule-1", 50, TSC.ScheduleItem.Type.Extract, - TSC.ScheduleItem.ExecutionOrder.Parallel, hourly_interval) + hourly_interval = TSC.HourlyInterval(start_time=time(2, 30), end_time=time(23, 0), interval_value=2) + new_schedule = TSC.ScheduleItem( + "hourly-schedule-1", + 50, + TSC.ScheduleItem.Type.Extract, + TSC.ScheduleItem.ExecutionOrder.Parallel, + hourly_interval, + ) new_schedule = self.server.schedules.create(new_schedule) self.assertEqual("5f42be25-8a43-47ba-971a-63f2d4e7029c", new_schedule.id) @@ -117,8 +120,13 @@ def test_create_daily(self): with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) daily_interval = TSC.DailyInterval(time(4, 50)) - new_schedule = TSC.ScheduleItem("daily-schedule-1", 90, TSC.ScheduleItem.Type.Subscription, - TSC.ScheduleItem.ExecutionOrder.Serial, daily_interval) + new_schedule = TSC.ScheduleItem( + "daily-schedule-1", + 90, + TSC.ScheduleItem.Type.Subscription, + TSC.ScheduleItem.ExecutionOrder.Serial, + daily_interval, + ) new_schedule = self.server.schedules.create(new_schedule) self.assertEqual("907cae38-72fd-417c-892a-95540c4664cd", new_schedule.id) @@ -137,11 +145,16 @@ def test_create_weekly(self): response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - weekly_interval = TSC.WeeklyInterval(time(9, 15), TSC.IntervalItem.Day.Monday, - TSC.IntervalItem.Day.Wednesday, - TSC.IntervalItem.Day.Friday) - new_schedule = TSC.ScheduleItem("weekly-schedule-1", 80, TSC.ScheduleItem.Type.Extract, - TSC.ScheduleItem.ExecutionOrder.Parallel, weekly_interval) + weekly_interval = TSC.WeeklyInterval( + time(9, 15), TSC.IntervalItem.Day.Monday, TSC.IntervalItem.Day.Wednesday, TSC.IntervalItem.Day.Friday + ) + new_schedule = TSC.ScheduleItem( + "weekly-schedule-1", + 80, + TSC.ScheduleItem.Type.Extract, + TSC.ScheduleItem.ExecutionOrder.Parallel, + weekly_interval, + ) new_schedule = self.server.schedules.create(new_schedule) self.assertEqual("1adff386-6be0-4958-9f81-a35e676932bf", new_schedule.id) @@ -154,8 +167,7 @@ def test_create_weekly(self): self.assertEqual("2016-09-16T16:15:00Z", format_datetime(new_schedule.next_run_at)) self.assertEqual(TSC.ScheduleItem.ExecutionOrder.Parallel, new_schedule.execution_order) self.assertEqual(time(9, 15), new_schedule.interval_item.start_time) - self.assertEqual(("Monday", "Wednesday", "Friday"), - new_schedule.interval_item.interval) + self.assertEqual(("Monday", "Wednesday", "Friday"), new_schedule.interval_item.interval) self.assertEqual(2, len(new_schedule.warnings)) self.assertEqual("warning 1", new_schedule.warnings[0]) self.assertEqual("warning 2", new_schedule.warnings[1]) @@ -166,8 +178,13 @@ def test_create_monthly(self): with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) monthly_interval = TSC.MonthlyInterval(time(7), 12) - new_schedule = TSC.ScheduleItem("monthly-schedule-1", 20, TSC.ScheduleItem.Type.Extract, - TSC.ScheduleItem.ExecutionOrder.Serial, monthly_interval) + new_schedule = TSC.ScheduleItem( + "monthly-schedule-1", + 20, + TSC.ScheduleItem.Type.Extract, + TSC.ScheduleItem.ExecutionOrder.Serial, + monthly_interval, + ) new_schedule = self.server.schedules.create(new_schedule) self.assertEqual("e06a7c75-5576-4f68-882d-8909d0219326", new_schedule.id) @@ -186,11 +203,15 @@ def test_update(self): with open(UPDATE_XML, "rb") as f: response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.put(self.baseurl + '/7bea1766-1543-4052-9753-9d224bc069b5', text=response_xml) - new_interval = TSC.WeeklyInterval(time(7), TSC.IntervalItem.Day.Monday, - TSC.IntervalItem.Day.Friday) - single_schedule = TSC.ScheduleItem("weekly-schedule-1", 90, TSC.ScheduleItem.Type.Extract, - TSC.ScheduleItem.ExecutionOrder.Parallel, new_interval) + m.put(self.baseurl + "/7bea1766-1543-4052-9753-9d224bc069b5", text=response_xml) + new_interval = TSC.WeeklyInterval(time(7), TSC.IntervalItem.Day.Monday, TSC.IntervalItem.Day.Friday) + single_schedule = TSC.ScheduleItem( + "weekly-schedule-1", + 90, + TSC.ScheduleItem.Type.Extract, + TSC.ScheduleItem.ExecutionOrder.Parallel, + new_interval, + ) single_schedule._id = "7bea1766-1543-4052-9753-9d224bc069b5" single_schedule.state = TSC.ScheduleItem.State.Suspended single_schedule = self.server.schedules.update(single_schedule) @@ -203,8 +224,7 @@ def test_update(self): self.assertEqual("2016-09-16T14:00:00Z", format_datetime(single_schedule.next_run_at)) self.assertEqual(TSC.ScheduleItem.ExecutionOrder.Parallel, single_schedule.execution_order) self.assertEqual(time(7), single_schedule.interval_item.start_time) - self.assertEqual(("Monday", "Friday"), - single_schedule.interval_item.interval) + self.assertEqual(("Monday", "Friday"), single_schedule.interval_item.interval) self.assertEqual(TSC.ScheduleItem.State.Suspended, single_schedule.state) # Tests calling update with a schedule item returned from the server @@ -220,17 +240,17 @@ def test_update_after_get(self): all_schedules, pagination_item = self.server.schedules.get() schedule_item = all_schedules[0] self.assertEqual(TSC.ScheduleItem.State.Active, schedule_item.state) - self.assertEqual('Weekday early mornings', schedule_item.name) + self.assertEqual("Weekday early mornings", schedule_item.name) # Update the schedule with requests_mock.mock() as m: - m.put(self.baseurl + '/c9cff7f9-309c-4361-99ff-d4ba8c9f5467', text=update_response_xml) + m.put(self.baseurl + "/c9cff7f9-309c-4361-99ff-d4ba8c9f5467", text=update_response_xml) schedule_item.state = TSC.ScheduleItem.State.Suspended - schedule_item.name = 'newName' + schedule_item.name = "newName" schedule_item = self.server.schedules.update(schedule_item) self.assertEqual(TSC.ScheduleItem.State.Suspended, schedule_item.state) - self.assertEqual('weekly-schedule-1', schedule_item.name) + self.assertEqual("weekly-schedule-1", schedule_item.name) def test_add_workbook(self): self.server.version = "2.8" @@ -241,10 +261,10 @@ def test_add_workbook(self): with open(ADD_WORKBOOK_TO_SCHEDULE, "rb") as f: add_workbook_response = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.server.workbooks.baseurl + '/bar', text=workbook_response) - m.put(baseurl + '/foo/workbooks', text=add_workbook_response) + m.get(self.server.workbooks.baseurl + "/bar", text=workbook_response) + m.put(baseurl + "/foo/workbooks", text=add_workbook_response) workbook = self.server.workbooks.get_by_id("bar") - result = self.server.schedules.add_to_schedule('foo', workbook=workbook) + result = self.server.schedules.add_to_schedule("foo", workbook=workbook) self.assertEqual(0, len(result), "Added properly") def test_add_workbook_with_warnings(self): @@ -256,10 +276,10 @@ def test_add_workbook_with_warnings(self): with open(ADD_WORKBOOK_TO_SCHEDULE_WITH_WARNINGS, "rb") as f: add_workbook_response = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.server.workbooks.baseurl + '/bar', text=workbook_response) - m.put(baseurl + '/foo/workbooks', text=add_workbook_response) + m.get(self.server.workbooks.baseurl + "/bar", text=workbook_response) + m.put(baseurl + "/foo/workbooks", text=add_workbook_response) workbook = self.server.workbooks.get_by_id("bar") - result = self.server.schedules.add_to_schedule('foo', workbook=workbook) + result = self.server.schedules.add_to_schedule("foo", workbook=workbook) self.assertEqual(1, len(result), "Not added properly") self.assertEqual(2, len(result[0].warnings)) @@ -272,8 +292,8 @@ def test_add_datasource(self): with open(ADD_DATASOURCE_TO_SCHEDULE, "rb") as f: add_datasource_response = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.server.datasources.baseurl + '/bar', text=datasource_response) - m.put(baseurl + '/foo/datasources', text=add_datasource_response) + m.get(self.server.datasources.baseurl + "/bar", text=datasource_response) + m.put(baseurl + "/foo/datasources", text=add_datasource_response) datasource = self.server.datasources.get_by_id("bar") - result = self.server.schedules.add_to_schedule('foo', datasource=datasource) + result = self.server.schedules.add_to_schedule("foo", datasource=datasource) self.assertEqual(0, len(result), "Added properly") diff --git a/test/test_server_info.py b/test/test_server_info.py index 3dadff7c1..08565f6d5 100644 --- a/test/test_server_info.py +++ b/test/test_server_info.py @@ -3,60 +3,60 @@ import requests_mock import tableauserverclient as TSC -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -SERVER_INFO_GET_XML = os.path.join(TEST_ASSET_DIR, 'server_info_get.xml') -SERVER_INFO_25_XML = os.path.join(TEST_ASSET_DIR, 'server_info_25.xml') -SERVER_INFO_404 = os.path.join(TEST_ASSET_DIR, 'server_info_404.xml') -SERVER_INFO_AUTH_INFO_XML = os.path.join(TEST_ASSET_DIR, 'server_info_auth_info.xml') +SERVER_INFO_GET_XML = os.path.join(TEST_ASSET_DIR, "server_info_get.xml") +SERVER_INFO_25_XML = os.path.join(TEST_ASSET_DIR, "server_info_25.xml") +SERVER_INFO_404 = os.path.join(TEST_ASSET_DIR, "server_info_404.xml") +SERVER_INFO_AUTH_INFO_XML = os.path.join(TEST_ASSET_DIR, "server_info_auth_info.xml") class ServerInfoTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") self.baseurl = self.server.server_info.baseurl self.server.version = "2.4" def test_server_info_get(self): - with open(SERVER_INFO_GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(SERVER_INFO_GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.server.server_info.baseurl, text=response_xml) actual = self.server.server_info.get() - self.assertEqual('10.1.0', actual.product_version) - self.assertEqual('10100.16.1024.2100', actual.build_number) - self.assertEqual('2.4', actual.rest_api_version) + self.assertEqual("10.1.0", actual.product_version) + self.assertEqual("10100.16.1024.2100", actual.build_number) + self.assertEqual("2.4", actual.rest_api_version) def test_server_info_use_highest_version_downgrades(self): - with open(SERVER_INFO_AUTH_INFO_XML, 'rb') as f: + with open(SERVER_INFO_AUTH_INFO_XML, "rb") as f: # This is the auth.xml endpoint present back to 9.0 Servers - auth_response_xml = f.read().decode('utf-8') - with open(SERVER_INFO_404, 'rb') as f: + auth_response_xml = f.read().decode("utf-8") + with open(SERVER_INFO_404, "rb") as f: # 10.1 serverInfo response - si_response_xml = f.read().decode('utf-8') + si_response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: # Return a 404 for serverInfo so we can pretend this is an old Server m.get(self.server.server_address + "/api/2.4/serverInfo", text=si_response_xml, status_code=404) m.get(self.server.server_address + "/auth?format=xml", text=auth_response_xml) self.server.use_server_version() - self.assertEqual(self.server.version, '2.2') + self.assertEqual(self.server.version, "2.2") def test_server_info_use_highest_version_upgrades(self): - with open(SERVER_INFO_GET_XML, 'rb') as f: - si_response_xml = f.read().decode('utf-8') + with open(SERVER_INFO_GET_XML, "rb") as f: + si_response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.server.server_address + "/api/2.4/serverInfo", text=si_response_xml) # Pretend we're old - self.server.version = '2.0' + self.server.version = "2.0" self.server.use_server_version() # Did we upgrade to 2.4? - self.assertEqual(self.server.version, '2.4') + self.assertEqual(self.server.version, "2.4") def test_server_use_server_version_flag(self): - with open(SERVER_INFO_25_XML, 'rb') as f: - si_response_xml = f.read().decode('utf-8') + with open(SERVER_INFO_25_XML, "rb") as f: + si_response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get('http://test/api/2.4/serverInfo', text=si_response_xml) - server = TSC.Server('http://test', use_server_version=True) - self.assertEqual(server.version, '2.5') + m.get("http://test/api/2.4/serverInfo", text=si_response_xml) + server = TSC.Server("http://test", use_server_version=True) + self.assertEqual(server.version, "2.5") diff --git a/test/test_site.py b/test/test_site.py index 8fbb4eda3..b8cb5bbfd 100644 --- a/test/test_site.py +++ b/test/test_site.py @@ -3,37 +3,37 @@ import requests_mock import tableauserverclient as TSC -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -GET_XML = os.path.join(TEST_ASSET_DIR, 'site_get.xml') -GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, 'site_get_by_id.xml') -GET_BY_NAME_XML = os.path.join(TEST_ASSET_DIR, 'site_get_by_name.xml') -UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'site_update.xml') -CREATE_XML = os.path.join(TEST_ASSET_DIR, 'site_create.xml') +GET_XML = os.path.join(TEST_ASSET_DIR, "site_get.xml") +GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "site_get_by_id.xml") +GET_BY_NAME_XML = os.path.join(TEST_ASSET_DIR, "site_get_by_name.xml") +UPDATE_XML = os.path.join(TEST_ASSET_DIR, "site_update.xml") +CREATE_XML = os.path.join(TEST_ASSET_DIR, "site_create.xml") class SiteTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") self.server.version = "3.10" # Fake signin - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' - self.server._site_id = '0626857c-1def-4503-a7d8-7907c3ff9d9f' + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" + self.server._site_id = "0626857c-1def-4503-a7d8-7907c3ff9d9f" self.baseurl = self.server.sites.baseurl def test_get(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_sites, pagination_item = self.server.sites.get() self.assertEqual(2, pagination_item.total_available) - self.assertEqual('dad65087-b08b-4603-af4e-2887b8aafc67', all_sites[0].id) - self.assertEqual('Active', all_sites[0].state) - self.assertEqual('Default', all_sites[0].name) - self.assertEqual('ContentOnly', all_sites[0].admin_mode) + self.assertEqual("dad65087-b08b-4603-af4e-2887b8aafc67", all_sites[0].id) + self.assertEqual("Active", all_sites[0].state) + self.assertEqual("Default", all_sites[0].name) + self.assertEqual("ContentOnly", all_sites[0].admin_mode) self.assertEqual(False, all_sites[0].revision_history_enabled) self.assertEqual(True, all_sites[0].subscribe_others_enabled) self.assertEqual(25, all_sites[0].revision_limit) @@ -43,10 +43,10 @@ def test_get(self): self.assertEqual(False, all_sites[0].editing_flows_enabled) self.assertEqual(False, all_sites[0].scheduling_flows_enabled) self.assertEqual(True, all_sites[0].allow_subscription_attachments) - self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', all_sites[1].id) - self.assertEqual('Active', all_sites[1].state) - self.assertEqual('Samples', all_sites[1].name) - self.assertEqual('ContentOnly', all_sites[1].admin_mode) + self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", all_sites[1].id) + self.assertEqual("Active", all_sites[1].state) + self.assertEqual("Samples", all_sites[1].name) + self.assertEqual("ContentOnly", all_sites[1].admin_mode) self.assertEqual(False, all_sites[1].revision_history_enabled) self.assertEqual(True, all_sites[1].subscribe_others_enabled) self.assertEqual(False, all_sites[1].guest_access_enabled) @@ -66,16 +66,16 @@ def test_get_before_signin(self): self.assertRaises(TSC.NotSignedInError, self.server.sites.get) def test_get_by_id(self): - with open(GET_BY_ID_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_BY_ID_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/dad65087-b08b-4603-af4e-2887b8aafc67', text=response_xml) - single_site = self.server.sites.get_by_id('dad65087-b08b-4603-af4e-2887b8aafc67') + m.get(self.baseurl + "/dad65087-b08b-4603-af4e-2887b8aafc67", text=response_xml) + single_site = self.server.sites.get_by_id("dad65087-b08b-4603-af4e-2887b8aafc67") - self.assertEqual('dad65087-b08b-4603-af4e-2887b8aafc67', single_site.id) - self.assertEqual('Active', single_site.state) - self.assertEqual('Default', single_site.name) - self.assertEqual('ContentOnly', single_site.admin_mode) + self.assertEqual("dad65087-b08b-4603-af4e-2887b8aafc67", single_site.id) + self.assertEqual("Active", single_site.state) + self.assertEqual("Default", single_site.name) + self.assertEqual("ContentOnly", single_site.admin_mode) self.assertEqual(False, single_site.revision_history_enabled) self.assertEqual(True, single_site.subscribe_others_enabled) self.assertEqual(False, single_site.disable_subscriptions) @@ -84,60 +84,73 @@ def test_get_by_id(self): self.assertEqual(True, single_site.catalog_obfuscation_enabled) def test_get_by_id_missing_id(self): - self.assertRaises(ValueError, self.server.sites.get_by_id, '') + self.assertRaises(ValueError, self.server.sites.get_by_id, "") def test_get_by_name(self): - with open(GET_BY_NAME_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_BY_NAME_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/testsite?key=name', text=response_xml) - single_site = self.server.sites.get_by_name('testsite') + m.get(self.baseurl + "/testsite?key=name", text=response_xml) + single_site = self.server.sites.get_by_name("testsite") - self.assertEqual('dad65087-b08b-4603-af4e-2887b8aafc67', single_site.id) - self.assertEqual('Active', single_site.state) - self.assertEqual('testsite', single_site.name) - self.assertEqual('ContentOnly', single_site.admin_mode) + self.assertEqual("dad65087-b08b-4603-af4e-2887b8aafc67", single_site.id) + self.assertEqual("Active", single_site.state) + self.assertEqual("testsite", single_site.name) + self.assertEqual("ContentOnly", single_site.admin_mode) self.assertEqual(False, single_site.revision_history_enabled) self.assertEqual(True, single_site.subscribe_others_enabled) self.assertEqual(False, single_site.disable_subscriptions) def test_get_by_name_missing_name(self): - self.assertRaises(ValueError, self.server.sites.get_by_name, '') + self.assertRaises(ValueError, self.server.sites.get_by_name, "") def test_update(self): - with open(UPDATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(UPDATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.put(self.baseurl + '/6b7179ba-b82b-4f0f-91ed-812074ac5da6', text=response_xml) - single_site = TSC.SiteItem(name='Tableau', content_url='tableau', - admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers, - user_quota=15, storage_quota=1000, - disable_subscriptions=True, revision_history_enabled=False, - data_acceleration_mode='disable', flow_auto_save_enabled=True, - web_extraction_enabled=False, metrics_content_type_enabled=True, - notify_site_admins_on_throttle=False, authoring_enabled=True, - custom_subscription_email_enabled=True, - custom_subscription_email='test@test.com', - custom_subscription_footer_enabled=True, - custom_subscription_footer='example_footer', ask_data_mode='EnabledByDefault', - named_sharing_enabled=False, mobile_biometrics_enabled=True, - sheet_image_enabled=False, derived_permissions_enabled=True, - user_visibility_mode='FULL', use_default_time_zone=False, - time_zone='America/Los_Angeles', auto_suspend_refresh_enabled=True, - auto_suspend_refresh_inactivity_window=55) - single_site._id = '6b7179ba-b82b-4f0f-91ed-812074ac5da6' + m.put(self.baseurl + "/6b7179ba-b82b-4f0f-91ed-812074ac5da6", text=response_xml) + single_site = TSC.SiteItem( + name="Tableau", + content_url="tableau", + admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers, + user_quota=15, + storage_quota=1000, + disable_subscriptions=True, + revision_history_enabled=False, + data_acceleration_mode="disable", + flow_auto_save_enabled=True, + web_extraction_enabled=False, + metrics_content_type_enabled=True, + notify_site_admins_on_throttle=False, + authoring_enabled=True, + custom_subscription_email_enabled=True, + custom_subscription_email="test@test.com", + custom_subscription_footer_enabled=True, + custom_subscription_footer="example_footer", + ask_data_mode="EnabledByDefault", + named_sharing_enabled=False, + mobile_biometrics_enabled=True, + sheet_image_enabled=False, + derived_permissions_enabled=True, + user_visibility_mode="FULL", + use_default_time_zone=False, + time_zone="America/Los_Angeles", + auto_suspend_refresh_enabled=True, + auto_suspend_refresh_inactivity_window=55, + ) + single_site._id = "6b7179ba-b82b-4f0f-91ed-812074ac5da6" single_site = self.server.sites.update(single_site) - self.assertEqual('6b7179ba-b82b-4f0f-91ed-812074ac5da6', single_site.id) - self.assertEqual('tableau', single_site.content_url) - self.assertEqual('Suspended', single_site.state) - self.assertEqual('Tableau', single_site.name) - self.assertEqual('ContentAndUsers', single_site.admin_mode) + self.assertEqual("6b7179ba-b82b-4f0f-91ed-812074ac5da6", single_site.id) + self.assertEqual("tableau", single_site.content_url) + self.assertEqual("Suspended", single_site.state) + self.assertEqual("Tableau", single_site.name) + self.assertEqual("ContentAndUsers", single_site.admin_mode) self.assertEqual(True, single_site.revision_history_enabled) self.assertEqual(13, single_site.revision_limit) self.assertEqual(True, single_site.disable_subscriptions) self.assertEqual(15, single_site.user_quota) - self.assertEqual('disable', single_site.data_acceleration_mode) + self.assertEqual("disable", single_site.data_acceleration_mode) self.assertEqual(True, single_site.flows_enabled) self.assertEqual(True, single_site.cataloging_enabled) self.assertEqual(True, single_site.flow_auto_save_enabled) @@ -146,39 +159,44 @@ def test_update(self): self.assertEqual(False, single_site.notify_site_admins_on_throttle) self.assertEqual(True, single_site.authoring_enabled) self.assertEqual(True, single_site.custom_subscription_email_enabled) - self.assertEqual('test@test.com', single_site.custom_subscription_email) + self.assertEqual("test@test.com", single_site.custom_subscription_email) self.assertEqual(True, single_site.custom_subscription_footer_enabled) - self.assertEqual('example_footer', single_site.custom_subscription_footer) - self.assertEqual('EnabledByDefault', single_site.ask_data_mode) + self.assertEqual("example_footer", single_site.custom_subscription_footer) + self.assertEqual("EnabledByDefault", single_site.ask_data_mode) self.assertEqual(False, single_site.named_sharing_enabled) self.assertEqual(True, single_site.mobile_biometrics_enabled) self.assertEqual(False, single_site.sheet_image_enabled) self.assertEqual(True, single_site.derived_permissions_enabled) - self.assertEqual('FULL', single_site.user_visibility_mode) + self.assertEqual("FULL", single_site.user_visibility_mode) self.assertEqual(False, single_site.use_default_time_zone) - self.assertEqual('America/Los_Angeles', single_site.time_zone) + self.assertEqual("America/Los_Angeles", single_site.time_zone) self.assertEqual(True, single_site.auto_suspend_refresh_enabled) self.assertEqual(55, single_site.auto_suspend_refresh_inactivity_window) def test_update_missing_id(self): - single_site = TSC.SiteItem('test', 'test') + single_site = TSC.SiteItem("test", "test") self.assertRaises(TSC.MissingRequiredFieldError, self.server.sites.update, single_site) def test_create(self): - with open(CREATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_site = TSC.SiteItem(name='Tableau', content_url='tableau', - admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers, user_quota=15, - storage_quota=1000, disable_subscriptions=True) + new_site = TSC.SiteItem( + name="Tableau", + content_url="tableau", + admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers, + user_quota=15, + storage_quota=1000, + disable_subscriptions=True, + ) new_site = self.server.sites.create(new_site) - self.assertEqual('0626857c-1def-4503-a7d8-7907c3ff9d9f', new_site.id) - self.assertEqual('tableau', new_site.content_url) - self.assertEqual('Tableau', new_site.name) - self.assertEqual('Active', new_site.state) - self.assertEqual('ContentAndUsers', new_site.admin_mode) + self.assertEqual("0626857c-1def-4503-a7d8-7907c3ff9d9f", new_site.id) + self.assertEqual("tableau", new_site.content_url) + self.assertEqual("Tableau", new_site.name) + self.assertEqual("Active", new_site.state) + self.assertEqual("ContentAndUsers", new_site.admin_mode) self.assertEqual(False, new_site.revision_history_enabled) self.assertEqual(True, new_site.subscribe_others_enabled) self.assertEqual(True, new_site.disable_subscriptions) @@ -186,23 +204,23 @@ def test_create(self): def test_delete(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f', status_code=204) - self.server.sites.delete('0626857c-1def-4503-a7d8-7907c3ff9d9f') + m.delete(self.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f", status_code=204) + self.server.sites.delete("0626857c-1def-4503-a7d8-7907c3ff9d9f") def test_delete_missing_id(self): - self.assertRaises(ValueError, self.server.sites.delete, '') + self.assertRaises(ValueError, self.server.sites.delete, "") def test_encrypt(self): with requests_mock.mock() as m: - m.post(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f/encrypt-extracts', status_code=200) - self.server.sites.encrypt_extracts('0626857c-1def-4503-a7d8-7907c3ff9d9f') + m.post(self.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/encrypt-extracts", status_code=200) + self.server.sites.encrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f") def test_recrypt(self): with requests_mock.mock() as m: - m.post(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f/reencrypt-extracts', status_code=200) - self.server.sites.re_encrypt_extracts('0626857c-1def-4503-a7d8-7907c3ff9d9f') + m.post(self.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/reencrypt-extracts", status_code=200) + self.server.sites.re_encrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f") def test_decrypt(self): with requests_mock.mock() as m: - m.post(self.baseurl + '/0626857c-1def-4503-a7d8-7907c3ff9d9f/decrypt-extracts', status_code=200) - self.server.sites.decrypt_extracts('0626857c-1def-4503-a7d8-7907c3ff9d9f') + m.post(self.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/decrypt-extracts", status_code=200) + self.server.sites.decrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f") diff --git a/test/test_sort.py b/test/test_sort.py index 0572a1e10..105240fba 100644 --- a/test/test_sort.py +++ b/test/test_sort.py @@ -7,10 +7,10 @@ class SortTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") self.server.version = "3.10" - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.workbooks.baseurl def test_empty_filter(self): @@ -21,21 +21,17 @@ def test_filter_equals(self): m.get(requests_mock.ANY) url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks" opts = TSC.RequestOptions(pagesize=13, pagenumber=13) - opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Operator.Equals, - 'Superstore')) + opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "Superstore")) resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('pagenumber=13', resp.request.query)) - self.assertTrue(re.search('pagesize=13', resp.request.query)) - self.assertTrue(re.search('filter=name%3aeq%3asuperstore', resp.request.query)) + self.assertTrue(re.search("pagenumber=13", resp.request.query)) + self.assertTrue(re.search("pagesize=13", resp.request.query)) + self.assertTrue(re.search("filter=name%3aeq%3asuperstore", resp.request.query)) def test_filter_equals_list(self): with self.assertRaises(ValueError) as cm: - TSC.Filter(TSC.RequestOptions.Field.Tags, - TSC.RequestOptions.Operator.Equals, - ['foo', 'bar']) + TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.Equals, ["foo", "bar"]) self.assertEqual("Filter values can only be a list if the operator is 'in'.", str(cm.exception)), @@ -45,28 +41,27 @@ def test_filter_in(self): url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks" opts = TSC.RequestOptions(pagesize=13, pagenumber=13) - opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, - TSC.RequestOptions.Operator.In, - ['stocks', 'market'])) + opts.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["stocks", "market"]) + ) resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('pagenumber=13', resp.request.query)) - self.assertTrue(re.search('pagesize=13', resp.request.query)) - self.assertTrue(re.search('filter=tags%3ain%3a%5bstocks%2cmarket%5d', resp.request.query)) + self.assertTrue(re.search("pagenumber=13", resp.request.query)) + self.assertTrue(re.search("pagesize=13", resp.request.query)) + self.assertTrue(re.search("filter=tags%3ain%3a%5bstocks%2cmarket%5d", resp.request.query)) def test_sort_asc(self): with requests_mock.mock() as m: m.get(requests_mock.ANY) url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks" opts = TSC.RequestOptions(pagesize=13, pagenumber=13) - opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, - TSC.RequestOptions.Direction.Asc)) + opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Asc)) resp = self.server.workbooks.get_request(url, request_object=opts) - self.assertTrue(re.search('pagenumber=13', resp.request.query)) - self.assertTrue(re.search('pagesize=13', resp.request.query)) - self.assertTrue(re.search('sort=name%3aasc', resp.request.query)) + self.assertTrue(re.search("pagenumber=13", resp.request.query)) + self.assertTrue(re.search("pagesize=13", resp.request.query)) + self.assertTrue(re.search("sort=name%3aasc", resp.request.query)) def test_filter_combo(self): with requests_mock.mock() as m: @@ -74,25 +69,34 @@ def test_filter_combo(self): url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/users" opts = TSC.RequestOptions(pagesize=13, pagenumber=13) - opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.LastLogin, - TSC.RequestOptions.Operator.GreaterThanOrEqual, - '2017-01-15T00:00:00:00Z')) + opts.filter.add( + TSC.Filter( + TSC.RequestOptions.Field.LastLogin, + TSC.RequestOptions.Operator.GreaterThanOrEqual, + "2017-01-15T00:00:00:00Z", + ) + ) - opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.SiteRole, - TSC.RequestOptions.Operator.Equals, - 'Publisher')) + opts.filter.add( + TSC.Filter(TSC.RequestOptions.Field.SiteRole, TSC.RequestOptions.Operator.Equals, "Publisher") + ) resp = self.server.workbooks.get_request(url, request_object=opts) - expected = 'pagenumber=13&pagesize=13&filter=lastlogin%3agte%3a' \ - '2017-01-15t00%3a00%3a00%3a00z%2csiterole%3aeq%3apublisher' + expected = ( + "pagenumber=13&pagesize=13&filter=lastlogin%3agte%3a" + "2017-01-15t00%3a00%3a00%3a00z%2csiterole%3aeq%3apublisher" + ) - self.assertTrue(re.search('pagenumber=13', resp.request.query)) - self.assertTrue(re.search('pagesize=13', resp.request.query)) - self.assertTrue(re.search( - 'filter=lastlogin%3agte%3a2017-01-15t00%3a00%3a00%3a00z%2csiterole%3aeq%3apublisher', - resp.request.query)) + self.assertTrue(re.search("pagenumber=13", resp.request.query)) + self.assertTrue(re.search("pagesize=13", resp.request.query)) + self.assertTrue( + re.search( + "filter=lastlogin%3agte%3a2017-01-15t00%3a00%3a00%3a00z%2csiterole%3aeq%3apublisher", + resp.request.query, + ) + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/test_subscription.py b/test/test_subscription.py index 15b845e56..951157575 100644 --- a/test/test_subscription.py +++ b/test/test_subscription.py @@ -13,7 +13,7 @@ class SubscriptionTests(unittest.TestCase): def setUp(self): self.server = TSC.Server("http://test") - self.server.version = '2.6' + self.server.version = "2.6" # Fake Signin self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" @@ -30,58 +30,59 @@ def test_get_subscriptions(self): self.assertEqual(2, pagination_item.total_available) subscription = all_subscriptions[0] - self.assertEqual('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', subscription.id) - self.assertEqual('NOT FOUND!', subscription.message) + self.assertEqual("382e9a6e-0c08-4a95-b6c1-c14df7bac3e4", subscription.id) + self.assertEqual("NOT FOUND!", subscription.message) self.assertTrue(subscription.attach_image) self.assertFalse(subscription.attach_pdf) self.assertFalse(subscription.suspended) self.assertFalse(subscription.send_if_view_empty) self.assertIsNone(subscription.page_orientation) self.assertIsNone(subscription.page_size_option) - self.assertEqual('Not Found Alert', subscription.subject) - self.assertEqual('cdd716ca-5818-470e-8bec-086885dbadee', subscription.target.id) - self.assertEqual('View', subscription.target.type) - self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id) - self.assertEqual('7617c389-cdca-4940-a66e-69956fcebf3e', subscription.schedule_id) + self.assertEqual("Not Found Alert", subscription.subject) + self.assertEqual("cdd716ca-5818-470e-8bec-086885dbadee", subscription.target.id) + self.assertEqual("View", subscription.target.type) + self.assertEqual("c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e", subscription.user_id) + self.assertEqual("7617c389-cdca-4940-a66e-69956fcebf3e", subscription.schedule_id) subscription = all_subscriptions[1] - self.assertEqual('23cb7630-afc8-4c8e-b6cd-83ae0322ec66', subscription.id) - self.assertEqual('overview', subscription.message) + self.assertEqual("23cb7630-afc8-4c8e-b6cd-83ae0322ec66", subscription.id) + self.assertEqual("overview", subscription.message) self.assertFalse(subscription.attach_image) self.assertTrue(subscription.attach_pdf) self.assertTrue(subscription.suspended) self.assertTrue(subscription.send_if_view_empty) - self.assertEqual('PORTRAIT', subscription.page_orientation) - self.assertEqual('A5', subscription.page_size_option) - self.assertEqual('Last 7 Days', subscription.subject) - self.assertEqual('2e6b4e8f-22dd-4061-8f75-bf33703da7e5', subscription.target.id) - self.assertEqual('Workbook', subscription.target.type) - self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id) - self.assertEqual('3407cd38-7b39-4983-86a6-67a1506a5e3f', subscription.schedule_id) + self.assertEqual("PORTRAIT", subscription.page_orientation) + self.assertEqual("A5", subscription.page_size_option) + self.assertEqual("Last 7 Days", subscription.subject) + self.assertEqual("2e6b4e8f-22dd-4061-8f75-bf33703da7e5", subscription.target.id) + self.assertEqual("Workbook", subscription.target.type) + self.assertEqual("c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e", subscription.user_id) + self.assertEqual("3407cd38-7b39-4983-86a6-67a1506a5e3f", subscription.schedule_id) def test_get_subscription_by_id(self): with open(GET_XML_BY_ID, "rb") as f: response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', text=response_xml) - subscription = self.server.subscriptions.get_by_id('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4') + m.get(self.baseurl + "/382e9a6e-0c08-4a95-b6c1-c14df7bac3e4", text=response_xml) + subscription = self.server.subscriptions.get_by_id("382e9a6e-0c08-4a95-b6c1-c14df7bac3e4") - self.assertEqual('382e9a6e-0c08-4a95-b6c1-c14df7bac3e4', subscription.id) - self.assertEqual('View', subscription.target.type) - self.assertEqual('cdd716ca-5818-470e-8bec-086885dbadee', subscription.target.id) - self.assertEqual('c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e', subscription.user_id) - self.assertEqual('Not Found Alert', subscription.subject) - self.assertEqual('7617c389-cdca-4940-a66e-69956fcebf3e', subscription.schedule_id) + self.assertEqual("382e9a6e-0c08-4a95-b6c1-c14df7bac3e4", subscription.id) + self.assertEqual("View", subscription.target.type) + self.assertEqual("cdd716ca-5818-470e-8bec-086885dbadee", subscription.target.id) + self.assertEqual("c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e", subscription.user_id) + self.assertEqual("Not Found Alert", subscription.subject) + self.assertEqual("7617c389-cdca-4940-a66e-69956fcebf3e", subscription.schedule_id) def test_create_subscription(self): - with open(CREATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) target_item = TSC.Target("960e61f2-1838-40b2-bba2-340c9492f943", "workbook") - new_subscription = TSC.SubscriptionItem("subject", "4906c453-d5ec-4972-9ff4-789b629bdfa2", - "8d30c8de-0a5f-4bee-b266-c621b4f3eed0", target_item) + new_subscription = TSC.SubscriptionItem( + "subject", "4906c453-d5ec-4972-9ff4-789b629bdfa2", "8d30c8de-0a5f-4bee-b266-c621b4f3eed0", target_item + ) new_subscription = self.server.subscriptions.create(new_subscription) self.assertEqual("78e9318d-2d29-4d67-b60f-3f2f5fd89ecc", new_subscription.id) @@ -93,5 +94,5 @@ def test_create_subscription(self): def test_delete_subscription(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/78e9318d-2d29-4d67-b60f-3f2f5fd89ecc', status_code=204) - self.server.subscriptions.delete('78e9318d-2d29-4d67-b60f-3f2f5fd89ecc') + m.delete(self.baseurl + "/78e9318d-2d29-4d67-b60f-3f2f5fd89ecc", status_code=204) + self.server.subscriptions.delete("78e9318d-2d29-4d67-b60f-3f2f5fd89ecc") diff --git a/test/test_table.py b/test/test_table.py index 45af43c9a..f03b9a522 100644 --- a/test/test_table.py +++ b/test/test_table.py @@ -8,17 +8,17 @@ from tableauserverclient.server.request_factory import RequestFactory from ._utils import read_xml_asset, read_xml_assets, asset -GET_XML = 'table_get.xml' -UPDATE_XML = 'table_update.xml' +GET_XML = "table_get.xml" +UPDATE_XML = "table_update.xml" class TableTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.server.version = "3.5" self.baseurl = self.server.tables.baseurl @@ -30,33 +30,33 @@ def test_get(self): all_tables, pagination_item = self.server.tables.get() self.assertEqual(4, pagination_item.total_available) - self.assertEqual('10224773-ecee-42ac-b822-d786b0b8e4d9', all_tables[0].id) - self.assertEqual('dim_Product', all_tables[0].name) + self.assertEqual("10224773-ecee-42ac-b822-d786b0b8e4d9", all_tables[0].id) + self.assertEqual("dim_Product", all_tables[0].name) - self.assertEqual('53c77bc1-fb41-4342-a75a-f68ac0656d0d', all_tables[1].id) - self.assertEqual('customer', all_tables[1].name) - self.assertEqual('dbo', all_tables[1].schema) - self.assertEqual('9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0', all_tables[1].contact_id) + self.assertEqual("53c77bc1-fb41-4342-a75a-f68ac0656d0d", all_tables[1].id) + self.assertEqual("customer", all_tables[1].name) + self.assertEqual("dbo", all_tables[1].schema) + self.assertEqual("9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0", all_tables[1].contact_id) self.assertEqual(False, all_tables[1].certified) def test_update(self): response_xml = read_xml_asset(UPDATE_XML) with requests_mock.mock() as m: - m.put(self.baseurl + '/10224773-ecee-42ac-b822-d786b0b8e4d9', text=response_xml) - single_table = TSC.TableItem('test') - single_table._id = '10224773-ecee-42ac-b822-d786b0b8e4d9' + m.put(self.baseurl + "/10224773-ecee-42ac-b822-d786b0b8e4d9", text=response_xml) + single_table = TSC.TableItem("test") + single_table._id = "10224773-ecee-42ac-b822-d786b0b8e4d9" - single_table.contact_id = '8e1a8235-c9ee-4d61-ae82-2ffacceed8e0' + single_table.contact_id = "8e1a8235-c9ee-4d61-ae82-2ffacceed8e0" single_table.certified = True single_table.certification_note = "Test" single_table = self.server.tables.update(single_table) - self.assertEqual('10224773-ecee-42ac-b822-d786b0b8e4d9', single_table.id) - self.assertEqual('8e1a8235-c9ee-4d61-ae82-2ffacceed8e0', single_table.contact_id) + self.assertEqual("10224773-ecee-42ac-b822-d786b0b8e4d9", single_table.id) + self.assertEqual("8e1a8235-c9ee-4d61-ae82-2ffacceed8e0", single_table.contact_id) self.assertEqual(True, single_table.certified) self.assertEqual("Test", single_table.certification_note) def test_delete(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/0448d2ed-590d-4fa0-b272-a2a8a24555b5', status_code=204) - self.server.tables.delete('0448d2ed-590d-4fa0-b272-a2a8a24555b5') + m.delete(self.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5", status_code=204) + self.server.tables.delete("0448d2ed-590d-4fa0-b272-a2a8a24555b5") diff --git a/test/test_tableauauth_model.py b/test/test_tableauauth_model.py index 94a44706a..3314aee26 100644 --- a/test/test_tableauauth_model.py +++ b/test/test_tableauauth_model.py @@ -5,10 +5,7 @@ class TableauAuthModelTests(unittest.TestCase): def setUp(self): - self.auth = TSC.TableauAuth('user', - 'password', - site_id='site1', - user_id_to_impersonate='admin') + self.auth = TSC.TableauAuth("user", "password", site_id="site1", user_id_to_impersonate="admin") def test_username_password_required(self): with self.assertRaises(TypeError): @@ -18,8 +15,6 @@ def test_site_arg_raises_warning(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") - tableau_auth = TSC.TableauAuth('user', - 'password', - site='Default') + tableau_auth = TSC.TableauAuth("user", "password", site="Default") self.assertTrue(any(item.category == DeprecationWarning for item in w)) diff --git a/test/test_task.py b/test/test_task.py index 566167d4a..329caf2e7 100644 --- a/test/test_task.py +++ b/test/test_task.py @@ -18,7 +18,7 @@ class TaskTests(unittest.TestCase): def setUp(self): self.server = TSC.Server("http://test") - self.server.version = '3.8' + self.server.version = "3.8" # Fake Signin self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" @@ -45,8 +45,8 @@ def test_get_tasks_with_workbook(self): all_tasks, pagination_item = self.server.tasks.get() task = all_tasks[0] - self.assertEqual('c7a9327e-1cda-4504-b026-ddb43b976d1d', task.target.id) - self.assertEqual('workbook', task.target.type) + self.assertEqual("c7a9327e-1cda-4504-b026-ddb43b976d1d", task.target.id) + self.assertEqual("workbook", task.target.type) def test_get_tasks_with_datasource(self): with open(GET_XML_WITH_DATASOURCE, "rb") as f: @@ -56,8 +56,8 @@ def test_get_tasks_with_datasource(self): all_tasks, pagination_item = self.server.tasks.get() task = all_tasks[0] - self.assertEqual('c7a9327e-1cda-4504-b026-ddb43b976d1d', task.target.id) - self.assertEqual('datasource', task.target.type) + self.assertEqual("c7a9327e-1cda-4504-b026-ddb43b976d1d", task.target.id) + self.assertEqual("datasource", task.target.type) def test_get_tasks_with_workbook_and_datasource(self): with open(GET_XML_WITH_WORKBOOK_AND_DATASOURCE, "rb") as f: @@ -66,9 +66,9 @@ def test_get_tasks_with_workbook_and_datasource(self): m.get(self.baseurl, text=response_xml) all_tasks, pagination_item = self.server.tasks.get() - self.assertEqual('workbook', all_tasks[0].target.type) - self.assertEqual('datasource', all_tasks[1].target.type) - self.assertEqual('workbook', all_tasks[2].target.type) + self.assertEqual("workbook", all_tasks[0].target.type) + self.assertEqual("datasource", all_tasks[1].target.type) + self.assertEqual("workbook", all_tasks[2].target.type) def test_get_task_with_schedule(self): with open(GET_XML_WITH_WORKBOOK, "rb") as f: @@ -78,63 +78,64 @@ def test_get_task_with_schedule(self): all_tasks, pagination_item = self.server.tasks.get() task = all_tasks[0] - self.assertEqual('c7a9327e-1cda-4504-b026-ddb43b976d1d', task.target.id) - self.assertEqual('workbook', task.target.type) - self.assertEqual('b60b4efd-a6f7-4599-beb3-cb677e7abac1', task.schedule_id) + self.assertEqual("c7a9327e-1cda-4504-b026-ddb43b976d1d", task.target.id) + self.assertEqual("workbook", task.target.type) + self.assertEqual("b60b4efd-a6f7-4599-beb3-cb677e7abac1", task.schedule_id) def test_delete(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/c7a9327e-1cda-4504-b026-ddb43b976d1d', status_code=204) - self.server.tasks.delete('c7a9327e-1cda-4504-b026-ddb43b976d1d') + m.delete(self.baseurl + "/c7a9327e-1cda-4504-b026-ddb43b976d1d", status_code=204) + self.server.tasks.delete("c7a9327e-1cda-4504-b026-ddb43b976d1d") def test_delete_missing_id(self): - self.assertRaises(ValueError, self.server.tasks.delete, '') + self.assertRaises(ValueError, self.server.tasks.delete, "") def test_get_materializeviews_tasks(self): with open(GET_XML_DATAACCELERATION_TASK, "rb") as f: response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get('{}/{}'.format( - self.server.tasks.baseurl, TaskItem.Type.DataAcceleration), text=response_xml) + m.get("{}/{}".format(self.server.tasks.baseurl, TaskItem.Type.DataAcceleration), text=response_xml) all_tasks, pagination_item = self.server.tasks.get(task_type=TaskItem.Type.DataAcceleration) task = all_tasks[0] - self.assertEqual('a462c148-fc40-4670-a8e4-39b7f0c58c7f', task.target.id) - self.assertEqual('workbook', task.target.type) - self.assertEqual('b22190b4-6ac2-4eed-9563-4afc03444413', task.schedule_id) - self.assertEqual(parse_datetime('2019-12-09T22:30:00Z'), task.schedule_item.next_run_at) - self.assertEqual(parse_datetime('2019-12-09T20:45:04Z'), task.last_run_at) + self.assertEqual("a462c148-fc40-4670-a8e4-39b7f0c58c7f", task.target.id) + self.assertEqual("workbook", task.target.type) + self.assertEqual("b22190b4-6ac2-4eed-9563-4afc03444413", task.schedule_id) + self.assertEqual(parse_datetime("2019-12-09T22:30:00Z"), task.schedule_item.next_run_at) + self.assertEqual(parse_datetime("2019-12-09T20:45:04Z"), task.last_run_at) self.assertEqual(TSC.TaskItem.Type.DataAcceleration, task.task_type) def test_delete_data_acceleration(self): with requests_mock.mock() as m: - m.delete('{}/{}/{}'.format( - self.server.tasks.baseurl, TaskItem.Type.DataAcceleration, - 'c9cff7f9-309c-4361-99ff-d4ba8c9f5467'), status_code=204) - self.server.tasks.delete('c9cff7f9-309c-4361-99ff-d4ba8c9f5467', - TaskItem.Type.DataAcceleration) + m.delete( + "{}/{}/{}".format( + self.server.tasks.baseurl, TaskItem.Type.DataAcceleration, "c9cff7f9-309c-4361-99ff-d4ba8c9f5467" + ), + status_code=204, + ) + self.server.tasks.delete("c9cff7f9-309c-4361-99ff-d4ba8c9f5467", TaskItem.Type.DataAcceleration) def test_get_by_id(self): with open(GET_XML_WITH_WORKBOOK, "rb") as f: response_xml = f.read().decode("utf-8") - task_id = 'f84901ac-72ad-4f9b-a87e-7a3500402ad6' + task_id = "f84901ac-72ad-4f9b-a87e-7a3500402ad6" with requests_mock.mock() as m: - m.get('{}/{}'.format(self.baseurl, task_id), text=response_xml) + m.get("{}/{}".format(self.baseurl, task_id), text=response_xml) task = self.server.tasks.get_by_id(task_id) - self.assertEqual('c7a9327e-1cda-4504-b026-ddb43b976d1d', task.target.id) - self.assertEqual('workbook', task.target.type) - self.assertEqual('b60b4efd-a6f7-4599-beb3-cb677e7abac1', task.schedule_id) + self.assertEqual("c7a9327e-1cda-4504-b026-ddb43b976d1d", task.target.id) + self.assertEqual("workbook", task.target.type) + self.assertEqual("b60b4efd-a6f7-4599-beb3-cb677e7abac1", task.schedule_id) self.assertEqual(TSC.TaskItem.Type.ExtractRefresh, task.task_type) def test_run_now(self): - task_id = 'f84901ac-72ad-4f9b-a87e-7a3500402ad6' + task_id = "f84901ac-72ad-4f9b-a87e-7a3500402ad6" task = TaskItem(task_id, TaskItem.Type.ExtractRefresh, 100) with open(GET_XML_RUN_NOW_RESPONSE, "rb") as f: response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post('{}/{}/runNow'.format(self.baseurl, task_id), text=response_xml) + m.post("{}/{}/runNow".format(self.baseurl, task_id), text=response_xml) job_response_content = self.server.tasks.run(task).decode("utf-8") - self.assertTrue('7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6' in job_response_content) - self.assertTrue('RefreshExtract' in job_response_content) + self.assertTrue("7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6" in job_response_content) + self.assertTrue("RefreshExtract" in job_response_content) diff --git a/test/test_user.py b/test/test_user.py index e4d1d6717..90299c317 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -4,31 +4,31 @@ import tableauserverclient as TSC from tableauserverclient.datetime_helpers import format_datetime -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -GET_XML = os.path.join(TEST_ASSET_DIR, 'user_get.xml') -GET_EMPTY_XML = os.path.join(TEST_ASSET_DIR, 'user_get_empty.xml') -GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, 'user_get_by_id.xml') -UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'user_update.xml') -ADD_XML = os.path.join(TEST_ASSET_DIR, 'user_add.xml') -POPULATE_WORKBOOKS_XML = os.path.join(TEST_ASSET_DIR, 'user_populate_workbooks.xml') -GET_FAVORITES_XML = os.path.join(TEST_ASSET_DIR, 'favorites_get.xml') -POPULATE_GROUPS_XML = os.path.join(TEST_ASSET_DIR, 'user_populate_groups.xml') +GET_XML = os.path.join(TEST_ASSET_DIR, "user_get.xml") +GET_EMPTY_XML = os.path.join(TEST_ASSET_DIR, "user_get_empty.xml") +GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "user_get_by_id.xml") +UPDATE_XML = os.path.join(TEST_ASSET_DIR, "user_update.xml") +ADD_XML = os.path.join(TEST_ASSET_DIR, "user_add.xml") +POPULATE_WORKBOOKS_XML = os.path.join(TEST_ASSET_DIR, "user_populate_workbooks.xml") +GET_FAVORITES_XML = os.path.join(TEST_ASSET_DIR, "favorites_get.xml") +POPULATE_GROUPS_XML = os.path.join(TEST_ASSET_DIR, "user_populate_groups.xml") class UserTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.users.baseurl def test_get(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl + "?fields=_all_", text=response_xml) all_users, pagination_item = self.server.users.get() @@ -36,24 +36,24 @@ def test_get(self): self.assertEqual(2, pagination_item.total_available) self.assertEqual(2, len(all_users)) - self.assertTrue(any(user.id == 'dd2239f6-ddf1-4107-981a-4cf94e415794' for user in all_users)) - single_user = next(user for user in all_users if user.id == 'dd2239f6-ddf1-4107-981a-4cf94e415794') - self.assertEqual('alice', single_user.name) - self.assertEqual('Publisher', single_user.site_role) - self.assertEqual('2016-08-16T23:17:06Z', format_datetime(single_user.last_login)) - self.assertEqual('alice cook', single_user.fullname) - self.assertEqual('alicecook@test.com', single_user.email) - - self.assertTrue(any(user.id == '2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3' for user in all_users)) - single_user = next(user for user in all_users if user.id == '2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3') - self.assertEqual('Bob', single_user.name) - self.assertEqual('Interactor', single_user.site_role) - self.assertEqual('Bob Smith', single_user.fullname) - self.assertEqual('bob@test.com', single_user.email) + self.assertTrue(any(user.id == "dd2239f6-ddf1-4107-981a-4cf94e415794" for user in all_users)) + single_user = next(user for user in all_users if user.id == "dd2239f6-ddf1-4107-981a-4cf94e415794") + self.assertEqual("alice", single_user.name) + self.assertEqual("Publisher", single_user.site_role) + self.assertEqual("2016-08-16T23:17:06Z", format_datetime(single_user.last_login)) + self.assertEqual("alice cook", single_user.fullname) + self.assertEqual("alicecook@test.com", single_user.email) + + self.assertTrue(any(user.id == "2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3" for user in all_users)) + single_user = next(user for user in all_users if user.id == "2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3") + self.assertEqual("Bob", single_user.name) + self.assertEqual("Interactor", single_user.site_role) + self.assertEqual("Bob Smith", single_user.fullname) + self.assertEqual("bob@test.com", single_user.email) def test_get_empty(self): - with open(GET_EMPTY_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_EMPTY_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_users, pagination_item = self.server.users.get() @@ -66,139 +66,137 @@ def test_get_before_signin(self): self.assertRaises(TSC.NotSignedInError, self.server.users.get) def test_get_by_id(self): - with open(GET_BY_ID_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_BY_ID_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/dd2239f6-ddf1-4107-981a-4cf94e415794', text=response_xml) - single_user = self.server.users.get_by_id('dd2239f6-ddf1-4107-981a-4cf94e415794') + m.get(self.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794", text=response_xml) + single_user = self.server.users.get_by_id("dd2239f6-ddf1-4107-981a-4cf94e415794") - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', single_user.id) - self.assertEqual('alice', single_user.name) - self.assertEqual('Alice', single_user.fullname) - self.assertEqual('Publisher', single_user.site_role) - self.assertEqual('ServerDefault', single_user.auth_setting) - self.assertEqual('2016-08-16T23:17:06Z', format_datetime(single_user.last_login)) - self.assertEqual('local', single_user.domain_name) + self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", single_user.id) + self.assertEqual("alice", single_user.name) + self.assertEqual("Alice", single_user.fullname) + self.assertEqual("Publisher", single_user.site_role) + self.assertEqual("ServerDefault", single_user.auth_setting) + self.assertEqual("2016-08-16T23:17:06Z", format_datetime(single_user.last_login)) + self.assertEqual("local", single_user.domain_name) def test_get_by_id_missing_id(self): - self.assertRaises(ValueError, self.server.users.get_by_id, '') + self.assertRaises(ValueError, self.server.users.get_by_id, "") def test_update(self): - with open(UPDATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(UPDATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.put(self.baseurl + '/dd2239f6-ddf1-4107-981a-4cf94e415794', text=response_xml) - single_user = TSC.UserItem('test', 'Viewer') - single_user._id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' - single_user.name = 'Cassie' - single_user.fullname = 'Cassie' - single_user.email = 'cassie@email.com' + m.put(self.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794", text=response_xml) + single_user = TSC.UserItem("test", "Viewer") + single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794" + single_user.name = "Cassie" + single_user.fullname = "Cassie" + single_user.email = "cassie@email.com" single_user = self.server.users.update(single_user) - self.assertEqual('Cassie', single_user.name) - self.assertEqual('Cassie', single_user.fullname) - self.assertEqual('cassie@email.com', single_user.email) - self.assertEqual('Viewer', single_user.site_role) + self.assertEqual("Cassie", single_user.name) + self.assertEqual("Cassie", single_user.fullname) + self.assertEqual("cassie@email.com", single_user.email) + self.assertEqual("Viewer", single_user.site_role) def test_update_missing_id(self): - single_user = TSC.UserItem('test', 'Interactor') + single_user = TSC.UserItem("test", "Interactor") self.assertRaises(TSC.MissingRequiredFieldError, self.server.users.update, single_user) def test_remove(self): with requests_mock.mock() as m: - m.delete(self.baseurl + '/dd2239f6-ddf1-4107-981a-4cf94e415794', status_code=204) - self.server.users.remove('dd2239f6-ddf1-4107-981a-4cf94e415794') + m.delete(self.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794", status_code=204) + self.server.users.remove("dd2239f6-ddf1-4107-981a-4cf94e415794") def test_remove_missing_id(self): - self.assertRaises(ValueError, self.server.users.remove, '') + self.assertRaises(ValueError, self.server.users.remove, "") def test_add(self): - with open(ADD_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(ADD_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '', text=response_xml) - new_user = TSC.UserItem(name='Cassie', site_role='Viewer', auth_setting='ServerDefault') + m.post(self.baseurl + "", text=response_xml) + new_user = TSC.UserItem(name="Cassie", site_role="Viewer", auth_setting="ServerDefault") new_user = self.server.users.add(new_user) - self.assertEqual('4cc4c17f-898a-4de4-abed-a1681c673ced', new_user.id) - self.assertEqual('Cassie', new_user.name) - self.assertEqual('Viewer', new_user.site_role) - self.assertEqual('ServerDefault', new_user.auth_setting) + self.assertEqual("4cc4c17f-898a-4de4-abed-a1681c673ced", new_user.id) + self.assertEqual("Cassie", new_user.name) + self.assertEqual("Viewer", new_user.site_role) + self.assertEqual("ServerDefault", new_user.auth_setting) def test_populate_workbooks(self): - with open(POPULATE_WORKBOOKS_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_WORKBOOKS_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/dd2239f6-ddf1-4107-981a-4cf94e415794/workbooks', - text=response_xml) - single_user = TSC.UserItem('test', 'Interactor') - single_user._id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' + m.get(self.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794/workbooks", text=response_xml) + single_user = TSC.UserItem("test", "Interactor") + single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794" self.server.users.populate_workbooks(single_user) workbook_list = list(single_user.workbooks) - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', workbook_list[0].id) - self.assertEqual('SafariSample', workbook_list[0].name) - self.assertEqual('SafariSample', workbook_list[0].content_url) + self.assertEqual("3cc6cd06-89ce-4fdc-b935-5294135d6d42", workbook_list[0].id) + self.assertEqual("SafariSample", workbook_list[0].name) + self.assertEqual("SafariSample", workbook_list[0].content_url) self.assertEqual(False, workbook_list[0].show_tabs) self.assertEqual(26, workbook_list[0].size) - self.assertEqual('2016-07-26T20:34:56Z', format_datetime(workbook_list[0].created_at)) - self.assertEqual('2016-07-26T20:35:05Z', format_datetime(workbook_list[0].updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', workbook_list[0].project_id) - self.assertEqual('default', workbook_list[0].project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', workbook_list[0].owner_id) - self.assertEqual(set(['Safari', 'Sample']), workbook_list[0].tags) + self.assertEqual("2016-07-26T20:34:56Z", format_datetime(workbook_list[0].created_at)) + self.assertEqual("2016-07-26T20:35:05Z", format_datetime(workbook_list[0].updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", workbook_list[0].project_id) + self.assertEqual("default", workbook_list[0].project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", workbook_list[0].owner_id) + self.assertEqual(set(["Safari", "Sample"]), workbook_list[0].tags) def test_populate_workbooks_missing_id(self): - single_user = TSC.UserItem('test', 'Interactor') + single_user = TSC.UserItem("test", "Interactor") self.assertRaises(TSC.MissingRequiredFieldError, self.server.users.populate_workbooks, single_user) def test_populate_favorites(self): - self.server.version = '2.5' + self.server.version = "2.5" baseurl = self.server.favorites.baseurl - single_user = TSC.UserItem('test', 'Interactor') - with open(GET_FAVORITES_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + single_user = TSC.UserItem("test", "Interactor") + with open(GET_FAVORITES_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get('{0}/{1}'.format(baseurl, single_user.id), text=response_xml) + m.get("{0}/{1}".format(baseurl, single_user.id), text=response_xml) self.server.users.populate_favorites(single_user) self.assertIsNotNone(single_user._favorites) - self.assertEqual(len(single_user.favorites['workbooks']), 1) - self.assertEqual(len(single_user.favorites['views']), 1) - self.assertEqual(len(single_user.favorites['projects']), 1) - self.assertEqual(len(single_user.favorites['datasources']), 1) + self.assertEqual(len(single_user.favorites["workbooks"]), 1) + self.assertEqual(len(single_user.favorites["views"]), 1) + self.assertEqual(len(single_user.favorites["projects"]), 1) + self.assertEqual(len(single_user.favorites["datasources"]), 1) - workbook = single_user.favorites['workbooks'][0] - view = single_user.favorites['views'][0] - datasource = single_user.favorites['datasources'][0] - project = single_user.favorites['projects'][0] + workbook = single_user.favorites["workbooks"][0] + view = single_user.favorites["views"][0] + datasource = single_user.favorites["datasources"][0] + project = single_user.favorites["projects"][0] - self.assertEqual(workbook.id, '6d13b0ca-043d-4d42-8c9d-3f3313ea3a00') - self.assertEqual(view.id, 'd79634e1-6063-4ec9-95ff-50acbf609ff5') - self.assertEqual(datasource.id, 'e76a1461-3b1d-4588-bf1b-17551a879ad9') - self.assertEqual(project.id, '1d0304cd-3796-429f-b815-7258370b9b74') + self.assertEqual(workbook.id, "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00") + self.assertEqual(view.id, "d79634e1-6063-4ec9-95ff-50acbf609ff5") + self.assertEqual(datasource.id, "e76a1461-3b1d-4588-bf1b-17551a879ad9") + self.assertEqual(project.id, "1d0304cd-3796-429f-b815-7258370b9b74") def test_populate_groups(self): - self.server.version = '3.7' - with open(POPULATE_GROUPS_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + self.server.version = "3.7" + with open(POPULATE_GROUPS_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.server.users.baseurl + '/dd2239f6-ddf1-4107-981a-4cf94e415794/groups', - text=response_xml) - single_user = TSC.UserItem('test', 'Interactor') - single_user._id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' + m.get(self.server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794/groups", text=response_xml) + single_user = TSC.UserItem("test", "Interactor") + single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794" self.server.users.populate_groups(single_user) group_list = list(single_user.groups) self.assertEqual(3, len(group_list)) - self.assertEqual('ef8b19c0-43b6-11e6-af50-63f5805dbe3c', group_list[0].id) - self.assertEqual('All Users', group_list[0].name) - self.assertEqual('local', group_list[0].domain_name) + self.assertEqual("ef8b19c0-43b6-11e6-af50-63f5805dbe3c", group_list[0].id) + self.assertEqual("All Users", group_list[0].name) + self.assertEqual("local", group_list[0].domain_name) - self.assertEqual('e7833b48-c6f7-47b5-a2a7-36e7dd232758', group_list[1].id) - self.assertEqual('Another group', group_list[1].name) - self.assertEqual('local', group_list[1].domain_name) + self.assertEqual("e7833b48-c6f7-47b5-a2a7-36e7dd232758", group_list[1].id) + self.assertEqual("Another group", group_list[1].name) + self.assertEqual("local", group_list[1].domain_name) - self.assertEqual('86a66d40-f289-472a-83d0-927b0f954dc8', group_list[2].id) - self.assertEqual('TableauExample', group_list[2].name) - self.assertEqual('local', group_list[2].domain_name) + self.assertEqual("86a66d40-f289-472a-83d0-927b0f954dc8", group_list[2].id) + self.assertEqual("TableauExample", group_list[2].name) + self.assertEqual("local", group_list[2].domain_name) diff --git a/test/test_view.py b/test/test_view.py index e32971ea2..ddb3871c4 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -6,109 +6,110 @@ from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient import UserItem, GroupItem, PermissionsRule -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -ADD_TAGS_XML = os.path.join(TEST_ASSET_DIR, 'view_add_tags.xml') -GET_XML = os.path.join(TEST_ASSET_DIR, 'view_get.xml') -GET_XML_ID = os.path.join(TEST_ASSET_DIR, 'view_get_id.xml') -GET_XML_USAGE = os.path.join(TEST_ASSET_DIR, 'view_get_usage.xml') -POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, 'Sample View Image.png') -POPULATE_PDF = os.path.join(TEST_ASSET_DIR, 'populate_pdf.pdf') -POPULATE_CSV = os.path.join(TEST_ASSET_DIR, 'populate_csv.csv') -POPULATE_PERMISSIONS_XML = os.path.join(TEST_ASSET_DIR, 'view_populate_permissions.xml') -UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, 'view_update_permissions.xml') -UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_update.xml') +ADD_TAGS_XML = os.path.join(TEST_ASSET_DIR, "view_add_tags.xml") +GET_XML = os.path.join(TEST_ASSET_DIR, "view_get.xml") +GET_XML_ID = os.path.join(TEST_ASSET_DIR, "view_get_id.xml") +GET_XML_USAGE = os.path.join(TEST_ASSET_DIR, "view_get_usage.xml") +POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, "Sample View Image.png") +POPULATE_PDF = os.path.join(TEST_ASSET_DIR, "populate_pdf.pdf") +POPULATE_CSV = os.path.join(TEST_ASSET_DIR, "populate_csv.csv") +POPULATE_PERMISSIONS_XML = os.path.join(TEST_ASSET_DIR, "view_populate_permissions.xml") +UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, "view_update_permissions.xml") +UPDATE_XML = os.path.join(TEST_ASSET_DIR, "workbook_update.xml") class ViewTests(unittest.TestCase): def setUp(self): - self.server = TSC.Server('http://test') - self.server.version = '3.2' + self.server = TSC.Server("http://test") + self.server.version = "3.2" # Fake sign in - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.views.baseurl self.siteurl = self.server.views.siteurl def test_get(self): - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_views, pagination_item = self.server.views.get() self.assertEqual(2, pagination_item.total_available) - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', all_views[0].id) - self.assertEqual('ENDANGERED SAFARI', all_views[0].name) - self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', all_views[0].content_url) - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_views[0].workbook_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[0].owner_id) - self.assertEqual('5241e88d-d384-4fd7-9c2f-648b5247efc5', all_views[0].project_id) - self.assertEqual(set(['tag1', 'tag2']), all_views[0].tags) + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", all_views[0].id) + self.assertEqual("ENDANGERED SAFARI", all_views[0].name) + self.assertEqual("SafariSample/sheets/ENDANGEREDSAFARI", all_views[0].content_url) + self.assertEqual("3cc6cd06-89ce-4fdc-b935-5294135d6d42", all_views[0].workbook_id) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_views[0].owner_id) + self.assertEqual("5241e88d-d384-4fd7-9c2f-648b5247efc5", all_views[0].project_id) + self.assertEqual(set(["tag1", "tag2"]), all_views[0].tags) self.assertIsNone(all_views[0].created_at) self.assertIsNone(all_views[0].updated_at) self.assertIsNone(all_views[0].sheet_type) - self.assertEqual('fd252f73-593c-4c4e-8584-c032b8022adc', all_views[1].id) - self.assertEqual('Overview', all_views[1].name) - self.assertEqual('Superstore/sheets/Overview', all_views[1].content_url) - self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_views[1].workbook_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[1].owner_id) - self.assertEqual('5b534f74-3226-11e8-b47a-cb2e00f738a3', all_views[1].project_id) - self.assertEqual('2002-05-30T09:00:00Z', format_datetime(all_views[1].created_at)) - self.assertEqual('2002-06-05T08:00:59Z', format_datetime(all_views[1].updated_at)) - self.assertEqual('story', all_views[1].sheet_type) + self.assertEqual("fd252f73-593c-4c4e-8584-c032b8022adc", all_views[1].id) + self.assertEqual("Overview", all_views[1].name) + self.assertEqual("Superstore/sheets/Overview", all_views[1].content_url) + self.assertEqual("6d13b0ca-043d-4d42-8c9d-3f3313ea3a00", all_views[1].workbook_id) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_views[1].owner_id) + self.assertEqual("5b534f74-3226-11e8-b47a-cb2e00f738a3", all_views[1].project_id) + self.assertEqual("2002-05-30T09:00:00Z", format_datetime(all_views[1].created_at)) + self.assertEqual("2002-06-05T08:00:59Z", format_datetime(all_views[1].updated_at)) + self.assertEqual("story", all_views[1].sheet_type) def test_get_by_id(self): - with open(GET_XML_ID, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML_ID, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5', text=response_xml) - view = self.server.views.get_by_id('d79634e1-6063-4ec9-95ff-50acbf609ff5') - - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', view.id) - self.assertEqual('ENDANGERED SAFARI', view.name) - self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', view.content_url) - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', view.workbook_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', view.owner_id) - self.assertEqual('5241e88d-d384-4fd7-9c2f-648b5247efc5', view.project_id) - self.assertEqual(set(['tag1', 'tag2']), view.tags) - self.assertEqual('2002-05-30T09:00:00Z', format_datetime(view.created_at)) - self.assertEqual('2002-06-05T08:00:59Z', format_datetime(view.updated_at)) - self.assertEqual('story', view.sheet_type) + m.get(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5", text=response_xml) + view = self.server.views.get_by_id("d79634e1-6063-4ec9-95ff-50acbf609ff5") + + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", view.id) + self.assertEqual("ENDANGERED SAFARI", view.name) + self.assertEqual("SafariSample/sheets/ENDANGEREDSAFARI", view.content_url) + self.assertEqual("3cc6cd06-89ce-4fdc-b935-5294135d6d42", view.workbook_id) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", view.owner_id) + self.assertEqual("5241e88d-d384-4fd7-9c2f-648b5247efc5", view.project_id) + self.assertEqual(set(["tag1", "tag2"]), view.tags) + self.assertEqual("2002-05-30T09:00:00Z", format_datetime(view.created_at)) + self.assertEqual("2002-06-05T08:00:59Z", format_datetime(view.updated_at)) + self.assertEqual("story", view.sheet_type) def test_get_by_id_missing_id(self): self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.get_by_id, None) def test_get_with_usage(self): - with open(GET_XML_USAGE, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML_USAGE, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl + "?includeUsageStatistics=true", text=response_xml) all_views, pagination_item = self.server.views.get(usage=True) - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', all_views[0].id) + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", all_views[0].id) self.assertEqual(7, all_views[0].total_views) self.assertIsNone(all_views[0].created_at) self.assertIsNone(all_views[0].updated_at) self.assertIsNone(all_views[0].sheet_type) - self.assertEqual('fd252f73-593c-4c4e-8584-c032b8022adc', all_views[1].id) + self.assertEqual("fd252f73-593c-4c4e-8584-c032b8022adc", all_views[1].id) self.assertEqual(13, all_views[1].total_views) - self.assertEqual('2002-05-30T09:00:00Z', format_datetime(all_views[1].created_at)) - self.assertEqual('2002-06-05T08:00:59Z', format_datetime(all_views[1].updated_at)) - self.assertEqual('story', all_views[1].sheet_type) + self.assertEqual("2002-05-30T09:00:00Z", format_datetime(all_views[1].created_at)) + self.assertEqual("2002-06-05T08:00:59Z", format_datetime(all_views[1].updated_at)) + self.assertEqual("story", all_views[1].sheet_type) def test_get_with_usage_and_filter(self): - with open(GET_XML_USAGE, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML_USAGE, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl + "?includeUsageStatistics=true&filter=name:in:[foo,bar]", text=response_xml) options = TSC.RequestOptions() - options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In, - ["foo", "bar"])) + options.filter.add( + TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In, ["foo", "bar"]) + ) all_views, pagination_item = self.server.views.get(req_options=options, usage=True) self.assertEqual("ENDANGERED SAFARI", all_views[0].name) @@ -121,56 +122,62 @@ def test_get_before_signin(self): self.assertRaises(TSC.NotSignedInError, self.server.views.get) def test_populate_preview_image(self): - with open(POPULATE_PREVIEW_IMAGE, 'rb') as f: + with open(POPULATE_PREVIEW_IMAGE, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.siteurl + '/workbooks/3cc6cd06-89ce-4fdc-b935-5294135d6d42/' - 'views/d79634e1-6063-4ec9-95ff-50acbf609ff5/previewImage', content=response) + m.get( + self.siteurl + "/workbooks/3cc6cd06-89ce-4fdc-b935-5294135d6d42/" + "views/d79634e1-6063-4ec9-95ff-50acbf609ff5/previewImage", + content=response, + ) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' - single_view._workbook_id = '3cc6cd06-89ce-4fdc-b935-5294135d6d42' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" + single_view._workbook_id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42" self.server.views.populate_preview_image(single_view) self.assertEqual(response, single_view.preview_image) def test_populate_preview_image_missing_id(self): single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_preview_image, single_view) single_view._id = None - single_view._workbook_id = '3cc6cd06-89ce-4fdc-b935-5294135d6d42' + single_view._workbook_id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42" self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_preview_image, single_view) def test_populate_image(self): - with open(POPULATE_PREVIEW_IMAGE, 'rb') as f: + with open(POPULATE_PREVIEW_IMAGE, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/image', content=response) + m.get(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image", content=response) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" self.server.views.populate_image(single_view) self.assertEqual(response, single_view.image) def test_populate_image_with_options(self): - with open(POPULATE_PREVIEW_IMAGE, 'rb') as f: + with open(POPULATE_PREVIEW_IMAGE, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?resolution=high&maxAge=10', - content=response) + m.get( + self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?resolution=high&maxAge=10", content=response + ) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High, maxage=10) self.server.views.populate_image(single_view, req_option) self.assertEqual(response, single_view.image) def test_populate_pdf(self): - with open(POPULATE_PDF, 'rb') as f: + with open(POPULATE_PDF, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?type=letter&orientation=portrait&maxAge=5', - content=response) + m.get( + self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?type=letter&orientation=portrait&maxAge=5", + content=response, + ) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" size = TSC.PDFRequestOptions.PageType.Letter orientation = TSC.PDFRequestOptions.Orientation.Portrait @@ -180,12 +187,12 @@ def test_populate_pdf(self): self.assertEqual(response, single_view.pdf) def test_populate_csv(self): - with open(POPULATE_CSV, 'rb') as f: + with open(POPULATE_CSV, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/data?maxAge=1', content=response) + m.get(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/data?maxAge=1", content=response) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" request_option = TSC.CSVRequestOptions(maxage=1) self.server.views.populate_csv(single_view, request_option) @@ -193,12 +200,12 @@ def test_populate_csv(self): self.assertEqual(response, csv_file) def test_populate_csv_default_maxage(self): - with open(POPULATE_CSV, 'rb') as f: + with open(POPULATE_CSV, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/data', content=response) + m.get(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/data", content=response) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" self.server.views.populate_csv(single_view) csv_file = b"".join(single_view.csv) @@ -210,8 +217,8 @@ def test_populate_image_missing_id(self): self.assertRaises(TSC.MissingRequiredFieldError, self.server.views.populate_image, single_view) def test_populate_permissions(self): - with open(POPULATE_PERMISSIONS_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_PERMISSIONS_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl + "/e490bec4-2652-4fda-8c4e-f087db6fa328/permissions", text=response_xml) single_view = TSC.ViewItem() @@ -220,62 +227,57 @@ def test_populate_permissions(self): self.server.views.populate_permissions(single_view) permissions = single_view.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, 'c8f2773a-c83a-11e8-8c8f-33e6d787b506') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow, - - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "c8f2773a-c83a-11e8-8c8f-33e6d787b506") + self.assertDictEqual( + permissions[0].capabilities, + { + TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow, + }, + ) def test_add_permissions(self): - with open(UPDATE_PERMISSIONS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(UPDATE_PERMISSIONS, "rb") as f: + response_xml = f.read().decode("utf-8") single_view = TSC.ViewItem() - single_view._id = '21778de4-b7b9-44bc-a599-1506a2639ace' + single_view._id = "21778de4-b7b9-44bc-a599-1506a2639ace" bob = UserItem.as_reference("7c37ee24-c4b1-42b6-a154-eaeab7ee330a") group_of_people = GroupItem.as_reference("5e5e1978-71fa-11e4-87dd-7382f5c437af") - new_permissions = [ - PermissionsRule(bob, {'Write': 'Allow'}), - PermissionsRule(group_of_people, {'Read': 'Deny'}) - ] + new_permissions = [PermissionsRule(bob, {"Write": "Allow"}), PermissionsRule(group_of_people, {"Read": "Deny"})] with requests_mock.mock() as m: m.put(self.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml) permissions = self.server.views.update_permissions(single_view, new_permissions) - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "5e5e1978-71fa-11e4-87dd-7382f5c437af") + self.assertDictEqual(permissions[0].capabilities, {TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny}) - self.assertEqual(permissions[1].grantee.tag_name, 'user') - self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') - self.assertDictEqual(permissions[1].capabilities, { - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow - }) + self.assertEqual(permissions[1].grantee.tag_name, "user") + self.assertEqual(permissions[1].grantee.id, "7c37ee24-c4b1-42b6-a154-eaeab7ee330a") + self.assertDictEqual(permissions[1].capabilities, {TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow}) def test_update_tags(self): - with open(ADD_TAGS_XML, 'rb') as f: - add_tags_xml = f.read().decode('utf-8') - with open(UPDATE_XML, 'rb') as f: - update_xml = f.read().decode('utf-8') + with open(ADD_TAGS_XML, "rb") as f: + add_tags_xml = f.read().decode("utf-8") + with open(UPDATE_XML, "rb") as f: + update_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.put(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags', text=add_tags_xml) - m.delete(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags/b', status_code=204) - m.delete(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags/d', status_code=204) - m.put(self.baseurl + '/d79634e1-6063-4ec9-95ff-50acbf609ff5', text=update_xml) + m.put(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags", text=add_tags_xml) + m.delete(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags/b", status_code=204) + m.delete(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags/d", status_code=204) + m.put(self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5", text=update_xml) single_view = TSC.ViewItem() - single_view._id = 'd79634e1-6063-4ec9-95ff-50acbf609ff5' - single_view._initial_tags.update(['a', 'b', 'c', 'd']) - single_view.tags.update(['a', 'c', 'e']) + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" + single_view._initial_tags.update(["a", "b", "c", "d"]) + single_view.tags.update(["a", "c", "e"]) updated_view = self.server.views.update(single_view) self.assertEqual(single_view.tags, updated_view.tags) diff --git a/test/test_webhook.py b/test/test_webhook.py index 3b002314f..279ede04f 100644 --- a/test/test_webhook.py +++ b/test/test_webhook.py @@ -6,27 +6,27 @@ from ._utils import read_xml_asset, read_xml_assets, asset -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") -GET_XML = asset('webhook_get.xml') -CREATE_XML = asset('webhook_create.xml') -CREATE_REQUEST_XML = asset('webhook_create_request.xml') +GET_XML = asset("webhook_get.xml") +CREATE_XML = asset("webhook_create.xml") +CREATE_REQUEST_XML = asset("webhook_create_request.xml") class WebhookTests(unittest.TestCase): def setUp(self) -> None: - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") self.server.version = "3.6" # Fake signin - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.webhooks.baseurl def test_get(self) -> None: - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) webhooks, _ = self.server.webhooks.get() @@ -45,20 +45,20 @@ def test_get_before_signin(self) -> None: def test_delete(self) -> None: with requests_mock.mock() as m: - m.delete(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', status_code=204) - self.server.webhooks.delete('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + m.delete(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204) + self.server.webhooks.delete("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") def test_delete_missing_id(self) -> None: - self.assertRaises(ValueError, self.server.webhooks.delete, '') + self.assertRaises(ValueError, self.server.webhooks.delete, "") def test_test(self) -> None: with requests_mock.mock() as m: - m.get(self.baseurl + '/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760/test', status_code=200) - self.server.webhooks.test('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + m.get(self.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760/test", status_code=200) + self.server.webhooks.test("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") def test_create(self) -> None: - with open(CREATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(CREATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) webhook_model = TSC.WebhookItem() @@ -71,13 +71,12 @@ def test_create(self) -> None: self.assertNotEqual(new_webhook.id, None) def test_request_factory(self) -> None: - with open(CREATE_REQUEST_XML, 'rb') as f: - webhook_request_expected = f.read().decode('utf-8') + with open(CREATE_REQUEST_XML, "rb") as f: + webhook_request_expected = f.read().decode("utf-8") webhook_item = WebhookItem() - webhook_item._set_values("webhook-id", "webhook-name", "url", "api-event-name", - None) - webhook_request_actual = '{}\n'.format(RequestFactory.Webhook.create_req(webhook_item).decode('utf-8')) + webhook_item._set_values("webhook-id", "webhook-name", "url", "api-event-name", None) + webhook_request_actual = "{}\n".format(RequestFactory.Webhook.create_req(webhook_item).decode("utf-8")) self.maxDiff = None # windows does /r/n for linebreaks, remove the extra char if it is there - self.assertEqual(webhook_request_expected.replace('\r', ''), webhook_request_actual) + self.assertEqual(webhook_request_expected.replace("\r", ""), webhook_request_actual) diff --git a/test/test_workbook.py b/test/test_workbook.py index 5ace90b2f..cf6bce1f3 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -6,6 +6,7 @@ import tableauserverclient as TSC import xml.etree.ElementTree as ET from pathlib import Path +import tempfile from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient.server.endpoint.exceptions import InternalServerError @@ -16,88 +17,90 @@ from ._utils import asset -TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), 'assets') - -ADD_TAGS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_add_tags.xml') -GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, 'workbook_get_by_id.xml') -GET_BY_ID_XML_PERSONAL = os.path.join(TEST_ASSET_DIR, 'workbook_get_by_id_personal.xml') -GET_EMPTY_XML = os.path.join(TEST_ASSET_DIR, 'workbook_get_empty.xml') -GET_INVALID_DATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_get_invalid_date.xml') -GET_XML = os.path.join(TEST_ASSET_DIR, 'workbook_get.xml') -POPULATE_CONNECTIONS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_connections.xml') -POPULATE_PDF = os.path.join(TEST_ASSET_DIR, 'populate_pdf.pdf') -POPULATE_PERMISSIONS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_permissions.xml') -POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, 'RESTAPISample Image.png') -POPULATE_VIEWS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_views.xml') -POPULATE_VIEWS_USAGE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_views_usage.xml') -PUBLISH_XML = os.path.join(TEST_ASSET_DIR, 'workbook_publish.xml') -PUBLISH_ASYNC_XML = os.path.join(TEST_ASSET_DIR, 'workbook_publish_async.xml') -REFRESH_XML = os.path.join(TEST_ASSET_DIR, 'workbook_refresh.xml') -UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_update.xml') -UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, 'workbook_update_permissions.xml') + +TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") + +ADD_TAGS_XML = os.path.join(TEST_ASSET_DIR, "workbook_add_tags.xml") +GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_by_id.xml") +GET_BY_ID_XML_PERSONAL = os.path.join(TEST_ASSET_DIR, "workbook_get_by_id_personal.xml") +GET_EMPTY_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_empty.xml") +GET_INVALID_DATE_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_invalid_date.xml") +GET_XML = os.path.join(TEST_ASSET_DIR, "workbook_get.xml") +POPULATE_CONNECTIONS_XML = os.path.join(TEST_ASSET_DIR, "workbook_populate_connections.xml") +POPULATE_PDF = os.path.join(TEST_ASSET_DIR, "populate_pdf.pdf") +POPULATE_PERMISSIONS_XML = os.path.join(TEST_ASSET_DIR, "workbook_populate_permissions.xml") +POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, "RESTAPISample Image.png") +POPULATE_VIEWS_XML = os.path.join(TEST_ASSET_DIR, "workbook_populate_views.xml") +POPULATE_VIEWS_USAGE_XML = os.path.join(TEST_ASSET_DIR, "workbook_populate_views_usage.xml") +PUBLISH_XML = os.path.join(TEST_ASSET_DIR, "workbook_publish.xml") +PUBLISH_ASYNC_XML = os.path.join(TEST_ASSET_DIR, "workbook_publish_async.xml") +REFRESH_XML = os.path.join(TEST_ASSET_DIR, "workbook_refresh.xml") +REVISION_XML = os.path.join(TEST_ASSET_DIR, "workbook_revision.xml") +UPDATE_XML = os.path.join(TEST_ASSET_DIR, "workbook_update.xml") +UPDATE_PERMISSIONS = os.path.join(TEST_ASSET_DIR, "workbook_update_permissions.xml") class WorkbookTests(unittest.TestCase): def setUp(self) -> None: - self.server = TSC.Server('http://test') + self.server = TSC.Server("http://test") # Fake sign in - self.server._site_id = 'dad65087-b08b-4603-af4e-2887b8aafc67' - self.server._auth_token = 'j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM' + self.server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67" + self.server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM" self.baseurl = self.server.workbooks.baseurl def test_get(self) -> None: - with open(GET_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_workbooks, pagination_item = self.server.workbooks.get() self.assertEqual(2, pagination_item.total_available) - self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_workbooks[0].id) - self.assertEqual('Superstore', all_workbooks[0].name) - self.assertEqual('Superstore', all_workbooks[0].content_url) + self.assertEqual("6d13b0ca-043d-4d42-8c9d-3f3313ea3a00", all_workbooks[0].id) + self.assertEqual("Superstore", all_workbooks[0].name) + self.assertEqual("Superstore", all_workbooks[0].content_url) self.assertEqual(False, all_workbooks[0].show_tabs) - self.assertEqual('http://tableauserver/#/workbooks/1/views', all_workbooks[0].webpage_url) + self.assertEqual("http://tableauserver/#/workbooks/1/views", all_workbooks[0].webpage_url) self.assertEqual(1, all_workbooks[0].size) - self.assertEqual('2016-08-03T20:34:04Z', format_datetime(all_workbooks[0].created_at)) - self.assertEqual('description for Superstore', all_workbooks[0].description) - self.assertEqual('2016-08-04T17:56:41Z', format_datetime(all_workbooks[0].updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', all_workbooks[0].project_id) - self.assertEqual('default', all_workbooks[0].project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_workbooks[0].owner_id) - - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_workbooks[1].id) - self.assertEqual('SafariSample', all_workbooks[1].name) - self.assertEqual('SafariSample', all_workbooks[1].content_url) - self.assertEqual('http://tableauserver/#/workbooks/2/views', all_workbooks[1].webpage_url) + self.assertEqual("2016-08-03T20:34:04Z", format_datetime(all_workbooks[0].created_at)) + self.assertEqual("description for Superstore", all_workbooks[0].description) + self.assertEqual("2016-08-04T17:56:41Z", format_datetime(all_workbooks[0].updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", all_workbooks[0].project_id) + self.assertEqual("default", all_workbooks[0].project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_workbooks[0].owner_id) + + self.assertEqual("3cc6cd06-89ce-4fdc-b935-5294135d6d42", all_workbooks[1].id) + self.assertEqual("SafariSample", all_workbooks[1].name) + self.assertEqual("SafariSample", all_workbooks[1].content_url) + self.assertEqual("http://tableauserver/#/workbooks/2/views", all_workbooks[1].webpage_url) self.assertEqual(False, all_workbooks[1].show_tabs) self.assertEqual(26, all_workbooks[1].size) - self.assertEqual('2016-07-26T20:34:56Z', format_datetime(all_workbooks[1].created_at)) - self.assertEqual('description for SafariSample', all_workbooks[1].description) - self.assertEqual('2016-07-26T20:35:05Z', format_datetime(all_workbooks[1].updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', all_workbooks[1].project_id) - self.assertEqual('default', all_workbooks[1].project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_workbooks[1].owner_id) - self.assertEqual(set(['Safari', 'Sample']), all_workbooks[1].tags) + self.assertEqual("2016-07-26T20:34:56Z", format_datetime(all_workbooks[1].created_at)) + self.assertEqual("description for SafariSample", all_workbooks[1].description) + self.assertEqual("2016-07-26T20:35:05Z", format_datetime(all_workbooks[1].updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", all_workbooks[1].project_id) + self.assertEqual("default", all_workbooks[1].project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", all_workbooks[1].owner_id) + self.assertEqual(set(["Safari", "Sample"]), all_workbooks[1].tags) def test_get_ignore_invalid_date(self) -> None: - with open(GET_INVALID_DATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_INVALID_DATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_workbooks, pagination_item = self.server.workbooks.get() self.assertEqual(None, format_datetime(all_workbooks[0].created_at)) - self.assertEqual('2016-08-04T17:56:41Z', format_datetime(all_workbooks[0].updated_at)) + self.assertEqual("2016-08-04T17:56:41Z", format_datetime(all_workbooks[0].updated_at)) def test_get_before_signin(self) -> None: self.server._auth_token = None self.assertRaises(TSC.NotSignedInError, self.server.workbooks.get) def test_get_empty(self) -> None: - with open(GET_EMPTY_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_EMPTY_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.get(self.baseurl, text=response_xml) all_workbooks, pagination_item = self.server.workbooks.get() @@ -106,124 +109,124 @@ def test_get_empty(self) -> None: self.assertEqual([], all_workbooks) def test_get_by_id(self) -> None: - with open(GET_BY_ID_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_BY_ID_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42', text=response_xml) - single_workbook = self.server.workbooks.get_by_id('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.get(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42", text=response_xml) + single_workbook = self.server.workbooks.get_by_id("3cc6cd06-89ce-4fdc-b935-5294135d6d42") - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', single_workbook.id) - self.assertEqual('SafariSample', single_workbook.name) - self.assertEqual('SafariSample', single_workbook.content_url) - self.assertEqual('http://tableauserver/#/workbooks/2/views', single_workbook.webpage_url) + self.assertEqual("3cc6cd06-89ce-4fdc-b935-5294135d6d42", single_workbook.id) + self.assertEqual("SafariSample", single_workbook.name) + self.assertEqual("SafariSample", single_workbook.content_url) + self.assertEqual("http://tableauserver/#/workbooks/2/views", single_workbook.webpage_url) self.assertEqual(False, single_workbook.show_tabs) self.assertEqual(26, single_workbook.size) - self.assertEqual('2016-07-26T20:34:56Z', format_datetime(single_workbook.created_at)) - self.assertEqual('description for SafariSample', single_workbook.description) - self.assertEqual('2016-07-26T20:35:05Z', format_datetime(single_workbook.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', single_workbook.project_id) - self.assertEqual('default', single_workbook.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', single_workbook.owner_id) - self.assertEqual(set(['Safari', 'Sample']), single_workbook.tags) - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', single_workbook.views[0].id) - self.assertEqual('ENDANGERED SAFARI', single_workbook.views[0].name) - self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', single_workbook.views[0].content_url) + self.assertEqual("2016-07-26T20:34:56Z", format_datetime(single_workbook.created_at)) + self.assertEqual("description for SafariSample", single_workbook.description) + self.assertEqual("2016-07-26T20:35:05Z", format_datetime(single_workbook.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", single_workbook.project_id) + self.assertEqual("default", single_workbook.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", single_workbook.owner_id) + self.assertEqual(set(["Safari", "Sample"]), single_workbook.tags) + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", single_workbook.views[0].id) + self.assertEqual("ENDANGERED SAFARI", single_workbook.views[0].name) + self.assertEqual("SafariSample/sheets/ENDANGEREDSAFARI", single_workbook.views[0].content_url) def test_get_by_id_personal(self) -> None: # workbooks in personal space don't have project_id or project_name - with open(GET_BY_ID_XML_PERSONAL, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(GET_BY_ID_XML_PERSONAL, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d43', text=response_xml) - single_workbook = self.server.workbooks.get_by_id('3cc6cd06-89ce-4fdc-b935-5294135d6d43') + m.get(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d43", text=response_xml) + single_workbook = self.server.workbooks.get_by_id("3cc6cd06-89ce-4fdc-b935-5294135d6d43") - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d43', single_workbook.id) - self.assertEqual('SafariSample', single_workbook.name) - self.assertEqual('SafariSample', single_workbook.content_url) - self.assertEqual('http://tableauserver/#/workbooks/2/views', single_workbook.webpage_url) + self.assertEqual("3cc6cd06-89ce-4fdc-b935-5294135d6d43", single_workbook.id) + self.assertEqual("SafariSample", single_workbook.name) + self.assertEqual("SafariSample", single_workbook.content_url) + self.assertEqual("http://tableauserver/#/workbooks/2/views", single_workbook.webpage_url) self.assertEqual(False, single_workbook.show_tabs) self.assertEqual(26, single_workbook.size) - self.assertEqual('2016-07-26T20:34:56Z', format_datetime(single_workbook.created_at)) - self.assertEqual('description for SafariSample', single_workbook.description) - self.assertEqual('2016-07-26T20:35:05Z', format_datetime(single_workbook.updated_at)) + self.assertEqual("2016-07-26T20:34:56Z", format_datetime(single_workbook.created_at)) + self.assertEqual("description for SafariSample", single_workbook.description) + self.assertEqual("2016-07-26T20:35:05Z", format_datetime(single_workbook.updated_at)) self.assertTrue(single_workbook.project_id) self.assertIsNone(single_workbook.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', single_workbook.owner_id) - self.assertEqual(set(['Safari', 'Sample']), single_workbook.tags) - self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', single_workbook.views[0].id) - self.assertEqual('ENDANGERED SAFARI', single_workbook.views[0].name) - self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', single_workbook.views[0].content_url) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", single_workbook.owner_id) + self.assertEqual(set(["Safari", "Sample"]), single_workbook.tags) + self.assertEqual("d79634e1-6063-4ec9-95ff-50acbf609ff5", single_workbook.views[0].id) + self.assertEqual("ENDANGERED SAFARI", single_workbook.views[0].name) + self.assertEqual("SafariSample/sheets/ENDANGEREDSAFARI", single_workbook.views[0].content_url) def test_get_by_id_missing_id(self) -> None: - self.assertRaises(ValueError, self.server.workbooks.get_by_id, '') + self.assertRaises(ValueError, self.server.workbooks.get_by_id, "") def test_refresh_id(self) -> None: - self.server.version = '2.8' + self.server.version = "2.8" self.baseurl = self.server.workbooks.baseurl - with open(REFRESH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(REFRESH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh', - status_code=202, text=response_xml) - self.server.workbooks.refresh('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.post(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh", status_code=202, text=response_xml) + self.server.workbooks.refresh("3cc6cd06-89ce-4fdc-b935-5294135d6d42") def test_refresh_object(self) -> None: - self.server.version = '2.8' + self.server.version = "2.8" self.baseurl = self.server.workbooks.baseurl - workbook = TSC.WorkbookItem('') - workbook._id = '3cc6cd06-89ce-4fdc-b935-5294135d6d42' - with open(REFRESH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + workbook = TSC.WorkbookItem("") + workbook._id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42" + with open(REFRESH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh', - status_code=202, text=response_xml) + m.post(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh", status_code=202, text=response_xml) self.server.workbooks.refresh(workbook) def test_delete(self) -> None: with requests_mock.mock() as m: - m.delete(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42', status_code=204) - self.server.workbooks.delete('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.delete(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42", status_code=204) + self.server.workbooks.delete("3cc6cd06-89ce-4fdc-b935-5294135d6d42") def test_delete_missing_id(self) -> None: - self.assertRaises(ValueError, self.server.workbooks.delete, '') + self.assertRaises(ValueError, self.server.workbooks.delete, "") def test_update(self) -> None: - with open(UPDATE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') - with requests_mock.mock() as m: - m.put(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2', text=response_xml) - single_workbook = TSC.WorkbookItem('1d0304cd-3796-429f-b815-7258370b9b74', show_tabs=True) - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' - single_workbook.owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' - single_workbook.name = 'renamedWorkbook' - single_workbook.data_acceleration_config = {'acceleration_enabled': True, - 'accelerate_now': False, - 'last_updated_at': None, - 'acceleration_status': None} + with open(UPDATE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.put(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml) + single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True) + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" + single_workbook.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794" + single_workbook.name = "renamedWorkbook" + single_workbook.data_acceleration_config = { + "acceleration_enabled": True, + "accelerate_now": False, + "last_updated_at": None, + "acceleration_status": None, + } single_workbook = self.server.workbooks.update(single_workbook) - self.assertEqual('1f951daf-4061-451a-9df1-69a8062664f2', single_workbook.id) + self.assertEqual("1f951daf-4061-451a-9df1-69a8062664f2", single_workbook.id) self.assertEqual(True, single_workbook.show_tabs) - self.assertEqual('1d0304cd-3796-429f-b815-7258370b9b74', single_workbook.project_id) - self.assertEqual('dd2239f6-ddf1-4107-981a-4cf94e415794', single_workbook.owner_id) - self.assertEqual('renamedWorkbook', single_workbook.name) - self.assertEqual(True, single_workbook.data_acceleration_config['acceleration_enabled']) - self.assertEqual(False, single_workbook.data_acceleration_config['accelerate_now']) + self.assertEqual("1d0304cd-3796-429f-b815-7258370b9b74", single_workbook.project_id) + self.assertEqual("dd2239f6-ddf1-4107-981a-4cf94e415794", single_workbook.owner_id) + self.assertEqual("renamedWorkbook", single_workbook.name) + self.assertEqual(True, single_workbook.data_acceleration_config["acceleration_enabled"]) + self.assertEqual(False, single_workbook.data_acceleration_config["accelerate_now"]) def test_update_missing_id(self) -> None: - single_workbook = TSC.WorkbookItem('test') + single_workbook = TSC.WorkbookItem("test") self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.update, single_workbook) def test_update_copy_fields(self) -> None: - with open(POPULATE_CONNECTIONS_XML, 'rb') as f: - connection_xml = f.read().decode('utf-8') - with open(UPDATE_XML, 'rb') as f: - update_xml = f.read().decode('utf-8') - with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/connections', text=connection_xml) - m.put(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2', text=update_xml) - single_workbook = TSC.WorkbookItem('1d0304cd-3796-429f-b815-7258370b9b74') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + with open(POPULATE_CONNECTIONS_XML, "rb") as f: + connection_xml = f.read().decode("utf-8") + with open(UPDATE_XML, "rb") as f: + update_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.get(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/connections", text=connection_xml) + m.put(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=update_xml) + single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" self.server.workbooks.populate_connections(single_workbook) updated_workbook = self.server.workbooks.update(single_workbook) @@ -234,19 +237,19 @@ def test_update_copy_fields(self) -> None: self.assertEqual(single_workbook._preview_image, updated_workbook._preview_image) def test_update_tags(self) -> None: - with open(ADD_TAGS_XML, 'rb') as f: - add_tags_xml = f.read().decode('utf-8') - with open(UPDATE_XML, 'rb') as f: - update_xml = f.read().decode('utf-8') - with requests_mock.mock() as m: - m.put(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/tags', text=add_tags_xml) - m.delete(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/tags/b', status_code=204) - m.delete(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/tags/d', status_code=204) - m.put(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2', text=update_xml) - single_workbook = TSC.WorkbookItem('1d0304cd-3796-429f-b815-7258370b9b74') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' - single_workbook._initial_tags.update(['a', 'b', 'c', 'd']) - single_workbook.tags.update(['a', 'c', 'e']) + with open(ADD_TAGS_XML, "rb") as f: + add_tags_xml = f.read().decode("utf-8") + with open(UPDATE_XML, "rb") as f: + update_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.put(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/tags", text=add_tags_xml) + m.delete(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/tags/b", status_code=204) + m.delete(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/tags/d", status_code=204) + m.put(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=update_xml) + single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" + single_workbook._initial_tags.update(["a", "b", "c", "d"]) + single_workbook.tags.update(["a", "c", "e"]) updated_workbook = self.server.workbooks.update(single_workbook) self.assertEqual(single_workbook.tags, updated_workbook.tags) @@ -254,9 +257,11 @@ def test_update_tags(self) -> None: def test_download(self) -> None: with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/content', - headers={'Content-Disposition': 'name="tableau_workbook"; filename="RESTAPISample.twbx"'}) - file_path = self.server.workbooks.download('1f951daf-4061-451a-9df1-69a8062664f2') + m.get( + self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content", + headers={"Content-Disposition": 'name="tableau_workbook"; filename="RESTAPISample.twbx"'}, + ) + file_path = self.server.workbooks.download("1f951daf-4061-451a-9df1-69a8062664f2") self.assertTrue(os.path.exists(file_path)) os.remove(file_path) @@ -264,9 +269,11 @@ def test_download_sanitizes_name(self) -> None: filename = "Name,With,Commas.twbx" disposition = 'name="tableau_workbook"; filename="{}"'.format(filename) with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/content', - headers={'Content-Disposition': disposition}) - file_path = self.server.workbooks.download('1f951daf-4061-451a-9df1-69a8062664f2') + m.get( + self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content", + headers={"Content-Disposition": disposition}, + ) + file_path = self.server.workbooks.download("1f951daf-4061-451a-9df1-69a8062664f2") self.assertEqual(os.path.basename(file_path), "NameWithCommas.twbx") self.assertTrue(os.path.exists(file_path)) os.remove(file_path) @@ -277,140 +284,141 @@ def test_download_extract_only(self) -> None: self.baseurl = self.server.workbooks.baseurl with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/content?includeExtract=False', - headers={'Content-Disposition': 'name="tableau_workbook"; filename="RESTAPISample.twbx"'}, - complete_qs=True) + m.get( + self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content?includeExtract=False", + headers={"Content-Disposition": 'name="tableau_workbook"; filename="RESTAPISample.twbx"'}, + complete_qs=True, + ) # Technically this shouldn't download a twbx, but we are interested in the qs, not the file - file_path = self.server.workbooks.download('1f951daf-4061-451a-9df1-69a8062664f2', include_extract=False) + file_path = self.server.workbooks.download("1f951daf-4061-451a-9df1-69a8062664f2", include_extract=False) self.assertTrue(os.path.exists(file_path)) os.remove(file_path) def test_download_missing_id(self) -> None: - self.assertRaises(ValueError, self.server.workbooks.download, '') + self.assertRaises(ValueError, self.server.workbooks.download, "") def test_populate_views(self) -> None: - with open(POPULATE_VIEWS_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_VIEWS_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/views', text=response_xml) - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + m.get(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/views", text=response_xml) + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" self.server.workbooks.populate_views(single_workbook) views_list = single_workbook.views - self.assertEqual('097dbe13-de89-445f-b2c3-02f28bd010c1', views_list[0].id) - self.assertEqual('GDP per capita', views_list[0].name) - self.assertEqual('RESTAPISample/sheets/GDPpercapita', views_list[0].content_url) + self.assertEqual("097dbe13-de89-445f-b2c3-02f28bd010c1", views_list[0].id) + self.assertEqual("GDP per capita", views_list[0].name) + self.assertEqual("RESTAPISample/sheets/GDPpercapita", views_list[0].content_url) - self.assertEqual('2c1ab9d7-8d64-4cc6-b495-52e40c60c330', views_list[1].id) - self.assertEqual('Country ranks', views_list[1].name) - self.assertEqual('RESTAPISample/sheets/Countryranks', views_list[1].content_url) + self.assertEqual("2c1ab9d7-8d64-4cc6-b495-52e40c60c330", views_list[1].id) + self.assertEqual("Country ranks", views_list[1].name) + self.assertEqual("RESTAPISample/sheets/Countryranks", views_list[1].content_url) - self.assertEqual('0599c28c-6d82-457e-a453-e52c1bdb00f5', views_list[2].id) - self.assertEqual('Interest rates', views_list[2].name) - self.assertEqual('RESTAPISample/sheets/Interestrates', views_list[2].content_url) + self.assertEqual("0599c28c-6d82-457e-a453-e52c1bdb00f5", views_list[2].id) + self.assertEqual("Interest rates", views_list[2].name) + self.assertEqual("RESTAPISample/sheets/Interestrates", views_list[2].content_url) def test_populate_views_with_usage(self) -> None: - with open(POPULATE_VIEWS_USAGE_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') - with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/views?includeUsageStatistics=true', - text=response_xml) - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + with open(POPULATE_VIEWS_USAGE_XML, "rb") as f: + response_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.get( + self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/views?includeUsageStatistics=true", + text=response_xml, + ) + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" self.server.workbooks.populate_views(single_workbook, usage=True) views_list = single_workbook.views - self.assertEqual('097dbe13-de89-445f-b2c3-02f28bd010c1', views_list[0].id) + self.assertEqual("097dbe13-de89-445f-b2c3-02f28bd010c1", views_list[0].id) self.assertEqual(2, views_list[0].total_views) - self.assertEqual('2c1ab9d7-8d64-4cc6-b495-52e40c60c330', views_list[1].id) + self.assertEqual("2c1ab9d7-8d64-4cc6-b495-52e40c60c330", views_list[1].id) self.assertEqual(37, views_list[1].total_views) - self.assertEqual('0599c28c-6d82-457e-a453-e52c1bdb00f5', views_list[2].id) + self.assertEqual("0599c28c-6d82-457e-a453-e52c1bdb00f5", views_list[2].id) self.assertEqual(0, views_list[2].total_views) def test_populate_views_missing_id(self) -> None: - single_workbook = TSC.WorkbookItem('test') + single_workbook = TSC.WorkbookItem("test") self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_views, single_workbook) def test_populate_connections(self) -> None: - with open(POPULATE_CONNECTIONS_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_CONNECTIONS_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/connections', text=response_xml) - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + m.get(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/connections", text=response_xml) + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" self.server.workbooks.populate_connections(single_workbook) - self.assertEqual('37ca6ced-58d7-4dcf-99dc-f0a85223cbef', single_workbook.connections[0].id) - self.assertEqual('dataengine', single_workbook.connections[0].connection_type) - self.assertEqual('4506225a-0d32-4ab1-82d3-c24e85f7afba', single_workbook.connections[0].datasource_id) - self.assertEqual('World Indicators', single_workbook.connections[0].datasource_name) + self.assertEqual("37ca6ced-58d7-4dcf-99dc-f0a85223cbef", single_workbook.connections[0].id) + self.assertEqual("dataengine", single_workbook.connections[0].connection_type) + self.assertEqual("4506225a-0d32-4ab1-82d3-c24e85f7afba", single_workbook.connections[0].datasource_id) + self.assertEqual("World Indicators", single_workbook.connections[0].datasource_name) def test_populate_permissions(self) -> None: - with open(POPULATE_PERMISSIONS_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(POPULATE_PERMISSIONS_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.get(self.baseurl + '/21778de4-b7b9-44bc-a599-1506a2639ace/permissions', text=response_xml) - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '21778de4-b7b9-44bc-a599-1506a2639ace' + m.get(self.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml) + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "21778de4-b7b9-44bc-a599-1506a2639ace" self.server.workbooks.populate_permissions(single_workbook) permissions = single_workbook.permissions - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow - }) - - self.assertEqual(permissions[1].grantee.tag_name, 'user') - self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') - self.assertDictEqual(permissions[1].capabilities, { - TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow, - TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Deny, - TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Deny - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "5e5e1978-71fa-11e4-87dd-7382f5c437af") + self.assertDictEqual( + permissions[0].capabilities, + { + TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow, + }, + ) + + self.assertEqual(permissions[1].grantee.tag_name, "user") + self.assertEqual(permissions[1].grantee.id, "7c37ee24-c4b1-42b6-a154-eaeab7ee330a") + self.assertDictEqual( + permissions[1].capabilities, + { + TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow, + TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Deny, + TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Deny, + }, + ) def test_add_permissions(self) -> None: - with open(UPDATE_PERMISSIONS, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(UPDATE_PERMISSIONS, "rb") as f: + response_xml = f.read().decode("utf-8") - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '21778de4-b7b9-44bc-a599-1506a2639ace' + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "21778de4-b7b9-44bc-a599-1506a2639ace" bob = UserItem.as_reference("7c37ee24-c4b1-42b6-a154-eaeab7ee330a") group_of_people = GroupItem.as_reference("5e5e1978-71fa-11e4-87dd-7382f5c437af") - new_permissions = [ - PermissionsRule(bob, {'Write': 'Allow'}), - PermissionsRule(group_of_people, {'Read': 'Deny'}) - ] + new_permissions = [PermissionsRule(bob, {"Write": "Allow"}), PermissionsRule(group_of_people, {"Read": "Deny"})] with requests_mock.mock() as m: m.put(self.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml) permissions = self.server.workbooks.update_permissions(single_workbook, new_permissions) - self.assertEqual(permissions[0].grantee.tag_name, 'group') - self.assertEqual(permissions[0].grantee.id, '5e5e1978-71fa-11e4-87dd-7382f5c437af') - self.assertDictEqual(permissions[0].capabilities, { - TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny - }) + self.assertEqual(permissions[0].grantee.tag_name, "group") + self.assertEqual(permissions[0].grantee.id, "5e5e1978-71fa-11e4-87dd-7382f5c437af") + self.assertDictEqual(permissions[0].capabilities, {TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny}) - self.assertEqual(permissions[1].grantee.tag_name, 'user') - self.assertEqual(permissions[1].grantee.id, '7c37ee24-c4b1-42b6-a154-eaeab7ee330a') - self.assertDictEqual(permissions[1].capabilities, { - TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow - }) + self.assertEqual(permissions[1].grantee.tag_name, "user") + self.assertEqual(permissions[1].grantee.id, "7c37ee24-c4b1-42b6-a154-eaeab7ee330a") + self.assertDictEqual(permissions[1].capabilities, {TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow}) def test_populate_connections_missing_id(self) -> None: - single_workbook = TSC.WorkbookItem('test') - self.assertRaises(TSC.MissingRequiredFieldError, - self.server.workbooks.populate_connections, - single_workbook) + single_workbook = TSC.WorkbookItem("test") + self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_connections, single_workbook) def test_populate_pdf(self) -> None: self.server.version = "3.4" @@ -418,10 +426,12 @@ def test_populate_pdf(self) -> None: with open(POPULATE_PDF, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape", - content=response) - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + m.get( + self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape", + content=response, + ) + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" type = TSC.PDFRequestOptions.PageType.A5 orientation = TSC.PDFRequestOptions.Orientation.Landscape @@ -431,346 +441,395 @@ def test_populate_pdf(self) -> None: self.assertEqual(response, single_workbook.pdf) def test_populate_preview_image(self) -> None: - with open(POPULATE_PREVIEW_IMAGE, 'rb') as f: + with open(POPULATE_PREVIEW_IMAGE, "rb") as f: response = f.read() with requests_mock.mock() as m: - m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/previewImage', content=response) - single_workbook = TSC.WorkbookItem('test') - single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + m.get(self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/previewImage", content=response) + single_workbook = TSC.WorkbookItem("test") + single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2" self.server.workbooks.populate_preview_image(single_workbook) self.assertEqual(response, single_workbook.preview_image) def test_populate_preview_image_missing_id(self) -> None: - single_workbook = TSC.WorkbookItem('test') - self.assertRaises(TSC.MissingRequiredFieldError, - self.server.workbooks.populate_preview_image, - single_workbook) + single_workbook = TSC.WorkbookItem("test") + self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_preview_image, single_workbook) def test_publish(self) -> None: - with open(PUBLISH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx') + sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx") publish_mode = self.server.PublishMode.CreateNew - new_workbook = self.server.workbooks.publish(new_workbook, - sample_workbook, - publish_mode) + new_workbook = self.server.workbooks.publish(new_workbook, sample_workbook, publish_mode) - self.assertEqual('a8076ca1-e9d8-495e-bae6-c684dbb55836', new_workbook.id) - self.assertEqual('RESTAPISample', new_workbook.name) - self.assertEqual('RESTAPISample_0', new_workbook.content_url) + self.assertEqual("a8076ca1-e9d8-495e-bae6-c684dbb55836", new_workbook.id) + self.assertEqual("RESTAPISample", new_workbook.name) + self.assertEqual("RESTAPISample_0", new_workbook.content_url) self.assertEqual(False, new_workbook.show_tabs) self.assertEqual(1, new_workbook.size) - self.assertEqual('2016-08-18T18:33:24Z', format_datetime(new_workbook.created_at)) - self.assertEqual('2016-08-18T20:31:34Z', format_datetime(new_workbook.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_workbook.project_id) - self.assertEqual('default', new_workbook.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_workbook.owner_id) - self.assertEqual('fe0b4e89-73f4-435e-952d-3a263fbfa56c', new_workbook.views[0].id) - self.assertEqual('GDP per capita', new_workbook.views[0].name) - self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) + self.assertEqual("2016-08-18T18:33:24Z", format_datetime(new_workbook.created_at)) + self.assertEqual("2016-08-18T20:31:34Z", format_datetime(new_workbook.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_workbook.project_id) + self.assertEqual("default", new_workbook.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_workbook.owner_id) + self.assertEqual("fe0b4e89-73f4-435e-952d-3a263fbfa56c", new_workbook.views[0].id) + self.assertEqual("GDP per capita", new_workbook.views[0].name) + self.assertEqual("RESTAPISample_0/sheets/GDPpercapita", new_workbook.views[0].content_url) def test_publish_a_packaged_file_object(self) -> None: - with open(PUBLISH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx') + sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx") - with open(sample_workbook, 'rb') as fp: + with open(sample_workbook, "rb") as fp: publish_mode = self.server.PublishMode.CreateNew - new_workbook = self.server.workbooks.publish(new_workbook, - fp, - publish_mode) + new_workbook = self.server.workbooks.publish(new_workbook, fp, publish_mode) - self.assertEqual('a8076ca1-e9d8-495e-bae6-c684dbb55836', new_workbook.id) - self.assertEqual('RESTAPISample', new_workbook.name) - self.assertEqual('RESTAPISample_0', new_workbook.content_url) + self.assertEqual("a8076ca1-e9d8-495e-bae6-c684dbb55836", new_workbook.id) + self.assertEqual("RESTAPISample", new_workbook.name) + self.assertEqual("RESTAPISample_0", new_workbook.content_url) self.assertEqual(False, new_workbook.show_tabs) self.assertEqual(1, new_workbook.size) - self.assertEqual('2016-08-18T18:33:24Z', format_datetime(new_workbook.created_at)) - self.assertEqual('2016-08-18T20:31:34Z', format_datetime(new_workbook.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_workbook.project_id) - self.assertEqual('default', new_workbook.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_workbook.owner_id) - self.assertEqual('fe0b4e89-73f4-435e-952d-3a263fbfa56c', new_workbook.views[0].id) - self.assertEqual('GDP per capita', new_workbook.views[0].name) - self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) + self.assertEqual("2016-08-18T18:33:24Z", format_datetime(new_workbook.created_at)) + self.assertEqual("2016-08-18T20:31:34Z", format_datetime(new_workbook.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_workbook.project_id) + self.assertEqual("default", new_workbook.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_workbook.owner_id) + self.assertEqual("fe0b4e89-73f4-435e-952d-3a263fbfa56c", new_workbook.views[0].id) + self.assertEqual("GDP per capita", new_workbook.views[0].name) + self.assertEqual("RESTAPISample_0/sheets/GDPpercapita", new_workbook.views[0].content_url) def test_publish_non_packeged_file_object(self) -> None: - with open(PUBLISH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = os.path.join(TEST_ASSET_DIR, 'RESTAPISample.twb') + sample_workbook = os.path.join(TEST_ASSET_DIR, "RESTAPISample.twb") - with open(sample_workbook, 'rb') as fp: + with open(sample_workbook, "rb") as fp: publish_mode = self.server.PublishMode.CreateNew - new_workbook = self.server.workbooks.publish(new_workbook, - fp, - publish_mode) + new_workbook = self.server.workbooks.publish(new_workbook, fp, publish_mode) - self.assertEqual('a8076ca1-e9d8-495e-bae6-c684dbb55836', new_workbook.id) - self.assertEqual('RESTAPISample', new_workbook.name) - self.assertEqual('RESTAPISample_0', new_workbook.content_url) + self.assertEqual("a8076ca1-e9d8-495e-bae6-c684dbb55836", new_workbook.id) + self.assertEqual("RESTAPISample", new_workbook.name) + self.assertEqual("RESTAPISample_0", new_workbook.content_url) self.assertEqual(False, new_workbook.show_tabs) self.assertEqual(1, new_workbook.size) - self.assertEqual('2016-08-18T18:33:24Z', format_datetime(new_workbook.created_at)) - self.assertEqual('2016-08-18T20:31:34Z', format_datetime(new_workbook.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_workbook.project_id) - self.assertEqual('default', new_workbook.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_workbook.owner_id) - self.assertEqual('fe0b4e89-73f4-435e-952d-3a263fbfa56c', new_workbook.views[0].id) - self.assertEqual('GDP per capita', new_workbook.views[0].name) - self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) + self.assertEqual("2016-08-18T18:33:24Z", format_datetime(new_workbook.created_at)) + self.assertEqual("2016-08-18T20:31:34Z", format_datetime(new_workbook.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_workbook.project_id) + self.assertEqual("default", new_workbook.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_workbook.owner_id) + self.assertEqual("fe0b4e89-73f4-435e-952d-3a263fbfa56c", new_workbook.views[0].id) + self.assertEqual("GDP per capita", new_workbook.views[0].name) + self.assertEqual("RESTAPISample_0/sheets/GDPpercapita", new_workbook.views[0].content_url) def test_publish_path_object(self) -> None: - with open(PUBLISH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = Path(TEST_ASSET_DIR) / 'SampleWB.twbx' + sample_workbook = Path(TEST_ASSET_DIR) / "SampleWB.twbx" publish_mode = self.server.PublishMode.CreateNew - new_workbook = self.server.workbooks.publish(new_workbook, - sample_workbook, - publish_mode) + new_workbook = self.server.workbooks.publish(new_workbook, sample_workbook, publish_mode) - self.assertEqual('a8076ca1-e9d8-495e-bae6-c684dbb55836', new_workbook.id) - self.assertEqual('RESTAPISample', new_workbook.name) - self.assertEqual('RESTAPISample_0', new_workbook.content_url) + self.assertEqual("a8076ca1-e9d8-495e-bae6-c684dbb55836", new_workbook.id) + self.assertEqual("RESTAPISample", new_workbook.name) + self.assertEqual("RESTAPISample_0", new_workbook.content_url) self.assertEqual(False, new_workbook.show_tabs) self.assertEqual(1, new_workbook.size) - self.assertEqual('2016-08-18T18:33:24Z', format_datetime(new_workbook.created_at)) - self.assertEqual('2016-08-18T20:31:34Z', format_datetime(new_workbook.updated_at)) - self.assertEqual('ee8c6e70-43b6-11e6-af4f-f7b0d8e20760', new_workbook.project_id) - self.assertEqual('default', new_workbook.project_name) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', new_workbook.owner_id) - self.assertEqual('fe0b4e89-73f4-435e-952d-3a263fbfa56c', new_workbook.views[0].id) - self.assertEqual('GDP per capita', new_workbook.views[0].name) - self.assertEqual('RESTAPISample_0/sheets/GDPpercapita', new_workbook.views[0].content_url) + self.assertEqual("2016-08-18T18:33:24Z", format_datetime(new_workbook.created_at)) + self.assertEqual("2016-08-18T20:31:34Z", format_datetime(new_workbook.updated_at)) + self.assertEqual("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", new_workbook.project_id) + self.assertEqual("default", new_workbook.project_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", new_workbook.owner_id) + self.assertEqual("fe0b4e89-73f4-435e-952d-3a263fbfa56c", new_workbook.views[0].id) + self.assertEqual("GDP per capita", new_workbook.views[0].name) + self.assertEqual("RESTAPISample_0/sheets/GDPpercapita", new_workbook.views[0].content_url) def test_publish_with_hidden_view(self) -> None: - with open(PUBLISH_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx') + sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx") publish_mode = self.server.PublishMode.CreateNew - new_workbook = self.server.workbooks.publish(new_workbook, - sample_workbook, - publish_mode, - hidden_views=['GDP per capita']) - + new_workbook.hidden_views = ["GDP per capita"] + new_workbook = self.server.workbooks.publish(new_workbook, sample_workbook, publish_mode) request_body = m._adapter.request_history[0]._request.body # order of attributes in xml is unspecified - self.assertTrue(re.search(rb'<\/views>', request_body)) - self.assertTrue(re.search(rb'<\/views>', request_body)) + self.assertTrue(re.search(rb"<\/views>", request_body)) + self.assertTrue(re.search(rb"<\/views>", request_body)) def test_publish_with_query_params(self) -> None: - with open(PUBLISH_ASYNC_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_ASYNC_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(self.baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx') + sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx") publish_mode = self.server.PublishMode.CreateNew - self.server.workbooks.publish(new_workbook, sample_workbook, publish_mode, - as_job=True, skip_connection_check=True) + self.server.workbooks.publish( + new_workbook, sample_workbook, publish_mode, as_job=True, skip_connection_check=True + ) request_query_params = m._adapter.request_history[0].qs - self.assertTrue('asjob' in request_query_params) - self.assertTrue(request_query_params['asjob']) - self.assertTrue('skipconnectioncheck' in request_query_params) - self.assertTrue(request_query_params['skipconnectioncheck']) + self.assertTrue("asjob" in request_query_params) + self.assertTrue(request_query_params["asjob"]) + self.assertTrue("skipconnectioncheck" in request_query_params) + self.assertTrue(request_query_params["skipconnectioncheck"]) def test_publish_async(self) -> None: - self.server.version = '3.0' + self.server.version = "3.0" baseurl = self.server.workbooks.baseurl - with open(PUBLISH_ASYNC_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_ASYNC_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: m.post(baseurl, text=response_xml) - new_workbook = TSC.WorkbookItem(name='Sample', - show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - sample_workbook = os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx') + sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx") publish_mode = self.server.PublishMode.CreateNew - new_job = self.server.workbooks.publish(new_workbook, - sample_workbook, - publish_mode, - as_job=True) + new_job = self.server.workbooks.publish(new_workbook, sample_workbook, publish_mode, as_job=True) - self.assertEqual('7c3d599e-949f-44c3-94a1-f30ba85757e4', new_job.id) - self.assertEqual('PublishWorkbook', new_job.type) - self.assertEqual('0', new_job.progress) - self.assertEqual('2018-06-29T23:22:32Z', format_datetime(new_job.created_at)) + self.assertEqual("7c3d599e-949f-44c3-94a1-f30ba85757e4", new_job.id) + self.assertEqual("PublishWorkbook", new_job.type) + self.assertEqual("0", new_job.progress) + self.assertEqual("2018-06-29T23:22:32Z", format_datetime(new_job.created_at)) self.assertEqual(1, new_job.finish_code) def test_publish_invalid_file(self) -> None: - new_workbook = TSC.WorkbookItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - self.assertRaises(IOError, self.server.workbooks.publish, new_workbook, '.', - self.server.PublishMode.CreateNew) + new_workbook = TSC.WorkbookItem("test", "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") + self.assertRaises(IOError, self.server.workbooks.publish, new_workbook, ".", self.server.PublishMode.CreateNew) def test_publish_invalid_file_type(self) -> None: - new_workbook = TSC.WorkbookItem('test', 'ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - self.assertRaises(ValueError, self.server.workbooks.publish, - new_workbook, os.path.join(TEST_ASSET_DIR, 'SampleDS.tds'), - self.server.PublishMode.CreateNew) + new_workbook = TSC.WorkbookItem("test", "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760") + self.assertRaises( + ValueError, + self.server.workbooks.publish, + new_workbook, + os.path.join(TEST_ASSET_DIR, "SampleDS.tds"), + self.server.PublishMode.CreateNew, + ) def test_publish_unnamed_file_object(self) -> None: - new_workbook = TSC.WorkbookItem('test') + new_workbook = TSC.WorkbookItem("test") - with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx'), 'rb') as f: + with open(os.path.join(TEST_ASSET_DIR, "SampleWB.twbx"), "rb") as f: - self.assertRaises(ValueError, self.server.workbooks.publish, - new_workbook, f, self.server.PublishMode.CreateNew - ) + self.assertRaises( + ValueError, self.server.workbooks.publish, new_workbook, f, self.server.PublishMode.CreateNew + ) def test_publish_non_bytes_file_object(self) -> None: - new_workbook = TSC.WorkbookItem('test') + new_workbook = TSC.WorkbookItem("test") - with open(os.path.join(TEST_ASSET_DIR, 'SampleWB.twbx')) as f: + with open(os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")) as f: - self.assertRaises(TypeError, self.server.workbooks.publish, - new_workbook, f, self.server.PublishMode.CreateNew - ) + self.assertRaises( + TypeError, self.server.workbooks.publish, new_workbook, f, self.server.PublishMode.CreateNew + ) def test_publish_file_object_of_unknown_type_raises_exception(self) -> None: - new_workbook = TSC.WorkbookItem('test') + new_workbook = TSC.WorkbookItem("test") with BytesIO() as file_object: - file_object.write(bytes.fromhex('89504E470D0A1A0A')) + file_object.write(bytes.fromhex("89504E470D0A1A0A")) file_object.seek(0) - self.assertRaises(ValueError, self.server.workbooks.publish, new_workbook, - file_object, self.server.PublishMode.CreateNew) + self.assertRaises( + ValueError, self.server.workbooks.publish, new_workbook, file_object, self.server.PublishMode.CreateNew + ) def test_publish_multi_connection(self) -> None: - new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) connection1 = TSC.ConnectionItem() - connection1.server_address = 'mysql.test.com' - connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection1.server_address = "mysql.test.com" + connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True) connection2 = TSC.ConnectionItem() - connection2.server_address = 'pgsql.test.com' - connection2.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection2.server_address = "pgsql.test.com" + connection2.connection_credentials = TSC.ConnectionCredentials("test", "secret", True) response = RequestFactory.Workbook._generate_xml(new_workbook, connections=[connection1, connection2]) # Can't use ConnectionItem parser due to xml namespace problems - connection_results = ET.fromstring(response).findall('.//connection') + connection_results = ET.fromstring(response).findall(".//connection") - self.assertEqual(connection_results[0].get('serverAddress', None), 'mysql.test.com') - self.assertEqual(connection_results[0].find('connectionCredentials').get('name', None), 'test') # type: ignore[union-attr] - self.assertEqual(connection_results[1].get('serverAddress', None), 'pgsql.test.com') - self.assertEqual(connection_results[1].find('connectionCredentials').get('password', None), 'secret') # type: ignore[union-attr] + self.assertEqual(connection_results[0].get("serverAddress", None), "mysql.test.com") + self.assertEqual(connection_results[0].find("connectionCredentials").get("name", None), "test") # type: ignore[union-attr] + self.assertEqual(connection_results[1].get("serverAddress", None), "pgsql.test.com") + self.assertEqual(connection_results[1].find("connectionCredentials").get("password", None), "secret") # type: ignore[union-attr] def test_publish_single_connection(self) -> None: - new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') - connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) + connection_creds = TSC.ConnectionCredentials("test", "secret", True) response = RequestFactory.Workbook._generate_xml(new_workbook, connection_credentials=connection_creds) # Can't use ConnectionItem parser due to xml namespace problems - credentials = ET.fromstring(response).findall('.//connectionCredentials') + credentials = ET.fromstring(response).findall(".//connectionCredentials") self.assertEqual(len(credentials), 1) - self.assertEqual(credentials[0].get('name', None), 'test') - self.assertEqual(credentials[0].get('password', None), 'secret') - self.assertEqual(credentials[0].get('embed', None), 'true') + self.assertEqual(credentials[0].get("name", None), "test") + self.assertEqual(credentials[0].get("password", None), "secret") + self.assertEqual(credentials[0].get("embed", None), "true") def test_credentials_and_multi_connect_raises_exception(self) -> None: - new_workbook = TSC.WorkbookItem(name='Sample', show_tabs=False, - project_id='ee8c6e70-43b6-11e6-af4f-f7b0d8e20760') + new_workbook = TSC.WorkbookItem( + name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" + ) - connection_creds = TSC.ConnectionCredentials('test', 'secret', True) + connection_creds = TSC.ConnectionCredentials("test", "secret", True) connection1 = TSC.ConnectionItem() - connection1.server_address = 'mysql.test.com' - connection1.connection_credentials = TSC.ConnectionCredentials('test', 'secret', True) + connection1.server_address = "mysql.test.com" + connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True) with self.assertRaises(RuntimeError): - response = RequestFactory.Workbook._generate_xml(new_workbook, - connection_credentials=connection_creds, - connections=[connection1]) + response = RequestFactory.Workbook._generate_xml( + new_workbook, connection_credentials=connection_creds, connections=[connection1] + ) def test_synchronous_publish_timeout_error(self) -> None: with requests_mock.mock() as m: - m.register_uri('POST', self.baseurl, status_code=504) + m.register_uri("POST", self.baseurl, status_code=504) - new_workbook = TSC.WorkbookItem(project_id='') + new_workbook = TSC.WorkbookItem(project_id="") publish_mode = self.server.PublishMode.CreateNew - self.assertRaisesRegex(InternalServerError, 'Please use asynchronous publishing to avoid timeouts', - self.server.workbooks.publish, new_workbook, asset('SampleWB.twbx'), publish_mode) + self.assertRaisesRegex( + InternalServerError, + "Please use asynchronous publishing to avoid timeouts", + self.server.workbooks.publish, + new_workbook, + asset("SampleWB.twbx"), + publish_mode, + ) def test_delete_extracts_all(self) -> None: self.server.version = "3.10" self.baseurl = self.server.workbooks.baseurl with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract', status_code=200) - self.server.workbooks.delete_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.post(self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract", status_code=200) + self.server.workbooks.delete_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42") def test_create_extracts_all(self) -> None: self.server.version = "3.10" self.baseurl = self.server.workbooks.baseurl - with open(PUBLISH_ASYNC_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') + with open(PUBLISH_ASYNC_XML, "rb") as f: + response_xml = f.read().decode("utf-8") with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract', - status_code=200, text=response_xml) - self.server.workbooks.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42') + m.post( + self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract", status_code=200, text=response_xml + ) + self.server.workbooks.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42") def test_create_extracts_one(self) -> None: self.server.version = "3.10" self.baseurl = self.server.workbooks.baseurl - datasource = TSC.DatasourceItem('test') - datasource._id = '1f951daf-4061-451a-9df1-69a8062664f2' + datasource = TSC.DatasourceItem("test") + datasource._id = "1f951daf-4061-451a-9df1-69a8062664f2" + + with open(PUBLISH_ASYNC_XML, "rb") as f: + response_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.post( + self.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract", status_code=200, text=response_xml + ) + self.server.workbooks.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42", False, datasource) + + def test_revisions(self) -> None: + self.baseurl = self.server.workbooks.baseurl + workbook = TSC.WorkbookItem("project", "test") + workbook._id = "06b944d2-959d-4604-9305-12323c95e70e" + + with open(REVISION_XML, "rb") as f: + response_xml = f.read().decode("utf-8") + with requests_mock.mock() as m: + m.get("{0}/{1}/revisions".format(self.baseurl, workbook.id), text=response_xml) + self.server.workbooks.populate_revisions(workbook) + revisions = workbook.revisions + + self.assertEqual(len(revisions), 3) + self.assertEqual("2016-07-26T20:34:56Z", format_datetime(revisions[0].created_at)) + self.assertEqual("2016-07-27T20:34:56Z", format_datetime(revisions[1].created_at)) + self.assertEqual("2016-07-28T20:34:56Z", format_datetime(revisions[2].created_at)) + + self.assertEqual(False, revisions[0].deleted) + self.assertEqual(False, revisions[0].current) + self.assertEqual(False, revisions[1].deleted) + self.assertEqual(False, revisions[1].current) + self.assertEqual(False, revisions[2].deleted) + self.assertEqual(True, revisions[2].current) + + self.assertEqual("Cassie", revisions[0].user_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", revisions[0].user_id) + self.assertIsNone(revisions[1].user_name) + self.assertIsNone(revisions[1].user_id) + self.assertEqual("Cassie", revisions[2].user_name) + self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", revisions[2].user_id) + + def test_delete_revision(self) -> None: + self.baseurl = self.server.workbooks.baseurl + workbook = TSC.WorkbookItem("project", "test") + workbook._id = "06b944d2-959d-4604-9305-12323c95e70e" - with open(PUBLISH_ASYNC_XML, 'rb') as f: - response_xml = f.read().decode('utf-8') with requests_mock.mock() as m: - m.post(self.baseurl + '/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract', - status_code=200, text=response_xml) - self.server.workbooks.create_extract('3cc6cd06-89ce-4fdc-b935-5294135d6d42', False, datasource) + m.delete("{0}/{1}/revisions/3".format(self.baseurl, workbook.id)) + self.server.workbooks.delete_revision(workbook.id, "3") + + def test_download_revision(self) -> None: + with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td: + m.get( + self.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/revisions/3/content", + headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'}, + ) + file_path = self.server.workbooks.download_revision("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", "3", td) + self.assertTrue(os.path.exists(file_path)) From 69663ab175da5759cc2e33f4ef0f59dfe4856c4d Mon Sep 17 00:00:00 2001 From: Jac Date: Thu, 27 Jan 2022 23:34:22 -0800 Subject: [PATCH 69/69] Update request_factory.py --- tableauserverclient/server/request_factory.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index 34d8e4404..2a16f5a8f 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -2,6 +2,7 @@ from requests.packages.urllib3.fields import RequestField from requests.packages.urllib3.filepost import encode_multipart_formdata +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Iterable from ..models import TaskItem, UserItem, GroupItem, PermissionsRule, FavoriteItem