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
@@ -7,11 +7,12 @@ from contextlib import closing
7
7
  from pathlib import Path
8
8
 
9
9
  from tableauserverclient.helpers.headers import fix_filename
10
+ from tableauserverclient.server.query import QuerySet
10
11
 
11
- from .endpoint import QuerysetEndpoint, api, parameter_added_in
12
- from .exceptions import InternalServerError, MissingRequiredFieldError
13
- from .permissions_endpoint import _PermissionsEndpoint
14
- from .resource_tagger import _ResourceTagger
12
+ from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, parameter_added_in
13
+ from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
14
+ from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
15
+ from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
15
16
 
16
17
  from tableauserverclient.filesys_helpers import (
17
18
  to_filename,
@@ -24,9 +25,11 @@ from tableauserverclient.models import WorkbookItem, ConnectionItem, ViewItem, P
24
25
  from tableauserverclient.server import RequestFactory
25
26
 
26
27
  from typing import (
28
+ Iterable,
27
29
  List,
28
30
  Optional,
29
31
  Sequence,
32
+ Set,
30
33
  Tuple,
31
34
  TYPE_CHECKING,
32
35
  Union,
@@ -35,8 +38,8 @@ from typing import (
35
38
  if TYPE_CHECKING:
36
39
  from tableauserverclient.server import Server
37
40
  from tableauserverclient.server.request_options import RequestOptions
38
- from tableauserverclient.models import DatasourceItem, ConnectionCredentials
39
- from .schedules_endpoint import AddResponse
41
+ from tableauserverclient.models import DatasourceItem
42
+ from tableauserverclient.server.endpoint.schedules_endpoint import AddResponse
40
43
 
41
44
  io_types_r = (io.BytesIO, io.BufferedReader)
42
45
  io_types_w = (io.BytesIO, io.BufferedWriter)
@@ -56,10 +59,9 @@ PathOrFileR = Union[FilePath, FileObjectR]
56
59
  PathOrFileW = Union[FilePath, FileObjectW]
57
60
 
58
61
 
59
- class Workbooks(QuerysetEndpoint):
62
+ class Workbooks(QuerysetEndpoint[WorkbookItem], TaggingMixin[WorkbookItem]):
60
63
  def __init__(self, parent_srv: "Server") -> None:
61
64
  super(Workbooks, self).__init__(parent_srv)
62
- self._resource_tagger = _ResourceTagger(parent_srv)
63
65
  self._permissions = _PermissionsEndpoint(parent_srv, lambda: self.baseurl)
64
66
 
65
67
  return None
@@ -147,7 +149,7 @@ class Workbooks(QuerysetEndpoint):
147
149
  error = "Workbook item missing ID. Workbook must be retrieved from server first."
148
150
  raise MissingRequiredFieldError(error)
149
151
 
150
- self._resource_tagger.update_tags(self.baseurl, workbook_item)
152
+ self.update_tags(workbook_item)
151
153
 
152
154
  # Update the workbook itself
153
155
  url = "{0}/{1}".format(self.baseurl, workbook_item.id)
@@ -160,13 +162,6 @@ class Workbooks(QuerysetEndpoint):
160
162
  updated_workbook = copy.copy(workbook_item)
161
163
  return updated_workbook._parse_common_tags(server_response.content, self.parent_srv.namespace)
162
164
 
163
- @api(version="2.3")
164
- def update_conn(self, *args, **kwargs):
165
- import warnings
166
-
167
- warnings.warn("update_conn is deprecated, please use update_connection instead")
168
- return self.update_connection(*args, **kwargs)
169
-
170
165
  # Update workbook_connection
171
166
  @api(version="2.3")
172
167
  def update_connection(self, workbook_item: WorkbookItem, connection_item: ConnectionItem) -> ConnectionItem:
@@ -189,9 +184,13 @@ class Workbooks(QuerysetEndpoint):
189
184
  workbook_id: str,
190
185
  filepath: Optional[PathOrFileW] = None,
191
186
  include_extract: bool = True,
192
- no_extract: Optional[bool] = None,
193
- ) -> str:
194
- return self.download_revision(workbook_id, None, filepath, include_extract, no_extract)
187
+ ) -> PathOrFileW:
188
+ return self.download_revision(
189
+ workbook_id,
190
+ None,
191
+ filepath,
192
+ include_extract,
193
+ )
195
194
 
196
195
  # Get all views of workbook
197
196
  @api(version="2.0")
@@ -315,21 +314,11 @@ class Workbooks(QuerysetEndpoint):
315
314
  workbook_item: WorkbookItem,
316
315
  file: PathOrFileR,
317
316
  mode: str,
318
- connection_credentials: Optional["ConnectionCredentials"] = None,
319
317
  connections: Optional[Sequence[ConnectionItem]] = None,
320
318
  as_job: bool = False,
321
- hidden_views: Optional[Sequence[str]] = None,
322
319
  skip_connection_check: bool = False,
323
320
  parameters=None,
324
321
  ):
