tableauserverclient 0.31__py3-none-any.whl → 0.33__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tableauserverclient/__init__.py +74 -4
- tableauserverclient/_version.py +3 -3
- tableauserverclient/config.py +16 -4
- tableauserverclient/models/__init__.py +100 -37
- tableauserverclient/models/connection_item.py +4 -2
- tableauserverclient/models/database_item.py +6 -0
- tableauserverclient/models/datasource_item.py +18 -6
- tableauserverclient/models/favorites_item.py +7 -7
- tableauserverclient/models/flow_item.py +6 -6
- tableauserverclient/models/groupset_item.py +53 -0
- tableauserverclient/models/interval_item.py +27 -14
- tableauserverclient/models/job_item.py +18 -2
- tableauserverclient/models/linked_tasks_item.py +102 -0
- tableauserverclient/models/permissions_item.py +53 -5
- tableauserverclient/models/project_item.py +4 -3
- tableauserverclient/models/reference_item.py +5 -0
- tableauserverclient/models/tableau_auth.py +22 -23
- tableauserverclient/models/tableau_types.py +9 -7
- tableauserverclient/models/virtual_connection_item.py +77 -0
- tableauserverclient/server/__init__.py +83 -8
- tableauserverclient/server/endpoint/__init__.py +69 -28
- tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
- tableauserverclient/server/endpoint/custom_views_endpoint.py +66 -5
- tableauserverclient/server/endpoint/databases_endpoint.py +21 -18
- tableauserverclient/server/endpoint/datasources_endpoint.py +115 -35
- tableauserverclient/server/endpoint/endpoint.py +53 -20
- tableauserverclient/server/endpoint/favorites_endpoint.py +1 -1
- tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
- tableauserverclient/server/endpoint/flow_runs_endpoint.py +45 -5
- tableauserverclient/server/endpoint/flows_endpoint.py +43 -16
- tableauserverclient/server/endpoint/groups_endpoint.py +85 -31
- tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
- tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
- tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
- tableauserverclient/server/endpoint/metadata_endpoint.py +3 -3
- tableauserverclient/server/endpoint/metrics_endpoint.py +1 -1
- tableauserverclient/server/endpoint/projects_endpoint.py +50 -18
- tableauserverclient/server/endpoint/resource_tagger.py +135 -3
- tableauserverclient/server/endpoint/tables_endpoint.py +19 -16
- tableauserverclient/server/endpoint/users_endpoint.py +40 -1
- tableauserverclient/server/endpoint/views_endpoint.py +97 -10
- tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
- tableauserverclient/server/endpoint/workbooks_endpoint.py +97 -45
- tableauserverclient/server/pager.py +46 -46
- tableauserverclient/server/query.py +62 -33
- tableauserverclient/server/request_factory.py +192 -49
- tableauserverclient/server/request_options.py +4 -2
- tableauserverclient/server/server.py +13 -6
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -16
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/RECORD +54 -48
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
- {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import logging
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
from tableauserverclient.config import BYTES_PER_MB, FILESIZE_LIMIT_MB
|
|
8
|
+
from tableauserverclient.filesys_helpers import get_file_object_size
|
|
9
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
10
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
6
11
|
from tableauserverclient.models import CustomViewItem, PaginationItem
|
|
7
12
|
from tableauserverclient.server import RequestFactory, RequestOptions, ImageRequestOptions
|
|
8
13
|
|
|
@@ -16,8 +21,17 @@ Delete a custom view
|
|
|
16
21
|
update the name or owner of a custom view.
|
|
17
22
|
"""
|
|
18
23
|
|
|
24
|
+
FilePath = Union[str, os.PathLike]
|
|
25
|
+
FileObject = Union[io.BufferedReader, io.BytesIO]
|
|
26
|
+
FileObjectR = Union[io.BufferedReader, io.BytesIO]
|
|
27
|
+
FileObjectW = Union[io.BufferedWriter, io.BytesIO]
|
|
28
|
+
PathOrFileR = Union[FilePath, FileObjectR]
|
|
29
|
+
PathOrFileW = Union[FilePath, FileObjectW]
|
|
30
|
+
io_types_r = (io.BufferedReader, io.BytesIO)
|
|
31
|
+
io_types_w = (io.BufferedWriter, io.BytesIO)
|
|
32
|
+
|
|
19
33
|
|
|
20
|
-
class CustomViews(QuerysetEndpoint):
|
|
34
|
+
class CustomViews(QuerysetEndpoint[CustomViewItem]):
|
|
21
35
|
def __init__(self, parent_srv):
|
|
22
36
|
super(CustomViews, self).__init__(parent_srv)
|
|
23
37
|
|
|
@@ -25,6 +39,10 @@ class CustomViews(QuerysetEndpoint):
|
|
|
25
39
|
def baseurl(self) -> str:
|
|
26
40
|
return "{0}/sites/{1}/customviews".format(self.parent_srv.baseurl, self.parent_srv.site_id)
|
|
27
41
|
|
|
42
|
+
@property
|
|
43
|
+
def expurl(self) -> str:
|
|
44
|
+
return f"{self.parent_srv._server_address}/api/exp/sites/{self.parent_srv.site_id}/customviews"
|
|
45
|
+
|
|
28
46
|
"""
|
|
29
47
|
If the request has no filter parameters: Administrators will see all custom views.
|
|
30
48
|
Other users will see only custom views that they own.
|
|
@@ -102,3 +120,46 @@ class CustomViews(QuerysetEndpoint):
|
|
|
102
120
|
url = "{0}/{1}".format(self.baseurl, view_id)
|
|
103
121
|
self.delete_request(url)
|
|
104
122
|
logger.info("Deleted single custom view (ID: {0})".format(view_id))
|
|
123
|
+
|
|
124
|
+
@api(version="3.21")
|
|
125
|
+
def download(self, view_item: CustomViewItem, file: PathOrFileW) -> PathOrFileW:
|
|
126
|
+
url = f"{self.expurl}/{view_item.id}/content"
|
|
127
|
+
server_response = self.get_request(url)
|
|
128
|
+
if isinstance(file, io_types_w):
|
|
129
|
+
file.write(server_response.content)
|
|
130
|
+
return file
|
|
131
|
+
|
|
132
|
+
with open(file, "wb") as f:
|
|
133
|
+
f.write(server_response.content)
|
|
134
|
+
|
|
135
|
+
return file
|
|
136
|
+
|
|
137
|
+
@api(version="3.21")
|
|
138
|
+
def publish(self, view_item: CustomViewItem, file: PathOrFileR) -> Optional[CustomViewItem]:
|
|
139
|
+
url = self.expurl
|
|
140
|
+
if isinstance(file, io_types_r):
|
|
141
|
+
size = get_file_object_size(file)
|
|
142
|
+
elif isinstance(file, (str, Path)) and (p := Path(file)).is_file():
|
|
143
|
+
size = p.stat().st_size
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError("File path or file object required for publishing custom view.")
|
|
146
|
+
|
|
147
|
+
if size >= FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
148
|
+
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
149
|
+
url = f"{url}?uploadSessionId={upload_session_id}"
|
|
150
|
+
xml_request, content_type = RequestFactory.CustomView.publish_req_chunked(view_item)
|
|
151
|
+
else:
|
|
152
|
+
if isinstance(file, io_types_r):
|
|
153
|
+
file.seek(0)
|
|
154
|
+
contents = file.read()
|
|
155
|
+
if view_item.name is None:
|
|
156
|
+
raise MissingRequiredFieldError("Custom view item missing name.")
|
|
157
|
+
filename = view_item.name
|
|
158
|
+
elif isinstance(file, (str, Path)):
|
|
159
|
+
filename = Path(file).name
|
|
160
|
+
contents = Path(file).read_bytes()
|
|
161
|
+
|
|
162
|
+
xml_request, content_type = RequestFactory.CustomView.publish_req(view_item, filename, contents)
|
|
163
|
+
|
|
164
|
+
server_response = self.post_request(url, xml_request, content_type)
|
|
165
|
+
return CustomViewItem.from_response(server_response.content, self.parent_srv.namespace)
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from .
|
|
5
|
-
from .endpoint import
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
2
|
+
from typing import Union, Iterable, Set
|
|
3
|
+
|
|
4
|
+
from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint
|
|
5
|
+
from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
|
|
6
|
+
from tableauserverclient.server.endpoint.endpoint import api, Endpoint
|
|
7
|
+
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
|
|
8
|
+
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
|
|
9
|
+
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
|
|
8
10
|
from tableauserverclient.server import RequestFactory
|
|
9
11
|
from tableauserverclient.models import DatabaseItem, TableItem, PaginationItem, Resource
|
|
10
12
|
|
|
11
13
|
from tableauserverclient.helpers.logging import logger
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
class Databases(Endpoint):
|
|
16
|
+
class Databases(Endpoint, TaggingMixin):
|
|
15
17
|
def __init__(self, parent_srv):
|
|
16
18
|
super(Databases, self).__init__(parent_srv)
|
|
17
19
|
|
|
@@ -88,17 +90,6 @@ class Databases(Endpoint):
|
|
|
88
90
|
def populate_permissions(self, item):
|
|
89
91
|
self._permissions.populate(item)
|
|
90
92
|
|
|
91
|
-
@api(version="3.5")
|
|
92
|
-
def update_permission(self, item, rules):
|
|
93
|
-
import warnings
|
|
94
|
-
|
|
95
|
-
warnings.warn(
|
|
96
|
-
"Server.databases.update_permission is deprecated, "
|
|
97
|
-
"please use Server.databases.update_permissions instead.",
|
|
98
|
-
DeprecationWarning,
|
|
99
|
-
)
|
|
100
|
-
return self._permissions.update(item, rules)
|
|
101
|
-
|
|
102
93
|
@api(version="3.5")
|
|
103
94
|
def update_permissions(self, item, rules):
|
|
104
95
|
return self._permissions.update(item, rules)
|
|
@@ -134,3 +125,15 @@ class Databases(Endpoint):
|
|
|
134
125
|
@api(version="3.5")
|
|
135
126
|
def delete_dqw(self, item):
|
|
136
127
|
self._data_quality_warnings.clear(item)
|
|
128
|
+
|
|
129
|
+
@api(version="3.9")
|
|
130
|
+
def add_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> Set[str]:
|
|
131
|
+
return super().add_tags(item, tags)
|
|
132
|
+
|
|
133
|
+
@api(version="3.9")
|
|
134
|
+
def delete_tags(self, item: Union[DatabaseItem, str], tags: Iterable[str]) -> None:
|
|
135
|
+
super().delete_tags(item, tags)
|
|
136
|
+
|
|
137
|
+
@api(version="3.9")
|
|
138
|
+
def update_tags(self, item: DatabaseItem) -> None:
|
|
139
|
+
raise NotImplementedError("Update tags is not supported for databases.")
|
|
@@ -6,22 +6,23 @@ import os
|
|
|
6
6
|
|
|
7
7
|
from contextlib import closing
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import List, Mapping, Optional, Sequence, Tuple, TYPE_CHECKING, Union
|
|
9
|
+
from typing import Iterable, List, Mapping, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union
|
|
10
10
|
|
|
11
11
|
from tableauserverclient.helpers.headers import fix_filename
|
|
12
|
+
from tableauserverclient.server.query import QuerySet
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from tableauserverclient.server import Server
|
|
15
16
|
from tableauserverclient.models import PermissionsRule
|
|
16
17
|
from .schedules_endpoint import AddResponse
|
|
17
18
|
|
|
18
|
-
from .dqw_endpoint import _DataQualityWarningEndpoint
|
|
19
|
-
from .endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
20
|
-
from .exceptions import InternalServerError, MissingRequiredFieldError
|
|
21
|
-
from .permissions_endpoint import _PermissionsEndpoint
|
|
22
|
-
from .resource_tagger import
|
|
19
|
+
from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
|
|
20
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, parameter_added_in
|
|
21
|
+
from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
|
|
22
|
+
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
|
|
23
|
+
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
|
|
23
24
|
|
|
24
|
-
from tableauserverclient.config import ALLOWED_FILE_EXTENSIONS, FILESIZE_LIMIT_MB, BYTES_PER_MB,
|
|
25
|
+
from tableauserverclient.config import ALLOWED_FILE_EXTENSIONS, FILESIZE_LIMIT_MB, BYTES_PER_MB, config
|
|
25
26
|
from tableauserverclient.filesys_helpers import (
|
|
26
27
|
make_download_path,
|
|
27
28
|
get_file_type,
|
|
@@ -54,10 +55,9 @@ PathOrFileR = Union[FilePath, FileObjectR]
|
|
|
54
55
|
PathOrFileW = Union[FilePath, FileObjectW]
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
class Datasources(QuerysetEndpoint):
|
|
58
|
+
class Datasources(QuerysetEndpoint[DatasourceItem], TaggingMixin[DatasourceItem]):
|
|
58
59
|
def __init__(self, parent_srv: "Server") -> None:
|
|
59
60
|
super(Datasources, self).__init__(parent_srv)
|
|
60
|
-
self._resource_tagger = _ResourceTagger(parent_srv)
|
|
61
61
|
self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
|
|
62
62
|
self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "datasource")
|
|
63
63
|
|
|
@@ -126,9 +126,13 @@ class Datasources(QuerysetEndpoint):
|
|
|
126
126
|
datasource_id: str,
|
|
127
127
|
filepath: Optional[PathOrFileW] = None,
|
|
128
128
|
include_extract: bool = True,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
) -> PathOrFileW:
|
|
130
|
+
return self.download_revision(
|
|
131
|
+
datasource_id,
|
|
132
|
+
None,
|
|
133
|
+
filepath,
|
|
134
|
+
include_extract,
|
|
135
|
+
)
|
|
132
136
|
|
|
133
137
|
# Update datasource
|
|
134
138
|
@api(version="2.0")
|
|
@@ -145,7 +149,7 @@ class Datasources(QuerysetEndpoint):
|
|
|
145
149
|
)
|
|
146
150
|
raise MissingRequiredFieldError(error)
|
|
147
151
|
|
|
148
|
-
self.
|
|
152
|
+
self.update_tags(datasource_item)
|
|
149
153
|
|
|
150
154
|
# Update the datasource itself
|
|
151
155
|
url = "{0}/{1}".format(self.baseurl, datasource_item.id)
|
|
@@ -268,7 +272,7 @@ class Datasources(QuerysetEndpoint):
|
|
|
268
272
|
if file_size >= FILESIZE_LIMIT_MB * BYTES_PER_MB:
|
|
269
273
|
logger.info(
|
|
270
274
|
"Publishing {} to server with chunking method (datasource over {}MB, chunk size {}MB)".format(
|
|
271
|
-
filename, FILESIZE_LIMIT_MB, CHUNK_SIZE_MB
|
|
275
|
+
filename, FILESIZE_LIMIT_MB, config.CHUNK_SIZE_MB
|
|
272
276
|
)
|
|
273
277
|
)
|
|
274
278
|
upload_session_id = self.parent_srv.fileuploads.upload(file)
|
|
@@ -351,17 +355,6 @@ class Datasources(QuerysetEndpoint):
|
|
|
351
355
|
def populate_permissions(self, item: DatasourceItem) -> None:
|
|
352
356
|
self._permissions.populate(item)
|
|
353
357
|
|
|
354
|
-
@api(version="2.0")
|
|
355
|
-
def update_permission(self, item, permission_item):
|
|
356
|
-
import warnings
|
|
357
|
-
|
|
358
|
-
warnings.warn(
|
|
359
|
-
"Server.datasources.update_permission is deprecated, "
|
|
360
|
-
"please use Server.datasources.update_permissions instead.",
|
|
361
|
-
DeprecationWarning,
|
|
362
|
-
)
|
|
363
|
-
self._permissions.update(item, permission_item)
|
|
364
|
-
|
|
365
358
|
@api(version="2.0")
|
|
366
359
|
def update_permissions(self, item: DatasourceItem, permission_item: List["PermissionsRule"]) -> None:
|
|
367
360
|
self._permissions.update(item, permission_item)
|
|
@@ -412,10 +405,9 @@ class Datasources(QuerysetEndpoint):
|
|
|
412
405
|
def download_revision(
|
|
413
406
|
self,
|
|
414
407
|
datasource_id: str,
|
|
415
|
-
revision_number: str,
|
|
408
|
+
revision_number: Optional[str],
|
|
416
409
|
filepath: Optional[PathOrFileW] = None,
|
|
417
410
|
include_extract: bool = True,
|
|
418
|
-
no_extract: Optional[bool] = None,
|
|
419
411
|
) -> PathOrFileW:
|
|
420
412
|
if not datasource_id:
|
|
421
413
|
error = "Datasource ID undefined."
|
|
@@ -424,14 +416,6 @@ class Datasources(QuerysetEndpoint):
|
|
|
424
416
|
url = "{0}/{1}/content".format(self.baseurl, datasource_id)
|
|
425
417
|
else:
|
|
426
418
|
url = "{0}/{1}/revisions/{2}/content".format(self.baseurl, datasource_id, revision_number)
|
|
427
|
-
if no_extract is False or no_extract is True:
|
|
428
|
-
import warnings
|
|
429
|
-
|
|
430
|
-
warnings.warn(
|
|
431
|
-
"no_extract is deprecated, use include_extract instead.",
|
|
432
|
-
DeprecationWarning,
|
|
433
|
-
)
|
|
434
|
-
include_extract = not no_extract
|
|
435
419
|
|
|
436
420
|
if not include_extract:
|
|
437
421
|
url += "?includeExtract=False"
|
|
@@ -475,3 +459,99 @@ class Datasources(QuerysetEndpoint):
|
|
|
475
459
|
self, schedule_id: str, item: DatasourceItem
|
|
476
460
|
) -> List["AddResponse"]: # actually should return a task
|
|
477
461
|
return self.parent_srv.schedules.add_to_schedule(schedule_id, datasource=item)
|
|
462
|
+
|
|
463
|
+
@api(version="1.0")
|
|
464
|
+
def add_tags(self, item: Union[DatasourceItem, str], tags: Union[Iterable[str], str]) -> Set[str]:
|
|
465
|
+
return super().add_tags(item, tags)
|
|
466
|
+
|
|
467
|
+
@api(version="1.0")
|
|
468
|
+
def delete_tags(self, item: Union[DatasourceItem, str], tags: Union[Iterable[str], str]) -> None:
|
|
469
|
+
return super().delete_tags(item, tags)
|
|
470
|
+
|
|
471
|
+
@api(version="1.0")
|
|
472
|
+
def update_tags(self, item: DatasourceItem) -> None:
|
|
473
|
+
return super().update_tags(item)
|
|
474
|
+
|
|
475
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[DatasourceItem]:
|
|
476
|
+
"""
|
|
477
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
478
|
+
size can be specified to limit the number of items returned in a single
|
|
479
|
+
request. If not specified, the default page size is 100. Page size can
|
|
480
|
+
be an integer between 1 and 1000.
|
|
481
|
+
|
|
482
|
+
No positional arguments are allowed. All filters must be specified as
|
|
483
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
484
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
485
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
486
|
+
names can either be in snake_case or camelCase.
|
|
487
|
+
|
|
488
|
+
This endpoint supports the following fields and operators:
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
authentication_type=...
|
|
492
|
+
authentication_type__in=...
|
|
493
|
+
connected_workbook_type=...
|
|
494
|
+
connected_workbook_type__gt=...
|
|
495
|
+
connected_workbook_type__gte=...
|
|
496
|
+
connected_workbook_type__lt=...
|
|
497
|
+
connected_workbook_type__lte=...
|
|
498
|
+
connection_to=...
|
|
499
|
+
connection_to__in=...
|
|
500
|
+
connection_type=...
|
|
501
|
+
connection_type__in=...
|
|
502
|
+
content_url=...
|
|
503
|
+
content_url__in=...
|
|
504
|
+
created_at=...
|
|
505
|
+
created_at__gt=...
|
|
506
|
+
created_at__gte=...
|
|
507
|
+
created_at__lt=...
|
|
508
|
+
created_at__lte=...
|
|
509
|
+
database_name=...
|
|
510
|
+
database_name__in=...
|
|
511
|
+
database_user_name=...
|
|
512
|
+
database_user_name__in=...
|
|
513
|
+
description=...
|
|
514
|
+
description__in=...
|
|
515
|
+
favorites_total=...
|
|
516
|
+
favorites_total__gt=...
|
|
517
|
+
favorites_total__gte=...
|
|
518
|
+
favorites_total__lt=...
|
|
519
|
+
favorites_total__lte=...
|
|
520
|
+
has_alert=...
|
|
521
|
+
has_embedded_password=...
|
|
522
|
+
has_extracts=...
|
|
523
|
+
is_certified=...
|
|
524
|
+
is_connectable=...
|
|
525
|
+
is_default_port=...
|
|
526
|
+
is_hierarchical=...
|
|
527
|
+
is_published=...
|
|
528
|
+
name=...
|
|
529
|
+
name__in=...
|
|
530
|
+
owner_domain=...
|
|
531
|
+
owner_domain__in=...
|
|
532
|
+
owner_email=...
|
|
533
|
+
owner_name=...
|
|
534
|
+
owner_name__in=...
|
|
535
|
+
project_name=...
|
|
536
|
+
project_name__in=...
|
|
537
|
+
server_name=...
|
|
538
|
+
server_name__in=...
|
|
539
|
+
server_port=...
|
|
540
|
+
size=...
|
|
541
|
+
size__gt=...
|
|
542
|
+
size__gte=...
|
|
543
|
+
size__lt=...
|
|
544
|
+
size__lte=...
|
|
545
|
+
table_name=...
|
|
546
|
+
table_name__in=...
|
|
547
|
+
tags=...
|
|
548
|
+
tags__in=...
|
|
549
|
+
type=...
|
|
550
|
+
updated_at=...
|
|
551
|
+
updated_at__gt=...
|
|
552
|
+
updated_at__gte=...
|
|
553
|
+
updated_at__lt=...
|
|
554
|
+
updated_at__lte=...
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|
|
@@ -1,26 +1,41 @@
|
|
|
1
|
+
from typing_extensions import Concatenate, ParamSpec
|
|
1
2
|
from tableauserverclient import datetime_helpers as datetime
|
|
2
3
|
|
|
4
|
+
import abc
|
|
3
5
|
from packaging.version import Version
|
|
4
6
|
from functools import wraps
|
|
5
7
|
from xml.etree.ElementTree import ParseError
|
|
6
|
-
from typing import
|
|
8
|
+
from typing import (
|
|
9
|
+
Any,
|
|
10
|
+
Callable,
|
|
11
|
+
Dict,
|
|
12
|
+
Generic,
|
|
13
|
+
List,
|
|
14
|
+
Optional,
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Tuple,
|
|
17
|
+
TypeVar,
|
|
18
|
+
Union,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from tableauserverclient.models.pagination_item import PaginationItem
|
|
22
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
7
23
|
|
|
8
|
-
from .exceptions import (
|
|
24
|
+
from tableauserverclient.server.endpoint.exceptions import (
|
|
9
25
|
ServerResponseError,
|
|
10
26
|
InternalServerError,
|
|
11
27
|
NonXMLResponseError,
|
|
12
28
|
NotSignedInError,
|
|
13
29
|
)
|
|
14
|
-
from
|
|
30
|
+
from tableauserverclient.server.exceptions import EndpointUnavailableError
|
|
15
31
|
|
|
16
32
|
from tableauserverclient.server.query import QuerySet
|
|
17
33
|
from tableauserverclient import helpers, get_versions
|
|
18
34
|
|
|
19
35
|
from tableauserverclient.helpers.logging import logger
|
|
20
|
-
from tableauserverclient.config import DELAY_SLEEP_SECONDS
|
|
21
36
|
|
|
22
37
|
if TYPE_CHECKING:
|
|
23
|
-
from
|
|
38
|
+
from tableauserverclient.server.server import Server
|
|
24
39
|
from requests import Response
|
|
25
40
|
|
|
26
41
|
|
|
@@ -34,7 +49,7 @@ TABLEAU_AUTH_HEADER = "x-tableau-auth"
|
|
|
34
49
|
USER_AGENT_HEADER = "User-Agent"
|
|
35
50
|
|
|
36
51
|
|
|
37
|
-
class Endpoint
|
|
52
|
+
class Endpoint:
|
|
38
53
|
def __init__(self, parent_srv: "Server"):
|
|
39
54
|
self.parent_srv = parent_srv
|
|
40
55
|
|
|
@@ -129,7 +144,9 @@ class Endpoint(object):
|
|
|
129
144
|
|
|
130
145
|
loggable_response = self.log_response_safely(server_response)
|
|
131
146
|
logger.debug("Server response from {0}".format(url))
|
|
132
|
-
#
|
|
147
|
+
# uncomment the following to log full responses in debug mode
|
|
148
|
+
# BE CAREFUL WHEN SHARING THESE RESULTS - MAY CONTAIN YOUR SENSITIVE DATA
|
|
149
|
+
# logger.debug(loggable_response)
|
|
133
150
|
|
|
134
151
|
if content_type == "application/xml":
|
|
135
152
|
self.parent_srv._namespace.detect(server_response.content)
|
|
@@ -228,7 +245,12 @@ class Endpoint(object):
|
|
|
228
245
|
)
|
|
229
246
|
|
|
230
247
|
|
|
231
|
-
|
|
248
|
+
E = TypeVar("E", bound="Endpoint")
|
|
249
|
+
P = ParamSpec("P")
|
|
250
|
+
R = TypeVar("R")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def api(version: str) -> Callable[[Callable[Concatenate[E, P], R]], Callable[Concatenate[E, P], R]]:
|
|
232
254
|
"""Annotate the minimum supported version for an endpoint.
|
|
233
255
|
|
|
234
256
|
Checks the version on the server object and compares normalized versions.
|
|
@@ -247,9 +269,9 @@ def api(version):
|
|
|
247
269
|
>>> ...
|
|
248
270
|
"""
|
|
249
271
|
|
|
250
|
-
def _decorator(func):
|
|
272
|
+
def _decorator(func: Callable[Concatenate[E, P], R]) -> Callable[Concatenate[E, P], R]:
|
|
251
273
|
@wraps(func)
|
|
252
|
-
def wrapper(self, *args, **kwargs):
|
|
274
|
+
def wrapper(self: E, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
253
275
|
self.parent_srv.assert_at_least_version(version, self.__class__.__name__)
|
|
254
276
|
return func(self, *args, **kwargs)
|
|
255
277
|
|
|
@@ -258,7 +280,7 @@ def api(version):
|
|
|
258
280
|
return _decorator
|
|
259
281
|
|
|
260
282
|
|
|
261
|
-
def parameter_added_in(**params):
|
|
283
|
+
def parameter_added_in(**params: str) -> Callable[[Callable[Concatenate[E, P], R]], Callable[Concatenate[E, P], R]]:
|
|
262
284
|
"""Annotate minimum versions for new parameters or request options on an endpoint.
|
|
263
285
|
|
|
264
286
|
The api decorator documents when an endpoint was added, this decorator annotates
|
|
@@ -281,9 +303,9 @@ def parameter_added_in(**params):
|
|
|
281
303
|
>>> ...
|
|
282
304
|
"""
|
|
283
305
|
|
|
284
|
-
def _decorator(func):
|
|
306
|
+
def _decorator(func: Callable[Concatenate[E, P], R]) -> Callable[Concatenate[E, P], R]:
|
|
285
307
|
@wraps(func)
|
|
286
|
-
def wrapper(self, *args, **kwargs):
|
|
308
|
+
def wrapper(self: E, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
287
309
|
import warnings
|
|
288
310
|
|
|
289
311
|
server_ver = Version(self.parent_srv.version or "0.0")
|
|
@@ -300,25 +322,36 @@ def parameter_added_in(**params):
|
|
|
300
322
|
return _decorator
|
|
301
323
|
|
|
302
324
|
|
|
303
|
-
|
|
325
|
+
T = TypeVar("T")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class QuerysetEndpoint(Endpoint, Generic[T]):
|
|
304
329
|
@api(version="2.0")
|
|
305
|
-
def all(self, *args, **kwargs):
|
|
306
|
-
|
|
330
|
+
def all(self, *args, page_size: Optional[int] = None, **kwargs) -> QuerySet[T]:
|
|
331
|
+
if args or kwargs:
|
|
332
|
+
raise ValueError(".all method takes no arguments.")
|
|
333
|
+
queryset = QuerySet(self, page_size=page_size)
|
|
307
334
|
return queryset
|
|
308
335
|
|
|
309
336
|
@api(version="2.0")
|
|
310
|
-
def filter(self, *_, **kwargs) -> QuerySet:
|
|
337
|
+
def filter(self, *_, page_size: Optional[int] = None, **kwargs) -> QuerySet[T]:
|
|
311
338
|
if _:
|
|
312
339
|
raise RuntimeError("Only keyword arguments accepted.")
|
|
313
|
-
queryset = QuerySet(self).filter(**kwargs)
|
|
340
|
+
queryset = QuerySet(self, page_size=page_size).filter(**kwargs)
|
|
314
341
|
return queryset
|
|
315
342
|
|
|
316
343
|
@api(version="2.0")
|
|
317
|
-
def order_by(self, *args, **kwargs):
|
|
344
|
+
def order_by(self, *args, **kwargs) -> QuerySet[T]:
|
|
345
|
+
if kwargs:
|
|
346
|
+
raise ValueError(".order_by does not accept keyword arguments.")
|
|
318
347
|
queryset = QuerySet(self).order_by(*args)
|
|
319
348
|
return queryset
|
|
320
349
|
|
|
321
350
|
@api(version="2.0")
|
|
322
|
-
def paginate(self, **kwargs):
|
|
351
|
+
def paginate(self, **kwargs) -> QuerySet[T]:
|
|
323
352
|
queryset = QuerySet(self).paginate(**kwargs)
|
|
324
353
|
return queryset
|
|
354
|
+
|
|
355
|
+
@abc.abstractmethod
|
|
356
|
+
def get(self, request_options: Optional[RequestOptions] = None) -> Tuple[List[T], PaginationItem]:
|
|
357
|
+
raise NotImplementedError(f".get has not been implemented for {self.__class__.__qualname__}")
|
|
@@ -2,7 +2,7 @@ from .endpoint import Endpoint, api
|
|
|
2
2
|
from tableauserverclient import datetime_helpers as datetime
|
|
3
3
|
from tableauserverclient.helpers.logging import logger
|
|
4
4
|
|
|
5
|
-
from tableauserverclient.config import BYTES_PER_MB,
|
|
5
|
+
from tableauserverclient.config import BYTES_PER_MB, config
|
|
6
6
|
from tableauserverclient.models import FileuploadItem
|
|
7
7
|
from tableauserverclient.server import RequestFactory
|
|
8
8
|
|
|
@@ -41,7 +41,7 @@ class Fileuploads(Endpoint):
|
|
|
41
41
|
|
|
42
42
|
try:
|
|
43
43
|
while True:
|
|
44
|
-
chunked_content = file_content.read(CHUNK_SIZE_MB * BYTES_PER_MB)
|
|
44
|
+
chunked_content = file_content.read(config.CHUNK_SIZE_MB * BYTES_PER_MB)
|
|
45
45
|
if not chunked_content:
|
|
46
46
|
break
|
|
47
47
|
yield chunked_content
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import List, Optional, Tuple, TYPE_CHECKING
|
|
3
3
|
|
|
4
|
-
from .endpoint import QuerysetEndpoint, api
|
|
5
|
-
from .exceptions import FlowRunFailedException, FlowRunCancelledException
|
|
4
|
+
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
|
|
5
|
+
from tableauserverclient.server.endpoint.exceptions import FlowRunFailedException, FlowRunCancelledException
|
|
6
6
|
from tableauserverclient.models import FlowRunItem, PaginationItem
|
|
7
7
|
from tableauserverclient.exponential_backoff import ExponentialBackoffTimer
|
|
8
8
|
|
|
9
9
|
from tableauserverclient.helpers.logging import logger
|
|
10
|
+
from tableauserverclient.server.query import QuerySet
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
|
-
from
|
|
13
|
-
from
|
|
13
|
+
from tableauserverclient.server.server import Server
|
|
14
|
+
from tableauserverclient.server.request_options import RequestOptions
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
class FlowRuns(QuerysetEndpoint):
|
|
17
|
+
class FlowRuns(QuerysetEndpoint[FlowRunItem]):
|
|
17
18
|
def __init__(self, parent_srv: "Server") -> None:
|
|
18
19
|
super(FlowRuns, self).__init__(parent_srv)
|
|
19
20
|
return None
|
|
@@ -78,3 +79,42 @@ class FlowRuns(QuerysetEndpoint):
|
|
|
78
79
|
raise FlowRunCancelledException(flow_run)
|
|
79
80
|
else:
|
|
80
81
|
raise AssertionError("Unexpected status in flow_run", flow_run)
|
|
82
|
+
|
|
83
|
+
def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[FlowRunItem]:
|
|
84
|
+
"""
|
|
85
|
+
Queries the Tableau Server for items using the specified filters. Page
|
|
86
|
+
size can be specified to limit the number of items returned in a single
|
|
87
|
+
request. If not specified, the default page size is 100. Page size can
|
|
88
|
+
be an integer between 1 and 1000.
|
|
89
|
+
|
|
90
|
+
No positional arguments are allowed. All filters must be specified as
|
|
91
|
+
keyword arguments. If you use the equality operator, you can specify it
|
|
92
|
+
through <field_name>=<value>. If you want to use a different operator,
|
|
93
|
+
you can specify it through <field_name>__<operator>=<value>. Field
|
|
94
|
+
names can either be in snake_case or camelCase.
|
|
95
|
+
|
|
96
|
+
This endpoint supports the following fields and operators:
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
complete_at=...
|
|
100
|
+
complete_at__gt=...
|
|
101
|
+
complete_at__gte=...
|
|
102
|
+
complete_at__lt=...
|
|
103
|
+
complete_at__lte=...
|
|
104
|
+
flow_id=...
|
|
105
|
+
flow_id__in=...
|
|
106
|
+
progress=...
|
|
107
|
+
progress__gt=...
|
|
108
|
+
progress__gte=...
|
|
109
|
+
progress__lt=...
|
|
110
|
+
progress__lte=...
|
|
111
|
+
started_at=...
|
|
112
|
+
started_at__gt=...
|
|
113
|
+
started_at__gte=...
|
|
114
|
+
started_at__lt=...
|
|
115
|
+
started_at__lte=...
|
|
116
|
+
user_id=...
|
|
117
|
+
user_id__in=...
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
return super().filter(*invalid, page_size=page_size, **kwargs)
|