Skip to content

Commit 24aa9d1

Browse files
committed
feat: enable retrieving site extension settings
1 parent e5c9035 commit 24aa9d1

File tree

6 files changed

+137
-4
lines changed

6 files changed

+137
-4
lines changed

tableauserverclient/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
DataFreshnessPolicyItem,
1515
DatasourceItem,
1616
ExtensionsServer,
17+
ExtensionsSiteSettings,
1718
FavoriteItem,
1819
FlowItem,
1920
FlowRunItem,
@@ -37,6 +38,7 @@
3738
ProjectItem,
3839
Resource,
3940
RevisionItem,
41+
SafeExtension,
4042
ScheduleItem,
4143
SiteAuthConfiguration,
4244
SiteOIDCConfiguration,
@@ -90,6 +92,7 @@
9092
"DQWItem",
9193
"ExcelRequestOptions",
9294
"ExtensionsServer",
95+
"ExtensionsSiteSettings",
9396
"FailedSignInError",
9497
"FavoriteItem",
9598
"FileuploadItem",
@@ -123,6 +126,7 @@
123126
"RequestOptions",
124127
"Resource",
125128
"RevisionItem",
129+
"SafeExtension",
126130
"ScheduleItem",
127131
"Server",
128132
"ServerInfoItem",

tableauserverclient/models/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from tableauserverclient.models.datasource_item import DatasourceItem
1111
from tableauserverclient.models.dqw_item import DQWItem
1212
from tableauserverclient.models.exceptions import UnpopulatedPropertyError
13-
from tableauserverclient.models.extensions_item import ExtensionsServer
13+
from tableauserverclient.models.extensions_item import ExtensionsServer, ExtensionsSiteSettings, SafeExtension
1414
from tableauserverclient.models.favorites_item import FavoriteItem
1515
from tableauserverclient.models.fileupload_item import FileuploadItem
1616
from tableauserverclient.models.flow_item import FlowItem
@@ -115,4 +115,6 @@
115115
"LinkedTaskFlowRunItem",
116116
"ExtractItem",
117117
"ExtensionsServer",
118+
"ExtensionsSiteSettings",
119+
"SafeExtension",
118120
]

tableauserverclient/models/extensions_item.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from typing import Optional, TypeVar, overload
1+
from typing import Optional, overload
22
from typing_extensions import Self
33

44
from defusedxml.ElementTree import fromstring
55

6-
T = TypeVar("T")
6+
from tableauserverclient.models.property_decorators import property_is_boolean
77

88

99
class ExtensionsServer:
@@ -17,6 +17,7 @@ def enabled(self) -> Optional[bool]:
1717
return self._enabled
1818

1919
@enabled.setter
20+
@property_is_boolean
2021
def enabled(self, value: Optional[bool]) -> None:
2122
self._enabled = value
2223

@@ -44,6 +45,87 @@ def from_response(cls: type[Self], response, ns) -> Self:
4445
return obj
4546

4647

48+
class SafeExtension:
49+
def __init__(
50+
self, url: Optional[str] = None, full_data_allowed: Optional[bool] = None, prompt_needed: Optional[bool] = None
51+
) -> None:
52+
self.url = url
53+
self._full_data_allowed = full_data_allowed
54+
self._prompt_needed = prompt_needed
55+
56+
@property
57+
def full_data_allowed(self) -> Optional[bool]:
58+
return self._full_data_allowed
59+
60+
@full_data_allowed.setter
61+
@property_is_boolean
62+
def full_data_allowed(self, value: Optional[bool]) -> None:
63+
self._full_data_allowed = value
64+
65+
@property
66+
def prompt_needed(self) -> Optional[bool]:
67+
return self._prompt_needed
68+
69+
@prompt_needed.setter
70+
@property_is_boolean
71+
def prompt_needed(self, value: Optional[bool]) -> None:
72+
self._prompt_needed = value
73+
74+
75+
class ExtensionsSiteSettings:
76+
def __init__(self) -> None:
77+
self._enabled: Optional[bool] = None
78+
self._use_default_settings: Optional[bool] = None
79+
self.safe_list: Optional[list[SafeExtension]] = None
80+
81+
@property
82+
def enabled(self) -> Optional[bool]:
83+
return self._enabled
84+
85+
@enabled.setter
86+
@property_is_boolean
87+
def enabled(self, value: Optional[bool]) -> None:
88+
self._enabled = value
89+
90+
@property
91+
def use_default_settings(self) -> Optional[bool]:
92+
return self._use_default_settings
93+
94+
@use_default_settings.setter
95+
@property_is_boolean
96+
def use_default_settings(self, value: Optional[bool]) -> None:
97+
self._use_default_settings = value
98+
99+
@classmethod
100+
def from_response(cls: type[Self], response, ns) -> Self:
101+
xml = fromstring(response)
102+
element = xml.find(".//t:extensionsSiteSettings", namespaces=ns)
103+
obj = cls()
104+
if element is None:
105+
raise ValueError("Missing extensionsSiteSettings element in response")
106+
107+
if (enabled_element := element.find("./t:extensionsEnabled", namespaces=ns)) is not None:
108+
obj.enabled = string_to_bool(enabled_element.text)
109+
if (default_settings_element := element.find("./t:useDefaultSetting", namespaces=ns)) is not None:
110+
obj.use_default_settings = string_to_bool(default_settings_element.text)
111+
112+
safe_list = []
113+
for safe_extension_element in element.findall("./t:safeList", namespaces=ns):
114+
url = safe_extension_element.find("./t:url", namespaces=ns)
115+
full_data_allowed = safe_extension_element.find("./t:fullDataAllowed", namespaces=ns)
116+
prompt_needed = safe_extension_element.find("./t:promptNeeded", namespaces=ns)
117+
118+
safe_extension = SafeExtension(
119+
url=url.text if url is not None else None,
120+
full_data_allowed=string_to_bool(full_data_allowed.text) if full_data_allowed is not None else None,
121+
prompt_needed=string_to_bool(prompt_needed.text) if prompt_needed is not None else None,
122+
)
123+
safe_list.append(safe_extension)
124+
125+
obj.safe_list = safe_list
126+
return obj
127+
128+
47129
@overload
48130
def string_to_bool(s: str) -> bool: ...
49131