325
- if connection_credentials is not None:
326
- import warnings
327
-
328
- warnings.warn(
329
- "connection_credentials is being deprecated. Use connections instead",
330
- DeprecationWarning,
331
- )
332
-
333
322
  if isinstance(file, (str, os.PathLike)):
334
323
  if not os.path.isfile(file):
335
324
  error = "File path does not lead to an existing file."
@@ -391,12 +380,9 @@ class Workbooks(QuerysetEndpoint):
391
380
  logger.info("Publishing {0} to server with chunking method (workbook over 64MB)".format(workbook_item.name))
392
381
  upload_session_id = self.parent_srv.fileuploads.upload(file)
393
382
  url = "{0}&uploadSessionId={1}".format(url, upload_session_id)
394
- conn_creds = connection_credentials
395
383
  xml_request, content_type = RequestFactory.Workbook.publish_req_chunked(
396
384
  workbook_item,
397
- connection_credentials=conn_creds,
398
385
  connections=connections,
399
- hidden_views=hidden_views,
400
386
  )
401
387
  else:
402
388
  logger.info("Publishing {0} to server".format(filename))
@@ -411,14 +397,11 @@ class Workbooks(QuerysetEndpoint):
411
397
  else:
412
398
  raise TypeError("file should be a filepath or file object.")
413
399
 
414
- conn_creds = connection_credentials
415
400
  xml_request, content_type = RequestFactory.Workbook.publish_req(
416
401
  workbook_item,
417
402
  filename,
418
403
  file_contents,
419
- connection_credentials=conn_creds,
420
404
  connections=connections,
421
- hidden_views=hidden_views,
422
405
  )
423
406
  logger.debug("Request xml: {0} ".format(redact_xml(xml_request[:1000])))
424
407
 
@@ -468,7 +451,6 @@ class Workbooks(QuerysetEndpoint):
468
451
  revision_number: Optional[str],
469
452
  filepath: Optional[PathOrFileW] = None,
470
453
  include_extract: bool = True,
471
- no_extract: Optional[bool] = None,
472
454
  ) -> PathOrFileW:
473
455
  if not workbook_id:
474
456
  error = "Workbook ID undefined."
@@ -478,15 +460,6 @@ class Workbooks(QuerysetEndpoint):
478
460
  else:
479
461
  url = "{0}/{1}/revisions/{2}/content".format(self.baseurl, workbook_id, revision_number)
480
462
 
481
- if no_extract is False or no_extract is True:
482
- import warnings
483
-
484
- warnings.warn(
485
- "no_extract is deprecated, use include_extract instead.",
486
- DeprecationWarning,
487
- )
488
- include_extract = not no_extract
489
-
490
463
  if not include_extract:
491
464
  url += "?includeExtract=False"
492
465
 
@@ -527,3 +500,82 @@ class Workbooks(QuerysetEndpoint):
527
500
  self, schedule_id: str, item: WorkbookItem
528
501
  ) -> List["AddResponse"]: # actually should return a task
529
502
  return self.parent_srv.schedules.add_to_schedule(schedule_id, workbook=item)
