tableauserverclient 0.32__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 (45) hide show
  1. tableauserverclient/__init__.py +10 -0
  2. tableauserverclient/_version.py +3 -3
  3. tableauserverclient/config.py +16 -4
  4. tableauserverclient/models/__init__.py +12 -0
  5. tableauserverclient/models/connection_item.py +4 -2
  6. tableauserverclient/models/database_item.py +6 -0
  7. tableauserverclient/models/flow_item.py +6 -6
  8. tableauserverclient/models/groupset_item.py +53 -0
  9. tableauserverclient/models/interval_item.py +27 -14
  10. tableauserverclient/models/job_item.py +18 -2
  11. tableauserverclient/models/linked_tasks_item.py +102 -0
  12. tableauserverclient/models/permissions_item.py +7 -4
  13. tableauserverclient/models/tableau_types.py +9 -7
  14. tableauserverclient/models/virtual_connection_item.py +77 -0
  15. tableauserverclient/server/endpoint/__init__.py +8 -0
  16. tableauserverclient/server/endpoint/auth_endpoint.py +3 -3
  17. tableauserverclient/server/endpoint/custom_views_endpoint.py +65 -4
  18. tableauserverclient/server/endpoint/databases_endpoint.py +21 -7
  19. tableauserverclient/server/endpoint/datasources_endpoint.py +105 -9
  20. tableauserverclient/server/endpoint/endpoint.py +32 -14
  21. tableauserverclient/server/endpoint/fileuploads_endpoint.py +2 -2
  22. tableauserverclient/server/endpoint/flow_runs_endpoint.py +44 -4
  23. tableauserverclient/server/endpoint/flows_endpoint.py +43 -6
  24. tableauserverclient/server/endpoint/groups_endpoint.py +82 -14
  25. tableauserverclient/server/endpoint/groupsets_endpoint.py +127 -0
  26. tableauserverclient/server/endpoint/jobs_endpoint.py +74 -7
  27. tableauserverclient/server/endpoint/linked_tasks_endpoint.py +45 -0
  28. tableauserverclient/server/endpoint/projects_endpoint.py +43 -0
  29. tableauserverclient/server/endpoint/resource_tagger.py +135 -3
  30. tableauserverclient/server/endpoint/tables_endpoint.py +19 -6
  31. tableauserverclient/server/endpoint/users_endpoint.py +39 -0
  32. tableauserverclient/server/endpoint/views_endpoint.py +94 -9
  33. tableauserverclient/server/endpoint/virtual_connections_endpoint.py +173 -0
  34. tableauserverclient/server/endpoint/workbooks_endpoint.py +91 -10
  35. tableauserverclient/server/pager.py +6 -7
  36. tableauserverclient/server/query.py +2 -1
  37. tableauserverclient/server/request_factory.py +178 -7
  38. tableauserverclient/server/request_options.py +4 -2
  39. tableauserverclient/server/server.py +8 -0
  40. {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/METADATA +15 -15
  41. {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/RECORD +45 -39
  42. {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/WHEEL +1 -1
  43. {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/LICENSE +0 -0
  44. {tableauserverclient-0.32.dist-info → tableauserverclient-0.33.dist-info}/LICENSE.versioneer +0 -0
  45. {tableauserverclient-0.32.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,6 +21,15 @@ 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
34
  class CustomViews(QuerysetEndpoint[CustomViewItem]):
21
35
  def __init__(self, parent_srv):
@@ -25,6 +39,10 @@ class CustomViews(QuerysetEndpoint[CustomViewItem]):
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[CustomViewItem]):
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
 
@@ -123,3 +125,15 @@ class Databases(Endpoint):
123
125
  @api(version="3.5")
124
126
  def delete_dqw(self, item):
125
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,9 +6,10 @@ 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
@@ -19,9 +20,9 @@ from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarning
19
20
  from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, parameter_added_in
20
21
  from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
21
22
  from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
22
- from tableauserverclient.server.endpoint.resource_tagger import _ResourceTagger
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[DatasourceItem]):
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,7 +126,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem]):
126
126
  datasource_id: str,
127
127
  filepath: Optional[PathOrFileW] = None,
128
128
  include_extract: bool = True,
129
- ) -> str:
129
+ ) -> PathOrFileW:
130
130
  return self.download_revision(
131
131
  datasource_id,
132
132
  None,
@@ -149,7 +149,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem]):
149
149
  )
150
150
  raise MissingRequiredFieldError(error)
151
151
 
152
- self._resource_tagger.update_tags(self.baseurl, datasource_item)
152
+ self.update_tags(datasource_item)
153
153
 
154
154
  # Update the datasource itself
155
155
  url = "{0}/{1}".format(self.baseurl, datasource_item.id)
