diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 000000000..e12d61383 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,33 @@ +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] + + - name: Lint with pycodestyle + run: | + pycodestyle tableauserverclient test samples + + - name: Test with pytest + run: | + pytest test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 41316d700..000000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -dist: xenial -language: python -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" -# command to install dependencies -install: - - "pip install -e ." - - "pip install pycodestyle" -# command to run tests -script: - # Tests - - python setup.py test - - pycodestyle tableauserverclient test samples diff --git a/CHANGELOG.md b/CHANGELOG.md index 85dc8a702..45a44b251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 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) 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/group_item.py b/tableauserverclient/models/group_item.py index ba9beec27..af9465dfb 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -9,13 +9,17 @@ class GroupItem(object): tag_name = 'group' - def __init__(self, name=None): - self._domain_name = None + class LicenseMode: + onLogin = 'onLogin' + onSync = 'onSync' + + def __init__(self, name=None, domain_name=None): self._id = None - self._users = None - self.name = name self._license_mode = None self._minimum_site_role = None + self._users = None + self.name = name + self.domain_name = domain_name @property def domain_name(self): @@ -43,8 +47,8 @@ def license_mode(self): return self._license_mode @license_mode.setter + @property_is_enum(LicenseMode) def license_mode(self, value): - # valid values = onSync, onLogin self._license_mode = value @property @@ -79,17 +83,18 @@ def from_response(cls, resp, ns): name = group_xml.get('name', None) group_item = cls(name) group_item._id = group_xml.get('id', None) - # AD groups have an extra element under this + + # Domain name is returned in a domain element for some calls + domain_elem = group_xml.find('.//t:domain', namespaces=ns) + if domain_elem is not None: + group_item.domain_name = domain_elem.get('name', None) + + # Import element is returned for both local and AD groups (2020.3+) import_elem = group_xml.find('.//t:import', namespaces=ns) - if (import_elem is not None): - group_item.domain_name = import_elem.get('domainName') - group_item.license_mode = import_elem.get('grantLicenseMode') - group_item.minimum_site_role = import_elem.get('siteRole') - else: - # local group, we will just have two extra attributes here - group_item.domain_name = 'local' - group_item.license_mode = group_xml.get('grantLicenseMode') - group_item.minimum_site_role = group_xml.get('siteRole') + if import_elem is not None: + group_item.domain_name = import_elem.get('domainName', None) + group_item.license_mode = import_elem.get('grantLicenseMode', None) + group_item.minimum_site_role = import_elem.get('siteRole', None) all_group_items.append(group_item) return all_group_items diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index d6aece83b..4cfbcb4e9 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -74,6 +74,14 @@ def name(self): def name(self, value): self._name = value + @property + def owner_id(self): + return self._owner_id + + @owner_id.setter + def owner_id(self, value): + raise NotImplementedError('REST API does not currently support updating project owner.') + def is_default(self): return self.name.lower() == 'default' @@ -86,7 +94,7 @@ def _parse_common_tags(self, project_xml, ns): self._set_values(None, name, description, content_permissions, parent_id) return self - def _set_values(self, project_id, name, description, content_permissions, parent_id): + def _set_values(self, project_id, name, description, content_permissions, parent_id, owner_id): if project_id is not None: self._id = project_id if name: @@ -97,6 +105,8 @@ def _set_values(self, project_id, name, description, content_permissions, parent self._content_permissions = content_permissions if parent_id: self.parent_id = parent_id + if owner_id: + self._owner_id = owner_id def _set_permissions(self, permissions): self._permissions = permissions @@ -111,9 +121,9 @@ 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) = 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) + project_item._set_values(id, name, description, content_permissions, parent_id, owner_id) all_project_items.append(project_item) return all_project_items @@ -124,5 +134,8 @@ def _parse_element(project_xml): description = project_xml.get('description', None) content_permissions = project_xml.get('contentPermissions', None) parent_id = project_xml.get('parentProjectId', None) + owner_id = None + for owner in project_xml: + owner_id = owner.get('id', None) - return id, name, description, content_permissions, parent_id + return id, name, description, content_permissions, parent_id, owner_id diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index 1ba854e72..f562289ce 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 " 0 and server_response.encoding: logger.debug(u'Server response from {0}:\n\t{1}'.format( url, server_response.content.decode(server_response.encoding))) return server_response def _check_status(self, server_response): - logger.debug(self._safe_to_log(server_response)) if server_response.status_code >= 500: raise InternalServerError(server_response) elif server_response.status_code not in Success_codes: diff --git a/tableauserverclient/server/endpoint/groups_endpoint.py b/tableauserverclient/server/endpoint/groups_endpoint.py index c873dc159..6a9b81afd 100644 --- a/tableauserverclient/server/endpoint/groups_endpoint.py +++ b/tableauserverclient/server/endpoint/groups_endpoint.py @@ -7,8 +7,6 @@ logger = logging.getLogger('tableau.endpoint.groups') -UNLICENSED_USER = UserItem.Roles.Unlicensed - class Groups(Endpoint): @property @@ -58,15 +56,28 @@ def delete(self, group_id): logger.info('Deleted single group (ID: {0})'.format(group_id)) @api(version="2.0") - def update(self, group_item, default_site_role=UNLICENSED_USER, as_job=False): + def update(self, group_item, default_site_role=None, as_job=False): + # (1/8/2021): Deprecated starting v0.15 + if default_site_role is not None: + import warnings + warnings.simplefilter('always', DeprecationWarning) + warnings.warn('Groups.update(...default_site_role=""...) is deprecated, ' + 'please set the minimum_site_role field of GroupItem', + DeprecationWarning) + group_item.minimum_site_role = default_site_role + if not group_item.id: error = "Group item missing ID." raise MissingRequiredFieldError(error) + if as_job and (group_item.domain_name is None or group_item.domain_name == 'local'): + error = "Local groups cannot be updated asynchronously." + raise ValueError(error) + url = "{0}/{1}".format(self.baseurl, group_item.id) - update_req = RequestFactory.Group.update_req(group_item, default_site_role) + update_req = RequestFactory.Group.update_req(group_item, None) server_response = self.put_request(url, update_req) logger.info('Updated group item (ID: {0})'.format(group_item.id)) - if (as_job): + if as_job: return JobItem.from_response(server_response.content, self.parent_srv.namespace)[0] else: return GroupItem.from_response(server_response.content, self.parent_srv.namespace)[0] diff --git a/tableauserverclient/server/endpoint/subscriptions_endpoint.py b/tableauserverclient/server/endpoint/subscriptions_endpoint.py index 00a7c6856..120a2a17b 100644 --- a/tableauserverclient/server/endpoint/subscriptions_endpoint.py +++ b/tableauserverclient/server/endpoint/subscriptions_endpoint.py @@ -1,4 +1,5 @@ from .endpoint import Endpoint, api +from .exceptions import MissingRequiredFieldError from .. import RequestFactory, SubscriptionItem, PaginationItem import logging @@ -51,3 +52,14 @@ def delete(self, subscription_id): url = "{0}/{1}".format(self.baseurl, subscription_id) self.delete_request(url) logger.info('Deleted subscription (ID: {0})'.format(subscription_id)) + + @api(version='2.3') + def update(self, subscription_item): + if not subscription_item.id: + error = "Subscription item missing ID. Subscription must be retrieved from server first." + raise MissingRequiredFieldError(error) + url = "{0}/{1}".format(self.baseurl, subscription_item.id) + update_req = RequestFactory.Subscription.update_req(subscription_item) + server_response = self.put_request(url, update_req) + logger.info('Updated subscription item (ID: {0})'.format(subscription_item.id)) + return SubscriptionItem.from_response(server_response.content, self.parent_srv.namespace)[0] diff --git a/tableauserverclient/server/endpoint/users_endpoint.py b/tableauserverclient/server/endpoint/users_endpoint.py index 5d3c69b26..17e12a8b1 100644 --- a/tableauserverclient/server/endpoint/users_endpoint.py +++ b/tableauserverclient/server/endpoint/users_endpoint.py @@ -1,6 +1,6 @@ from .endpoint import QuerysetEndpoint, api from .exceptions import MissingRequiredFieldError -from .. import RequestFactory, RequestOptions, UserItem, WorkbookItem, PaginationItem +from .. import RequestFactory, RequestOptions, UserItem, WorkbookItem, PaginationItem, GroupItem from ..pager import Pager import copy @@ -96,3 +96,23 @@ def _get_wbs_for_user(self, user_item, req_options=None): def populate_favorites(self, user_item): self.parent_srv.favorites.get(user_item) + + # Get groups for user + @api(version="3.7") + def populate_groups(self, user_item, req_options=None): + if not user_item.id: + error = "User item missing ID." + raise MissingRequiredFieldError(error) + + def groups_for_user_pager(): + return Pager(lambda options: self._get_groups_for_user(user_item, options), req_options) + + user_item._set_groups(groups_for_user_pager) + + def _get_groups_for_user(self, user_item, req_options=None): + url = "{0}/{1}/groups".format(self.baseurl, user_item.id) + server_response = self.get_request(url, req_options) + logger.info('Populated groups for user (ID: {0})'.format(user_item.id)) + group_item = GroupItem.from_response(server_response.content, self.parent_srv.namespace) + pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) + return group_item, pagination_item diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py index cd2792f5d..8c848c295 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -36,6 +36,16 @@ def get(self, req_options=None, usage=False): all_view_items = ViewItem.from_response(server_response.content, self.parent_srv.namespace) return all_view_items, pagination_item + @api(version="3.1") + def get_by_id(self, view_id): + if not view_id: + error = "View item missing ID." + raise MissingRequiredFieldError(error) + logger.info('Querying single view (ID: {0})'.format(view_id)) + url = "{0}/{1}".format(self.baseurl, view_id) + server_response = self.get_request(url) + return ViewItem.from_response(server_response.content, self.parent_srv.namespace)[0] + @api(version="2.0") def populate_preview_image(self, view_item): if not view_item.id or not view_item.workbook_id: diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 62f94f99a..e40d9e1dd 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -256,7 +256,7 @@ def delete_permission(self, item, capability_item): def publish( self, workbook_item, file, mode, connection_credentials=None, connections=None, as_job=False, - hidden_views=None + hidden_views=None, skip_connection_check=False ): if connection_credentials is not None: @@ -318,6 +318,9 @@ def publish( if as_job: url += '&{0}=true'.format('asJob') + if skip_connection_check: + url += '&{0}=true'.format('skipConnectionCheck') + # Determine if chunking is required (64MB is the limit for single upload method) if file_size >= 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 65ce5a069..e34220188 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -277,10 +277,8 @@ def create_local_req(self, group_item): xml_request = ET.Element('tsRequest') group_element = ET.SubElement(xml_request, 'group') group_element.attrib['name'] = group_item.name - if group_item.license_mode is not None: - group_element.attrib['grantLicenseMode'] = group_item.license_mode if group_item.minimum_site_role is not None: - group_element.attrib['SiteRole'] = group_item.minimum_site_role + group_element.attrib['minimumSiteRole'] = group_item.minimum_site_role return ET.tostring(xml_request) def create_ad_req(self, group_item): @@ -295,23 +293,36 @@ def create_ad_req(self, group_item): import_element.attrib['domainName'] = group_item.domain_name if group_item.license_mode is not None: - import_element.attrib['grantLicenseMode'] = group_item.license + import_element.attrib['grantLicenseMode'] = group_item.license_mode if group_item.minimum_site_role is not None: - import_element.attrib['SiteRole'] = group_item.minimum_site_role + import_element.attrib['siteRole'] = group_item.minimum_site_role return ET.tostring(xml_request) def update_req(self, group_item, default_site_role=None): + # (1/8/2021): Deprecated starting v0.15 if default_site_role is not None: + import warnings + warnings.simplefilter('always', DeprecationWarning) + warnings.warn('RequestFactory.Group.update_req(...default_site_role="") is deprecated, ' + 'please set the minimum_site_role field of GroupItem', + DeprecationWarning) group_item.minimum_site_role = default_site_role + xml_request = ET.Element('tsRequest') group_element = ET.SubElement(xml_request, 'group') group_element.attrib['name'] = group_item.name - if group_item.domain_name != 'local': - project_element = ET.SubElement(group_element, 'import') - project_element.attrib['source'] = "ActiveDirectory" - project_element.attrib['domainName'] = group_item.domain_name - project_element.attrib['siteRole'] = group_item.minimum_site_role - project_element.attrib['grantLicenseMode'] = group_item.license_mode + if group_item.domain_name is not None and group_item.domain_name != 'local': + # Import element is only accepted in the request for AD groups + import_element = ET.SubElement(group_element, 'import') + import_element.attrib['source'] = "ActiveDirectory" + import_element.attrib['domainName'] = group_item.domain_name + import_element.attrib['siteRole'] = group_item.minimum_site_role + if group_item.license_mode is not None: + import_element.attrib['grantLicenseMode'] = group_item.license_mode + else: + # Local group request does not accept an 'import' element + if group_item.minimum_site_role is not None: + group_element.attrib['minimumSiteRole'] = group_item.minimum_site_role return ET.tostring(xml_request) @@ -461,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() @@ -469,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): @@ -484,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) @@ -593,13 +756,12 @@ def update_req(self, workbook_item): if workbook_item.owner_id: owner_element = ET.SubElement(workbook_element, 'owner') owner_element.attrib['id'] = workbook_item.owner_id - if workbook_item.data_acceleration_config is not None and \ - 'acceleration_enabled' in workbook_item.data_acceleration_config: + if workbook_item.data_acceleration_config['acceleration_enabled'] is not None: data_acceleration_config = workbook_item.data_acceleration_config data_acceleration_element = ET.SubElement(workbook_element, 'dataAccelerationConfig') data_acceleration_element.attrib['accelerationEnabled'] = str(data_acceleration_config ["acceleration_enabled"]).lower() - if "accelerate_now" in data_acceleration_config: + if data_acceleration_config['accelerate_now'] is not None: data_acceleration_element.attrib['accelerateNow'] = str(data_acceleration_config ["accelerate_now"]).lower() @@ -665,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/group_update.xml b/test/assets/group_update.xml index 828e3f251..3c54524c0 100644 --- a/test/assets/group_update.xml +++ b/test/assets/group_update.xml @@ -2,7 +2,7 @@ - /> + + + \ No newline at end of file diff --git a/test/assets/project_get.xml b/test/assets/project_get.xml index 777412b30..7898c8c13 100644 --- a/test/assets/project_get.xml +++ b/test/assets/project_get.xml @@ -2,8 +2,8 @@ - - - + + + 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/assets/view_get_id.xml b/test/assets/view_get_id.xml new file mode 100644 index 000000000..6110a0a3a --- /dev/null +++ b/test/assets/view_get_id.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/test_group.py b/test/test_group.py index 8aeb4817d..082a63ba3 100644 --- a/test/test_group.py +++ b/test/test_group.py @@ -224,3 +224,13 @@ def test_update(self): 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' + self.assertRaises(ValueError, self.server.groups.update, group, as_job=True) + + # mimic group returned from server where domain name is set to 'local' + group.domain_name = "local" + self.assertRaises(ValueError, self.server.groups.update, group, as_job=True) diff --git a/test/test_group_model.py b/test/test_group_model.py index eb11adcdd..617a5d954 100644 --- a/test/test_group_model.py +++ b/test/test_group_model.py @@ -12,3 +12,13 @@ def test_invalid_name(self): with self.assertRaises(ValueError): group.name = "" + + def test_invalid_minimum_site_role(self): + group = TSC.GroupItem("grp") + with self.assertRaises(ValueError): + group.minimum_site_role = "Captain" + + def test_invalid_license_mode(self): + group = TSC.GroupItem("grp") + with self.assertRaises(ValueError): + group.license_mode = "off" diff --git a/test/test_project.py b/test/test_project.py index 5e9869c6e..045f0a43e 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -39,16 +39,19 @@ def test_get(self): 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('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('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 @@ -156,7 +159,7 @@ def test_populate_workbooks(self): 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._owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' single_project._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' self.server.projects.populate_workbook_default_permissions(single_project) @@ -227,7 +230,7 @@ def test_delete_workbook_default_permission(self): 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._owner_id = 'dd2239f6-ddf1-4107-981a-4cf94e415794' single_project._id = '9dbd2263-16b5-46e1-9c43-a76bb8ab65fb' self.server.projects.populate_workbook_default_permissions(single_project) diff --git a/test/test_project_model.py b/test/test_project_model.py index 56e6c3d11..55cf20b26 100644 --- a/test/test_project_model.py +++ b/test/test_project_model.py @@ -22,3 +22,8 @@ def test_parent_id(self): project = TSC.ProjectItem("proj") project.parent_id = "foo" self.assertEqual(project.parent_id, "foo") + + def test_owner_id(self): + project = TSC.ProjectItem("proj") + with self.assertRaises(NotImplementedError): + project.owner_id = "new_owner" 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_view.py b/test/test_view.py index 1bd88995a..e32971ea2 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -10,6 +10,7 @@ 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') @@ -60,6 +61,27 @@ def test_get(self): 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 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) + + 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') 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