503
+
504
+ @api(version="1.0")
505
+ def add_tags(self, item: Union[WorkbookItem, str], tags: Union[Iterable[str], str]) -> Set[str]:
506
+ return super().add_tags(item, tags)
507
+
508
+ @api(version="1.0")
509
+ def delete_tags(self, item: Union[WorkbookItem, str], tags: Union[Iterable[str], str]) -> None:
510
+ return super().delete_tags(item, tags)
511
+
512
+ @api(version="1.0")
513
+ def update_tags(self, item: WorkbookItem) -> None:
514
+ return super().update_tags(item)
515
+
516
+ def filter(self, *invalid, page_size: Optional[int] = None, **kwargs) -> QuerySet[WorkbookItem]:
517
+ """
518
+ Queries the Tableau Server for items using the specified filters. Page
519
+ size can be specified to limit the number of items returned in a single
520
+ request. If not specified, the default page size is 100. Page size can
521
+ be an integer between 1 and 1000.
522
+
523
+ No positional arguments are allowed. All filters must be specified as
524
+ keyword arguments. If you use the equality operator, you can specify it
525
+ through <field_name>=<value>. If you want to use a different operator,
526
+ you can specify it through <field_name>__<operator>=<value>. Field
527
+ names can either be in snake_case or camelCase.
528
+
529
+ This endpoint supports the following fields and operators:
530
+
531
+
532
+ created_at=...
533
+ created_at__gt=...
534
+ created_at__gte=...
535
+ created_at__lt=...
536
+ created_at__lte=...
537
+ content_url=...
538
+ content_url__in=...
539
+ display_tabs=...
540
+ favorites_total=...
541
+ favorites_total__gt=...
542
+ favorites_total__gte=...
543
+ favorites_total__lt=...
544
+ favorites_total__lte=...
545
+ has_alerts=...
546
+ has_extracts=...
547
+ name=...
548
+ name__in=...
549
+ owner_domain=...
550
+ owner_domain__in=...
551
+ owner_email=...
552
+ owner_email__in=...
553
+ owner_name=...
554
+ owner_name__in=...
555
+ project_name=...
556
+ project_name__in=...
557
+ sheet_count=...
558
+ sheet_count__gt=...
559
+ sheet_count__gte=...
560
+ sheet_count__lt=...
561
+ sheet_count__lte=...
562
+ size=...
563
+ size__gt=...
564
+ size__gte=...
565
+ size__lt=...
566
+ size__lte=...
567
+ subscriptions_total=...
568
+ subscriptions_total__gt=...
569
+ subscriptions_total__gte=...
570
+ subscriptions_total__lt=...
571
+ subscriptions_total__lte=...
572
+ tags=...
573
+ tags__in=...
574
+ updated_at=...
575
+ updated_at__gt=...
576
+ updated_at__gte=...
577
+ updated_at__lt=...
578
+ updated_at__lte=...
579
+ """
580
+
581
+ return super().filter(*invalid, page_size=page_size, **kwargs)
@@ -1,9 +1,27 @@
1
+ import copy
1
2
  from functools import partial
3
+ from typing import Iterable, Iterator, List, Optional, Protocol, Tuple, TypeVar, Union, runtime_checkable
2
4
 
3
- from . import RequestOptions
5
+ from tableauserverclient.models.pagination_item import PaginationItem
6
+ from tableauserverclient.server.request_options import RequestOptions
4
7
 
5
8
 
6
- class Pager(object):
9
+ T = TypeVar("T")
10
+
11
+
12
+ @runtime_checkable
13
+ class Endpoint(Protocol[T]):
14
+ def get(self, req_options: Optional[RequestOptions]) -> Tuple[List[T], PaginationItem]:
15
+ ...
16
+
17
+
18
+ @runtime_checkable
19
+ class CallableEndpoint(Protocol[T]):
20
+ def __call__(self, __req_options: Optional[RequestOptions], **kwargs) -> Tuple[List[T], PaginationItem]:
21
+ ...
22
+
23
+
24
+ class Pager(Iterable[T]):
7
25
  """
8
26
  Generator that takes an endpoint (top level endpoints with `.get)` and lazily loads items from Server.
9
27
  Supports all `RequestOptions` including starting on any page. Also used by models to load sub-models
@@ -12,12 +30,17 @@ class Pager(object):
12
30
  Will loop over anything that returns (List[ModelItem], PaginationItem).
13
31
  """
14
32
 
15
- def __init__(self, endpoint, request_opts=None, **kwargs):
16
- if hasattr(endpoint, "get"):
33
+ def __init__(
34
+ self,
35
+ endpoint: Union[CallableEndpoint[T], Endpoint[T]],
36
+ request_opts: Optional[RequestOptions] = None,
37
+ **kwargs,
38
+ ) -> None:
39
+ if isinstance(endpoint, Endpoint):
17
40
  # The simpliest case is to take an Endpoint and call its get
18
41
  endpoint = partial(endpoint.get, **kwargs)
19
42
  self._endpoint = endpoint
20
- elif callable(endpoint):
43
+ elif isinstance(endpoint, CallableEndpoint):
21
44
  # but if they pass a callable then use that instead (used internally)
22
45
  endpoint = partial(endpoint, **kwargs)
23
46
  self._endpoint = endpoint
@@ -25,47 +48,24 @@ class Pager(object):
25
48
  # Didn't get something we can page over
26
49
  raise ValueError("Pager needs a server endpoint to page through.")
27
50
 
28
- self._options = request_opts
51
+ self._options = request_opts or RequestOptions()
29
52
 