tableauserverclient/server/endpoint/extensions_endpoint.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from tableauserverclient.models.extensions_item import ExtensionsServer
1+
from tableauserverclient.models.extensions_item import ExtensionsServer, ExtensionsSiteSettings
22
from tableauserverclient.server.endpoint.endpoint import Endpoint
33
from tableauserverclient.server.endpoint.endpoint import api
44
from tableauserverclient.server.request_factory import RequestFactory
@@ -12,6 +12,10 @@ def __init__(self, parent_srv):
1212
def _server_baseurl(self) -> str:
1313
return f"{self.parent_srv.baseurl}/settings/extensions"
1414

15+
@property
16+
def baseurl(self) -> str:
17+
return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/settings/extensions"
18+
1519
@api(version="3.21")
1620
def get_server_settings(self) -> ExtensionsServer:
1721
"""Lists the settings for extensions of a server
@@ -41,3 +45,15 @@ def update_server_settings(self, extensions_server: ExtensionsServer) -> Extensi
4145
req = RequestFactory.Extensions.update_server_extensions(extensions_server)
4246
response = self.put_request(self._server_baseurl, req)
4347
return ExtensionsServer.from_response(response.content, self.parent_srv.namespace)
48+
49+
@api(version="3.21")
50+
def get(self) -> ExtensionsSiteSettings:
51+
"""Lists the extensions settings for the site
52+
53+
Returns
54+
-------
55+
list[ExtensionsSiteSettings]
56+
The site extensions settings
57+
"""
58+
response = self.get_request(self.baseurl)
59+
return ExtensionsSiteSettings.from_response(response.content, self.parent_srv.namespace)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<tsResponse xmlns="http://tableau.com/api" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tableau.com/api http://tableau.com/api/ts-api-2.3.xsd">
3+
<extensionsSiteSettings>
4+
<extensionsEnabled>true</extensionsEnabled>
5+
<useDefaultSetting>false</useDefaultSetting>
6+
<safeList>
7+
<url>http://localhost:9123/Dynamic.html</url>
8+
<fullDataAllowed>true</fullDataAllowed>
9+
<promptNeeded>true</promptNeeded>
10+
</safeList>
11+
</extensionsSiteSettings>
12+
</tsResponse>

test/test_extensions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
GET_SERVER_EXT_SETTINGS = TEST_ASSET_DIR / "extensions_server_settings_true.xml"
1212
GET_SERVER_EXT_SETTINGS_FALSE = TEST_ASSET_DIR / "extensions_server_settings_false.xml"
13+
GET_SITE_SETTINGS = TEST_ASSET_DIR / "extensions_site_settings.xml"
1314

1415

1516
@pytest.fixture(scope="function")
@@ -57,3 +58,19 @@ def test_update_server_extensions_settings(server: TSC.Server) -> None:
5758
assert updated_settings.enabled is False
5859
assert updated_settings.block_list is not None
5960
assert len(updated_settings.block_list) == 0
61+
62+
63+
def test_get_site_settings(server: TSC.Server) -> None:
64+
with requests_mock.mock() as m:
65+
m.get(server.extensions.baseurl, text=GET_SITE_SETTINGS.read_text())
66+
site_settings = server.extensions.get()
67+
68+
assert isinstance(site_settings, TSC.ExtensionsSiteSettings)
69+
assert site_settings.enabled is True
70+
assert site_settings.use_default_settings is False
71+
assert site_settings.safe_list is not None
72+
assert len(site_settings.safe_list) == 1
73+
first_safe = site_settings.safe_list[0]
74+
assert first_safe.url == "http://localhost:9123/Dynamic.html"
75+
assert first_safe.full_data_allowed is True
76+
assert first_safe.prompt_needed is True

0 commit comments

Comments
 (0)