@@ -272,7 +272,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem]):
272
272
  if file_size >= FILESIZE_LIMIT_MB * BYTES_PER_MB:
273
273
  logger.info(
274
274
  "Publishing {} to server with chunking method (datasource over {}MB, chunk size {}MB)".format(
275
- filename, FILESIZE_LIMIT_MB, CHUNK_SIZE_MB
275
+ filename, FILESIZE_LIMIT_MB, config.CHUNK_SIZE_MB
276
276
  )
277
277
  )
278
278
  upload_session_id = self.parent_srv.fileuploads.upload(file)
@@ -405,7 +405,7 @@ class Datasources(QuerysetEndpoint[DatasourceItem]):
405
405
  def download_revision(
406
406
  self,
407
407
  datasource_id: str,
408
- revision_number: str,
408
+ revision_number: Optional[str],
409
409
  filepath: Optional[PathOrFileW] = None,
410
410
  include_extract: bool = True,
411
411
  ) -> PathOrFileW:
@@ -459,3 +459,99 @@ class Datasources(QuerysetEndpoint[DatasourceItem]):
459
459
  self, schedule_id: str, item: DatasourceItem
460
460
  ) -> List["AddResponse"]: # actually should return a task
461
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,30 +1,41 @@
1
+ from typing_extensions import Concatenate, ParamSpec
1
2
  from tableauserverclient import datetime_helpers as datetime
2
3
 
3
4
  import abc
4
5
  from packaging.version import Version
5
6
  from functools import wraps
6
7
  from xml.etree.ElementTree import ParseError
7
- from typing import Any, Callable, Dict, Generic, List, Optional, TYPE_CHECKING, Tuple, TypeVar, 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
+ )
8
20
 
9
21
  from tableauserverclient.models.pagination_item import PaginationItem
10
22
  from tableauserverclient.server.request_options import RequestOptions
11
23
 
12
- from .exceptions import (
24
+ from tableauserverclient.server.endpoint.exceptions import (
13
25
  ServerResponseError,
14
26
  InternalServerError,
15
27
  NonXMLResponseError,
16
28
  NotSignedInError,
17
29
  )
18
- from ..exceptions import EndpointUnavailableError
30
+ from tableauserverclient.server.exceptions import EndpointUnavailableError
19
31
 
20
32
  from tableauserverclient.server.query import QuerySet
21
33
  from tableauserverclient import helpers, get_versions
22
34
 
23
35
  from tableauserverclient.helpers.logging import logger
24
- from tableauserverclient.config import DELAY_SLEEP_SECONDS
25
36
 
26
37
  if TYPE_CHECKING:
27
- from ..server import Server
38
+ from tableauserverclient.server.server import Server
28
39
  from requests import Response
29
40
 
30
41
 
@@ -38,7 +49,7 @@ TABLEAU_AUTH_HEADER = "x-tableau-auth"
38
49
  USER_AGENT_HEADER = "User-Agent"
39
50
 
40
51
 
41
- class Endpoint(object):
52
+ class Endpoint:
42
53
  def __init__(self, parent_srv: "Server"):
43
54
  self.parent_srv = parent_srv
44
55
 
@@ -133,7 +144,9 @@ class Endpoint(object):
133
144
 
134
145
  loggable_response = self.log_response_safely(server_response)
135
146
  logger.debug("Server response from {0}".format(url))
136
- # 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)
137
150
 
138
151
  if content_type == "application/xml":
139
152
  self.parent_srv._namespace.detect(server_response.content)
@@ -232,7 +245,12 @@ class Endpoint(object):
232
245
  )
233
246
 
234
247
 
235
- 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]]:
236
254
  """Annotate the minimum supported version for an endpoint.
237
255
 
238
256
  Checks the version on the server object and compares normalized versions.
@@ -251,9 +269,9 @@ def api(version):
251
269
  >>> ...
252
270
  """
253
271
 
254
- def _decorator(func):
272
+ def _decorator(func: Callable[Concatenate[E, P], R]) -> Callable[Concatenate[E, P], R]:
255
273
  @wraps(func)
256
- def wrapper(self, *args, **kwargs):
274
+ def wrapper(self: E, *args: P.args, **kwargs: P.kwargs) -> R:
257
275
  self.parent_srv.assert_at_least_version(version, self.__class__.__name__)
258
276
  return func(self, *args, **kwargs)
259
277
 
@@ -262,7 +280,7 @@ def api(version):
262
280
  return _decorator
263
281
 
264
282
 
265
- def parameter_added_in(**params):
283
+ def parameter_added_in(**params: str) -> Callable[[Callable[Concatenate[E, P], R]], Callable[Concatenate[E, P], R]]:
266
284
  """Annotate minimum versions for new parameters or request options on an endpoint.
267
285
 
268
286
  The api decorator documents when an endpoint was added, this decorator annotates
@@ -285,9 +303,9 @@ def parameter_added_in(**params):
285
303
  >>> ...
286
304
  """