30
- # If we have options we could be starting on any page, backfill the count
31
- if self._options:
32
- self._count = (self._options.pagenumber - 1) * self._options.pagesize
33
- else:
34
- self._count = 0
35
- self._options = RequestOptions()
36
-
37
- def __iter__(self):
38
- # Fetch the first page
39
- current_item_list, last_pagination_item = self._endpoint(self._options)
40
-
41
- if last_pagination_item.total_available is None:
42
- # This endpoint does not support pagination, drain the list and return
43
- while current_item_list:
44
- yield current_item_list.pop(0)
45
-
46
- return
47
-
48
- # Get the rest on demand as a generator
49
- while self._count < last_pagination_item.total_available:
50
- if (
51
- len(current_item_list) == 0
52
- and (last_pagination_item.page_number * last_pagination_item.page_size)
53
- < last_pagination_item.total_available
54
- ):
55
- current_item_list, last_pagination_item = self._load_next_page(last_pagination_item)
56
-
57
- try:
58
- yield current_item_list.pop(0)
59
- self._count += 1
60
-
61
- except IndexError:
62
- # The total count on Server changed while fetching exit gracefully
53
+ def __iter__(self) -> Iterator[T]:
54
+ options = copy.deepcopy(self._options)
55
+ while True:
56
+ # Fetch the first page
57
+ current_item_list, pagination_item = self._endpoint(options)
58
+
59
+ if pagination_item.total_available is None:
60
+ # This endpoint does not support pagination, drain the list and return
61
+ yield from current_item_list
62
+ return
63
+ yield from current_item_list
64
+
65
+ if pagination_item.page_size * pagination_item.page_number >= pagination_item.total_available:
66
+ # Last page, exit
63
67
  return
64
68
 
65
- def _load_next_page(self, last_pagination_item):
66
- next_page = last_pagination_item.page_number + 1
67
- opts = RequestOptions(pagenumber=next_page, pagesize=last_pagination_item.page_size)
68
- if self._options is not None:
69
- opts.sort, opts.filter = self._options.sort, self._options.filter
70
- current_item_list, last_pagination_item = self._endpoint(opts)
71
- return current_item_list, last_pagination_item
69
+ # Update the options to fetch the next page
70
+ options.pagenumber = pagination_item.page_number + 1
71
+ options.pagesize = pagination_item.page_size
@@ -1,9 +1,26 @@
1
- from typing import Tuple
2
- from .filter import Filter
3
- from .request_options import RequestOptions
4
- from .sort import Sort
1
+ from collections.abc import Sized
2
+ from itertools import count
3
+ from typing import Iterable, Iterator, List, Optional, Protocol, Tuple, TYPE_CHECKING, TypeVar, overload
4
+ from tableauserverclient.config import config
5
+ from tableauserverclient.models.pagination_item import PaginationItem
6
+ from tableauserverclient.server.filter import Filter
7
+ from tableauserverclient.server.request_options import RequestOptions
8
+ from tableauserverclient.server.sort import Sort
5
9
  import math
6
10
 
11
+ from typing_extensions import Self
12
+
13
+ if TYPE_CHECKING:
14
+ from tableauserverclient.server.endpoint import QuerysetEndpoint
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ class Slice(Protocol):
20
+ start: Optional[int]
21
+ step: Optional[int]
22
+ stop: Optional[int]
23
+
7
24
 
8
25
  def to_camel_case(word: str) -> str:
9
26
  return word.split("_")[0] + "".join(x.capitalize() or "_" for x in word.split("_")[1:])
