Skip to content

Commit a910a8e

Browse files
committed
fix: merge conflict
2 parents cf6b723 + fd187ba commit a910a8e

17 files changed

+1746
-1528
lines changed

tableauserverclient/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from tableauserverclient.namespace import NEW_NAMESPACE as DEFAULT_NAMESPACE
33
from tableauserverclient.models import (
44
BackgroundJobItem,
5+
CollectionItem,
56
ColumnItem,
67
ConnectionCredentials,
78
ConnectionItem,
@@ -73,7 +74,7 @@
7374

7475
__all__ = [
7576
"BackgroundJobItem",
76-
"BackgroundJobItem",
77+
"CollectionItem",
7778
"ColumnItem",
7879
"ConnectionCredentials",
7980
"ConnectionItem",

tableauserverclient/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from tableauserverclient.models.collection_item import CollectionItem
12
from tableauserverclient.models.column_item import ColumnItem
23
from tableauserverclient.models.connection_credentials import ConnectionCredentials
34
from tableauserverclient.models.connection_item import ConnectionItem
@@ -53,6 +54,7 @@
5354
from tableauserverclient.models.extract_item import ExtractItem
5455

5556
__all__ = [
57+
"CollectionItem",
5658
"ColumnItem",
5759
"ConnectionCredentials",
5860
"ConnectionItem",
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
from xml.etree.ElementTree import Element
4+
5+
from defusedxml.ElementTree import fromstring
6+
from typing_extensions import Self
7+
8+
from tableauserverclient.datetime_helpers import parse_datetime
9+
from tableauserverclient.models.user_item import UserItem
10+
11+
12+
class CollectionItem:
13+
def __init__(self) -> None:
14+
self.id: Optional[str] = None
15+
self.name: Optional[str] = None
16+
self.description: Optional[str] = None
17+
self.created_at: Optional[datetime] = None
18+
self.updated_at: Optional[datetime] = None
19+
self.owner: Optional[UserItem] = None
20+
self.total_item_count: Optional[int] = None
21+
self.permissioned_item_count: Optional[int] = None
22+
self.visibility: Optional[str] = None # Assuming visibility is a string, adjust as necessary
23+
24+
@classmethod
25+
def from_response(cls, response: bytes, ns) -> list[Self]:
26+
parsed_response = fromstring(response)
27+
28+
collection_elements = parsed_response.findall(".//t:collection", namespaces=ns)
29+
if not collection_elements:
30+
raise ValueError("No collection element found in the response")
31+
32+
collections = [cls.from_xml(c, ns) for c in collection_elements]
33+
return collections
34+
35+
@classmethod
36+
def from_xml(cls, xml: Element, ns) -> Self:
37+
collection_item = cls()
38+
collection_item.id = xml.get("id")
39+
collection_item.name = xml.get("name")
40+
collection_item.description = xml.get("description")
41+
collection_item.created_at = parse_datetime(xml.get("createdAt"))
42+
collection_item.updated_at = parse_datetime(xml.get("updatedAt"))
43+
owner_element = xml.find(".//t:owner", namespaces=ns)
44+
if owner_element is not None:
45+
collection_item.owner = UserItem.from_xml(owner_element, ns)
46+
else:
47+
collection_item.owner = None
48+
collection_item.total_item_count = int(xml.get("totalItemCount", 0))
49+
collection_item.permissioned_item_count = int(xml.get("permissionedItemCount", 0))
50+
collection_item.visibility = xml.get("visibility")
51+
52+
return collection_item

tableauserverclient/models/datasource_item.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ def _set_values(
490490
self._owner = owner
491491

492492
@classmethod
493-
def from_response(cls, resp: str, ns: dict) -> list["DatasourceItem"]:
493+
def from_response(cls, resp: bytes, ns: dict) -> list["DatasourceItem"]:
494494
all_datasource_items = list()
495495
parsed_response = fromstring(resp)
496496
all_datasource_xml = parsed_response.findall(".//t:datasource", namespaces=ns)

tableauserverclient/models/favorites_item.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import logging
22

3-
from typing import Union
3+
from typing import TypedDict, Union
44
from defusedxml.ElementTree import fromstring
5-
6-
from tableauserverclient.models.tableau_types import TableauItem
5+
from tableauserverclient.models.collection_item import CollectionItem
76
from tableauserverclient.models.datasource_item import DatasourceItem
87
from tableauserverclient.models.flow_item import FlowItem
98
from tableauserverclient.models.project_item import ProjectItem
@@ -13,16 +12,22 @@
1312

1413
from tableauserverclient.helpers.logging import logger
1514

16-
FavoriteType = dict[
17-
str,
18-
list[TableauItem],
19-
]
15+
16+
class FavoriteType(TypedDict):
17+
collections: list[CollectionItem]
18+
datasources: list[DatasourceItem]
19+
flows: list[FlowItem]
20+
projects: list[ProjectItem]
21+
metrics: list[MetricItem]
22+
views: list[ViewItem]
23+
workbooks: list[WorkbookItem]
2024

2125

2226
class FavoriteItem:
2327
@classmethod
2428
def from_response(cls, xml: Union[str, bytes], namespace: dict) -> FavoriteType:
2529
favorites: FavoriteType = {
30+
"collections": [],
2631
"datasources": [],
2732
"flows": [],
2833
"projects": [],
@@ -32,6 +37,7 @@ def from_response(cls, xml: Union[str, bytes], namespace: dict) -> FavoriteType:
3237
}
3338
parsed_response = fromstring(xml)
3439

40+
collections_xml = parsed_response.findall(".//t:favorite/t:collection", namespace)
3541
datasources_xml = parsed_response.findall(".//t:favorite/t:datasource", namespace)
3642
flows_xml = parsed_response.findall(".//t:favorite/t:flow", namespace)
3743
metrics_xml = parsed_response.findall(".//t:favorite/t:metric", namespace)
@@ -40,13 +46,14 @@ def from_response(cls, xml: Union[str, bytes], namespace: dict) -> FavoriteType:
4046
workbooks_xml = parsed_response.findall(".//t:favorite/t:workbook", namespace)
4147

4248
logger.debug(
43-
"ds: {}, flows: {}, metrics: {}, projects: {}, views: {}, wbs: {}".format(
49+
"ds: {}, flows: {}, metrics: {}, projects: {}, views: {}, wbs: {}, collections: {}".format(
4450
len(datasources_xml),
4551
len(flows_xml),
4652
len(metrics_xml),
4753
len(projects_xml),
4854
len(views_xml),
4955
len(workbooks_xml),
56+
len(collections_xml),
5057
)
5158
)
5259
for datasource in datasources_xml:
@@ -85,5 +92,11 @@ def from_response(cls, xml: Union[str, bytes], namespace: dict) -> FavoriteType:
8592
logger.debug(fav_workbook)
8693
favorites["workbooks"].append(fav_workbook)
8794

95+
for collection in collections_xml:
96+
fav_collection = CollectionItem.from_xml(collection, namespace)
97+
if fav_collection:
98+
logger.debug(fav_collection)
99+
favorites["collections"].append(fav_collection)
100+
88101
logger.debug(favorites)
89102
return favorites

tableauserverclient/models/permissions_item.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Capability:
4444
SaveAs = "SaveAs"
4545
PulseMetricDefine = "PulseMetricDefine"
4646
ExtractRefresh = "ExtractRefresh"
47+
WebAuthoringForFlows = "WebAuthoringForFlows"
4748

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

tableauserverclient/models/user_item.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
if TYPE_CHECKING:
1919
from tableauserverclient.server import Pager
20+
from tableauserverclient.models.favorites_item import FavoriteType
2021

2122

2223
class UserItem:
@@ -131,7 +132,7 @@ def __init__(
131132
self._id: Optional[str] = None
132133
self._last_login: Optional[datetime] = None
133134
self._workbooks = None
134-
self._favorites: Optional[dict[str, list]] = None
135+
self._favorites: Optional["FavoriteType"] = None
135136
self._groups = None
136137
self.email: Optional[str] = None
137138
self.fullname: Optional[str] = None
@@ -218,7 +219,7 @@ def workbooks(self) -> "Pager":
218219
return self._workbooks()
219220

220221
@property
221-
def favorites(self) -> dict[str, list]:
222+
def favorites(self) -> "FavoriteType":
222223
if self._favorites is None:
223224
error = "User item must be populated with favorites first."
224225
raise UnpopulatedPropertyError(error)

tableauserverclient/server/endpoint/datasources_endpoint.py

Lines changed: 94 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
from contextlib import closing
88
from pathlib import Path
9-
from typing import Literal, Optional, TYPE_CHECKING, Union, overload
10-
from collections.abc import Iterable, Mapping, Sequence
9+
from typing import Literal, Optional, TYPE_CHECKING, TypedDict, TypeVar, Union, overload
10+
from collections.abc import Iterable, Sequence
1111

1212
from tableauserverclient.helpers.headers import fix_filename
1313
from tableauserverclient.models.dqw_item import DQWItem
@@ -50,13 +50,50 @@
5050
FileObject = Union[io.BufferedReader, io.BytesIO]
5151
PathOrFile = Union[FilePath, FileObject]
5252

53-
FilePath = Union[str, os.PathLike]
5453
FileObjectR = Union[io.BufferedReader, io.BytesIO]
5554
FileObjectW = Union[io.BufferedWriter, io.BytesIO]
5655
PathOrFileR = Union[FilePath, FileObjectR]
5756
PathOrFileW = Union[FilePath, FileObjectW]
5857

5958

59+
HyperActionCondition = TypedDict(
60+
"HyperActionCondition",
61+
{
62+
"op": str,
63+
"target-col": str,
64+
"source-col": str,
65+
},
66+
)
67+
68+
HyperActionRow = TypedDict(
69+
"HyperActionRow",
70+
{
71+
"action": Literal[
72+
"update",
73+
"upsert",
74+
"delete",
75+
],
76+
"source-table": str,
77+
"target-table": str,
78+
"condition": HyperActionCondition,
79+
},
80+
)
81+
82+
HyperActionTable = TypedDict(
83+
"HyperActionTable",
84+
{
85+
"action": Literal[
86+
"insert",
87+
"replace",
88+
],
89+
"source-table": str,
90+
"target-table": str,
91+
},
92+
)
93+
94+
HyperAction = Union[HyperActionTable, HyperActionRow]
95+
96+
6097
class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]):
6198
def __init__(self, parent_srv: "Server") -> None:
6299
super().__init__(parent_srv)
@@ -191,16 +228,34 @@ def delete(self, datasource_id: str) -> None:
191228
self.delete_request(url)
192229
logger.info(f"Deleted single datasource (ID: {datasource_id})")
193230

231+
T = TypeVar("T", bound=FileObjectW)
232+
233+
@overload
234+
def download(
235+
self,
236+
datasource_id: str,
237+
filepath: T,
238+
include_extract: bool = True,
239+
) -> T: ...
240+
241+
@overload
242+
def download(
243+
self,
244+
datasource_id: str,
245+
filepath: Optional[FilePath] = None,
246+
include_extract: bool = True,
247+
) -> str: ...
248+
194249
# Download 1 datasource by id
195250
@api(version="2.0")
196251
@parameter_added_in(no_extract="2.5")
197252
@parameter_added_in(include_extract="2.5")
198253
def download(
199254
self,
200-
datasource_id: str,
201-
filepath: Optional[PathOrFileW] = None,
202-
include_extract: bool = True,
203-
) -> PathOrFileW:
255+
datasource_id,
256+
filepath=None,
257+
include_extract=True,
258+
):
204259
"""
205260
Downloads the specified data source from a site. The data source is
206261
downloaded as a .tdsx file.
@@ -479,13 +534,13 @@ def publish(
479534
@parameter_added_in(as_job="3.0")
480535
def publish(
481536
self,
482-
datasource_item: DatasourceItem,
483-
file: PathOrFileR,
484-
mode: str,
485-
connection_credentials: Optional[ConnectionCredentials] = None,
486-
connections: Optional[Sequence[ConnectionItem]] = None,
487-
as_job: bool = False,
488-
) -> Union[DatasourceItem, JobItem]:
537+
datasource_item,
538+
file,
539+
mode,
540+
connection_credentials=None,
541+
connections=None,
542+
as_job=False,
543+
):
489544
"""
490545
Publishes a data source to a server, or appends data to an existing
491546
data source.
@@ -631,7 +686,7 @@ def update_hyper_data(
631686
datasource_or_connection_item: Union[DatasourceItem, ConnectionItem, str],
632687
*,
633688
request_id: str,
634-
actions: Sequence[Mapping],
689+
actions: Sequence[HyperAction],
635690
payload: Optional[FilePath] = None,
636691
) -> JobItem:
637692
"""
@@ -898,15 +953,35 @@ def _get_datasource_revisions(
898953
revisions = RevisionItem.from_response(server_response.content, self.parent_srv.namespace, datasource_item)
899954
return revisions
900955

901-
# Download 1 datasource revision by revision number
902-
@api(version="2.3")
956+
T = TypeVar("T", bound=FileObjectW)
957+
958+
@overload
959+
def download_revision(
960+
self,
961+
datasource_id: str,
962+
revision_number: Optional[str],
963+
filepath: T,
964+
include_extract: bool = True,
965+
) -> T: ...
966+
967+
@overload
903968
def download_revision(
904969
self,
905970
datasource_id: str,
906971
revision_number: Optional[str],
907-
filepath: Optional[PathOrFileW] = None,
972+
filepath: Optional[FilePath] = None,
908973
include_extract: bool = True,
909-
) -> PathOrFileW:
974+
) -> str: ...
975+
976+
# Download 1 datasource revision by revision number
977+
@api(version="2.3")
978+
def download_revision(
979+
self,
980+
datasource_id,
981+
revision_number,
982+
filepath=None,
983+
include_extract=True,
984+
):
910985
"""
911986
Downloads a specific version of a data source prior to the current one
912987
in .tdsx format. To download the current version of a data source set

test/assets/favorites_get.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,17 @@
4343
<tags />
4444
</datasource>
4545
</favorite>
46+
<favorite>
47+
<collection id="8c57cb8a-d65f-4a32-813e-5a3f86e8f94e"
48+
name="sample collection"
49+
description="description for sample collection"
50+
totalItemCount="3"
51+
permissionedItemCount="2"
52+
visibility="Private"
53+
createdAt="2016-08-11T21:22:40Z"
54+
updatedAt="2016-08-11T21:34:17Z">
55+
<owner id="5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" />
56+
</collection>
57+
</favorite>
4658
</favorites>
47-
</tsResponse>
59+
</tsResponse>

0 commit comments

Comments
 (0)