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.
Files changed (54) hide show
  1. tableauserverclient/__init__.py +74 -4
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +16 -4
  4. tableauserverclient/models/__init__.py +100 -37
  5. tableauserverclient/models/connection_item.py +4 -2
  6. tableauserverclient/models/database_item.py +6 -0
  7. tableauserverclient/models/datasource_item.py +18 -6
  8. tableauserverclient/models/favorites_item.py +7 -7
  9. tableauserverclient/models/flow_item.py +6 -6
  10. tableauserverclient/models/groupset_item.py +53 -0
  11. tableauserverclient/models/interval_item.py +27 -14
  12. tableauserverclient/models/job_item.py +18 -2
  13. tableauserverclient/models/linked_tasks_item.py +102 -0
  14. tableauserverclient/models/permissions_item.py +53 -5
  15. tableauserverclient/models/project_item.py +4 -3
  16. tableauserverclient/models/reference_item.py +5 -0
  17. tableauserverclient/models/tableau_auth.py +22 -23
  18. tableauserverclient/models/tableau_types.py +9 -7
  19. tableauserverclient/models/virtual_connection_item.py +77 -0
  20. tableauserverclient/server/__init__.py +83 -8
  21. tableauserverclient/server/endpoint/__init__.py +69 -28
  22. tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
  23. tableauserverclient/server/endpoint/custom_views_endpoint.py +66 -5
  24. tableauserverclient/server/endpoint/databases_endpoint.py +21 -18
  25. tableauserverclient/server/endpoint/datasources_endpoint.py +115 -35
  26. tableauserverclient/server/endpoint/endpoint.py +53 -20
  27. tableauserverclient/server/endpoint/favorites_endpoint.py +1 -1
  28. tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
  29. tableauserverclient/server/endpoint/flow_runs_endpoint.py +45 -5
  30. tableauserverclient/server/endpoint/flows_endpoint.py +43 -16
  31. tableauserverclient/server/endpoint/groups_endpoint.py +85 -31
  32. tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
  33. tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
  34. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
  35. tableauserverclient/server/endpoint/metadata_endpoint.py +3 -3
  36. tableauserverclient/server/endpoint/metrics_endpoint.py +1 -1
  37. tableauserverclient/server/endpoint/projects_endpoint.py +50 -18
  38. tableauserverclient/server/endpoint/resource_tagger.py +135 -3
  39. tableauserverclient/server/endpoint/tables_endpoint.py +19 -16
  40. tableauserverclient/server/endpoint/users_endpoint.py +40 -1
  41. tableauserverclient/server/endpoint/views_endpoint.py +97 -10
  42. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
  43. tableauserverclient/server/endpoint/workbooks_endpoint.py +97 -45
  44. tableauserverclient/server/pager.py +46 -46
  45. tableauserverclient/server/query.py +62 -33
  46. tableauserverclient/server/request_factory.py +192 -49
  47. tableauserverclient/server/request_options.py +4 -2
  48. tableauserverclient/server/server.py +13 -6
  49. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -16
  50. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/RECORD +54 -48
  51. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
  52. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
  53. {tableauserverclient-0.31.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
  54. {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
- from typing import List, Optional, Tuple
3
-
4
- from .endpoint import QuerysetEndpoint, api
5
- from .exceptions import MissingRequiredFieldError
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
- from .default_permissions_endpoint import _DefaultPermissionsEndpoint
4
- from .dqw_endpoint import _DataQualityWarningEndpoint
5
- from .endpoint import api, Endpoint
6
- from .exceptions import MissingRequiredFieldError
7
- from .permissions_endpoint import _PermissionsEndpoint
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 _ResourceTagger
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, CHUNK_SIZE_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
- no_extract: Optional[bool] = None,
130
- ) -> str:
131
- return self.download_revision(datasource_id, None, filepath, include_extract, no_extract)
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._resource_tagger.update_tags(self.baseurl, datasource_item)
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 Any, Callable, Dict, Optional, TYPE_CHECKING, Union
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 ..exceptions import EndpointUnavailableError
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 ..server import Server
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(object):
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
- # logger.debug("\n\t{1}".format(loggable_response))
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
- def api(version):
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
- class QuerysetEndpoint(Endpoint):
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
- queryset = QuerySet(self)
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__}")
@@ -1,4 +1,4 @@
1
- from .endpoint import Endpoint, api
1
+ from tableauserverclient.server.endpoint.endpoint import Endpoint, api
2
2
  from requests import Response
3
3
  from tableauserverclient.helpers.logging import logger
4
4
  from tableauserverclient.models import (
@@ -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, CHUNK_SIZE_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 ..server import Server
13
- from ..request_options import RequestOptions
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)