287
305
 
288
- def _decorator(func):
306
+ def _decorator(func: Callable[Concatenate[E, P], R]) -> Callable[Concatenate[E, P], R]:
289
307
  @wraps(func)
290
- def wrapper(self, *args, **kwargs):
308
+ def wrapper(self: E, *args: P.args, **kwargs: P.kwargs) -> R:
291
309
  import warnings
292
310
 
293
311
  server_ver = Version(self.parent_srv.version or "0.0")
@@ -335,5 +353,5 @@ class QuerysetEndpoint(Endpoint, Generic[T]):
335
353
  return queryset
336
354
 
337
355
  @abc.abstractmethod
338
- def get(self, request_options: RequestOptions) -> Tuple[List[T], PaginationItem]:
356
+ def get(self, request_options: Optional[RequestOptions] = None) -> Tuple[List[T], PaginationItem]:
339
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, 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,16 +1,17 @@
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
17
  class FlowRuns(QuerysetEndpoint[FlowRunItem]):
@@ -78,3 +79,42 @@ class FlowRuns(QuerysetEndpoint[FlowRunItem]):
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)
@@ -9,11 +9,11 @@ from typing import Iterable, List, Optional, TYPE_CHECKING, Tuple, Union
9
9
 
10
10
  from tableauserverclient.helpers.headers import fix_filename
11
11
 
12
- from .dqw_endpoint import _DataQualityWarningEndpoint
13
- from .endpoint import QuerysetEndpoint, api
14
- from .exceptions import InternalServerError, MissingRequiredFieldError
15
- from .permissions_endpoint import _PermissionsEndpoint
16
- from .resource_tagger import _ResourceTagger
12
+ from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint
13
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
14
+ from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
15
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
16
+ from tableauserverclient.server.endpoint.resource_tagger import _ResourceTagger, TaggingMixin
17
17
  from tableauserverclient.models import FlowItem, PaginationItem, ConnectionItem, JobItem
18
18
  from tableauserverclient.server import RequestFactory
19
19
  from tableauserverclient.filesys_helpers import (
@@ -22,6 +22,7 @@ from tableauserverclient.filesys_helpers import (
22
22
  get_file_type,
23
23
  get_file_object_size,
24
24
  )
25
+ from tableauserverclient.server.query import QuerySet
25
26
 
26
27
  io_types_r = (io.BytesIO, io.BufferedReader)
27
28
  io_types_w = (io.BytesIO, io.BufferedWriter)
@@ -50,7 +51,7 @@ PathOrFileR = Union[FilePath, FileObjectR]
50
51
  PathOrFileW = Union[FilePath, FileObjectW]
51
52
 
52
53
 
53
- class Flows(QuerysetEndpoint[FlowItem]):
54
+ class Flows(QuerysetEndpoint[FlowItem], TaggingMixin[FlowItem]):
54
55
  def __init__(self, parent_srv):
55
56
  super(Flows, self).__init__(parent_srv)
56
57
  self._resource_tagger = _ResourceTagger(parent_srv)
@@ -295,3 +296,39 @@ class Flows(QuerysetEndpoint[FlowItem]):
295
296
  self, schedule_id: str, item: FlowItem
296
297
  ) -> List["AddResponse"]: # actually should return a task
297
298
  return self.parent_srv.schedules.add_to_schedule(schedule_id, flow=item)
299
+
300
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[FlowItem]:
301
+ """
302
+ Queries the Tableau Server for items using the specified filters. Page
303
+ size can be specified to limit the number of items returned in a single
304
+ request. If not specified, the default page size is 100. Page size can
305
+ be an integer between 1 and 1000.
306
+
307
+ No positional arguments are allowed. All filters must be specified as
308
+ keyword arguments. If you use the equality operator, you can specify it
309
+ through <field_name>=<value>. If you want to use a different operator,
310
+ you can specify it through <field_name>__<operator>=<value>. Field
311
+ names can either be in snake_case or camelCase.
312
+
313
+ This endpoint supports the following fields and operators:
314
+
315
+
316
+ created_at=...
317
+ created_at__gt=...
318
+ created_at__gte=...
319
+ created_at__lt=...
320
+ created_at__lte=...
321
+ name=...
322
+ name__in=...
323
+ owner_name=...
324
+ project_id=...
325
+ project_name=...
326
+ project_name__in=...
327
+ updated=...
328
+ updated__gt=...
329
+ updated__gte=...
330
+ updated__lt=...
331
+ updated__lte=...
332
+ """
333
+
334
+ return super().filter(*invalid, page_size=page_size, **kwargs)