@@ -16,28 +33,35 @@ see pagination_sample
16
33
  """
17
34
 
18
35
 
19
- class QuerySet:
20
- def __init__(self, model):
36
+ class QuerySet(Iterable[T], Sized):
37
+ def __init__(self, model: "QuerysetEndpoint[T]", page_size: Optional[int] = None) -> None:
21
38
  self.model = model
22
- self.request_options = RequestOptions()
23
- self._result_cache = None
24
- self._pagination_item = None
39
+ self.request_options = RequestOptions(pagesize=page_size or config.PAGE_SIZE)
40
+ self._result_cache: List[T] = []
41
+ self._pagination_item = PaginationItem()
25
42
 
26
- def __iter__(self):
43
+ def __iter__(self: Self) -> Iterator[T]:
27
44
  # Not built to be re-entrant. Starts back at page 1, and empties
28
- # the result cache.
29
- self.request_options.pagenumber = 1
30
- self._result_cache = None
31
- total = self.total_available
32
- size = self.page_size
33
- yield from self._result_cache
45
+ # the result cache. Ensure the result_cache is empty to not yield
46
+ # items from prior usage.
47
+ self._result_cache = []
34
48
 
35
- # Loop through the subsequent pages.
36
- for page in range(1, math.ceil(total / size)):
37
- self.request_options.pagenumber = page + 1
38
- self._result_cache = None
49
+ for page in count(1):
50
+ self.request_options.pagenumber = page
51
+ self._result_cache = []
39
52
  self._fetch_all()
40
53
  yield from self._result_cache
54
+ # Set result_cache to empty so the fetch will populate
55
+ if (page * self.page_size) >= len(self):
56
+ return
57
+
58
+ @overload
59
+ def __getitem__(self: Self, k: Slice) -> List[T]:
60
+ ...
61
+
62
+ @overload
63
+ def __getitem__(self: Self, k: int) -> T:
64
+ ...
41
65
 
42
66
  def __getitem__(self, k):
43
67
  page = self.page_number
@@ -78,7 +102,7 @@ class QuerySet:
78
102
  return self._result_cache[k % size]
79
103
  elif k in range(self.total_available):
80
104
  # Otherwise, check if k is even sensible to return
81
- self._result_cache = None
105
+ self._result_cache = []
82
106
  # Add one to k, otherwise it gets stuck at page boundaries, e.g. 100
83
107
  self.request_options.pagenumber = max(1, math.ceil((k + 1) / size))
84
108
  return self[k]
@@ -86,53 +110,57 @@ class QuerySet:
86
110
  # If k is unreasonable, raise an IndexError.
87
111
  raise IndexError
88
112
 
89
- def _fetch_all(self):
113
+ def _fetch_all(self: Self) -> None:
90
114
  """
91
115
  Retrieve the data and store result and pagination item in cache
92
116
  """
93
- if self._result_cache is None:
117
+ if not self._result_cache:
94
118
  self._result_cache, self._pagination_item = self.model.get(self.request_options)
95
119
 
96
- def __len__(self) -> int:
120
+ def __len__(self: Self) -> int:
97
121
  return self.total_available
98
122
 
99
123
  @property
100
- def total_available(self) -> int:
124
+ def total_available(self: Self) -> int:
101
125
  self._fetch_all()
102
126
  return self._pagination_item.total_available
103
127
 
104
128
  @property
105
- def page_number(self) -> int:
129
+ def page_number(self: Self) -> int:
106
130
  self._fetch_all()
107
131
  return self._pagination_item.page_number
108
132
 
109
133
  @property
110
- def page_size(self) -> int:
134
+ def page_size(self: Self) -> int:
111
135
  self._fetch_all()
112
136
  return self._pagination_item.page_size
113
137
 
114
- def filter(self, *invalid, **kwargs):
138
+ def filter(self: Self, *invalid, page_size: Optional[int] = None, **kwargs) -> Self:
115
139
  if invalid:
116
- raise RuntimeError(f"Only accepts keyword arguments.")
140
+ raise RuntimeError("Only accepts keyword arguments.")
117
141
  for kwarg_key, value in kwargs.items():
118
142
  field_name, operator = self._parse_shorthand_filter(kwarg_key)
119
143
  self.request_options.filter.add(Filter(field_name, operator, value))
144
+
145
+ if page_size:
146
+ self.request_options.pagesize = page_size
120
147
  return self
121
148
 
122
- def order_by(self, *args):
149
+ def order_by(self: Self, *args) -> Self:
123
150
  for arg in args:
124
151
  field_name, direction = self._parse_shorthand_sort(arg)
125
152
  self.request_options.sort.add(Sort(field_name, direction))
126
153
  return self
127
154
 
128
- def paginate(self, **kwargs):
155
+ def paginate(self: Self, **kwargs) -> Self:
129
156
  if "page_number" in kwargs:
130
157
  self.request_options.pagenumber = kwargs["page_number"]
131
158
  if "page_size" in kwargs:
132
159
  self.request_options.pagesize = kwargs["page_size"]
133
160
  return self
134
161
 
135
- def _parse_shorthand_filter(self, key: str) -> Tuple[str, str]:
162
+ @staticmethod
163
+ def _parse_shorthand_filter(key: str) -> Tuple[str, str]:
136
164
  tokens = key.split("__", 1)
137
165
  if len(tokens) == 1:
138
166
  operator = RequestOptions.Operator.Equals
@@ -146,7 +174,8 @@ class QuerySet:
146
174
  raise ValueError("Field name `{}` is not valid.".format(field))
147
175
  return (field, operator)
148
176
 
149
- def _parse_shorthand_sort(self, key: str) -> Tuple[str, str]:
177
+ @staticmethod
178
+ def _parse_shorthand_sort(key: str) -> Tuple[str, str]:
150
179
  direction = RequestOptions.Direction.Asc
151
180
  if key.startswith("-"):
152
181
  direction = RequestOptions.Direction.Desc