10000 Type hinting on Datasources and Workbooks (#804) · elsherif/server-client-python@946469c · GitHub
[go: up one dir, main page]

Skip to content

Commit 946469c

Browse files
jorwoodsJordan Woods
and
Jordan Woods
authored
Type hinting on Datasources and Workbooks (tableau#804)
Adds types to function signatures for Workbook and Data Source endpoints. Co-authored-by: Jordan Woods <Jordan.Woods@mkcorp.com>
1 parent feed39c commit 946469c

21 files changed

+404
-298
lines changed

.github/workflows/run-tests.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Python tests
22

3-
on: [push]
3+
on: [push, pull_request]
44

55
jobs:
66
build:
@@ -24,13 +24,11 @@ jobs:
2424
run: |
2525
python -m pip install --upgrade pip
2626
pip install -e .[test]
27-
pip install mypy
2827
2928
- name: Test with pytest
3029
run: |
3130
pytest test
3231
33-
- name: Run Mypy but allow failures
32+
- name: Run Mypy tests
3433
run: |
35-
mypy --show-error-codes --disable-error-code misc tableauserverclient
36-
continue-on-error: true
34+
mypy --show-error-codes --disable-error-code misc --disable-error-code import tableauserverclient test

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@ smoke=pytest
2626
[tool:pytest]
2727
testpaths = test smoke
2828
addopts = --junitxml=./test.junit.xml
29+
30+
[mypy]
31+
ignore_missing_imports = True

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# This makes work easier for offline installs or low bandwidth machines
1616
needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
1717
pytest_runner = ['pytest-runner'] if needs_pytest else []
18-
test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0']
18+
test_requirements = ['mock', 'pycodestyle', 'pytest', 'requests-mock>=1.0,<2.0', 'mypy==0.910']
1919

2020
setup(
2121
name='tableauserverclient',

tableauserverclient/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class NotThisMethod(Exception):
5151
"""Exception raised if a method is not valid for the current scenario."""
5252

5353

54-
LONG_VERSION_PY = {}
54+
LONG_VERSION_PY = {} # type: ignore
5555
HANDLERS = {}
5656

5757

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import time
22

33
# Polling for server-side events (such as job completion) uses exponential backoff for the sleep intervals between polls
4-
ASYNC_POLL_MIN_INTERVAL=0.5
5-
ASYNC_POLL_MAX_INTERVAL=30
6-
ASYNC_POLL_BACKOFF_FACTOR=1.4
4+
ASYNC_POLL_MIN_INTERVAL = 0.5
5+
ASYNC_POLL_MAX_INTERVAL = 30
6+
ASYNC_POLL_BACKOFF_FACTOR = 1.4
77

88

9-
class ExponentialBackoffTimer():
9+
class ExponentialBackoffTimer:
1010
def __init__(self, *, timeout=None):
1111
self.start_time = time.time()
1212
self.timeout = timeout
@@ -15,7 +15,7 @@ def __init__(self, *, timeout=None):
1515
def sleep(self):
1616
max_sleep_time = ASYNC_POLL_MAX_INTERVAL
1717
if self.timeout is not None:
18-
elapsed = (time.time() - self.start_time)
18+
elapsed = time.time() - self.start_time
1919
if elapsed >= self.timeout:
2020
raise TimeoutError(f"Timeout after {elapsed} seconds waiting for asynchronous event")
2121
remaining_time = self.timeout - elapsed
@@ -27,4 +27,4 @@ def sleep(self):
2727
max_sleep_time = max(max_sleep_time, ASYNC_POLL_MIN_INTERVAL)
2828

2929
time.sleep(min(self.current_sleep_interval, max_sleep_time))
30-
self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR
30+
self.current_sleep_interval *= ASYNC_POLL_BACKOFF_FACTOR

tableauserverclient/models/database_item.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ def dqws(self):
5353
def content_permissions(self):
5454
return self._content_permissions
5555

56+
@content_permissions.setter
57+
@property_is_enum(ContentPermissions)
58+
def content_permissions(self, value):
59+
self._content_permissions = value
60+
5661
@property
5762
def permissions(self):
5863
if self._permissions is None:
@@ -67,11 +72,6 @@ def default_table_permissions(self):
6772
raise UnpopulatedPropertyError(error)
6873
return self._default_table_permissions()
6974

70-
@content_permissions.setter
71-
@property_is_enum(ContentPermissions)
72-
def content_permissions(self, value):
73-
self._content_permissions = value
74-
7575
@property
7676
def id(self):
7777
return self._id

tableauserverclient/models/datasource_item.py

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,86 +9,95 @@
99
from ..datetime_helpers import parse_datetime
1010
import copy
1111

12+
from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING, Union
13+
14+
if TYPE_CHECKING:
15+
from .permissions_item import PermissionsRule
16+
from .connection_item import ConnectionItem
17+
import datetime
18+
1219

1320
class DatasourceItem(object):
1421
class AskDataEnablement:
1522
Enabled = "Enabled"
1623
Disabled = "Disabled"
1724
SiteDefault = "SiteDefault"
1825

19-
def __init__(self, project_id, name=None):
26+
def __init__(self, project_id: str, name: str = None) -> None:
2027
self._ask_data_enablement = None
2128
self._certified = None
2229
self._certification_note = None
2330
self._connections = None
24-
self._content_url = None
31+
self._content_url: Optional[str] = None
2532
self._created_at = None
2633
self._datasource_type = None
2734
self._description = None
2835
self._encrypt_extracts = None
2936
self._has_extracts = None
30-
self._id = None
31-
self._initial_tags = set()
32-
self._project_name = None
37+
self._id: Optional[str] = None
38+
self._initial_tags: Set = set()
39+
self._project_name: Optional[str] = None
3340
self._updated_at = None
3441
self._use_remote_query_agent = None
3542
self._webpage_url = None
3643
self.description = None
3744
self.name = name
38-
self.owner_id = None
45+
self.owner_id: Optional[str] = None
3946
self.project_id = project_id
40-
self.tags = set()
47+
self.tags: Set[str] = set()
4148

4249
self._permissions = None
4350
self._data_quality_warnings = None
4451

52+
return None
53+
4554
@property
46-
def ask_data_enablement(self):
55+
def ask_data_enablement(self) -> Optional["DatasourceItem.AskDataEnablement"]:
4756
return self._ask_data_enablement
4857

4958
@ask_data_enablement.setter
5059
@property_is_enum(AskDataEnablement)
51-
def ask_data_enablement(self, value):
60+
def ask_data_enablement(self, value: Optional["DatasourceItem.AskDataEnablement"]):
5261
self._ask_data_enablement = value
5362

5463
@property
55-
def connections(self):
64+
def connections(self) -> Optional[List["ConnectionItem"]]:
5665
if self._connections is None:
5766
error = "Datasource item must be populated with connections first."
5867
raise UnpopulatedPropertyError(error)
5968
return self._connections()
6069

6170
@property
62-
def permissions(self):
71+
def permissions(self) -> Optional[List["PermissionsRule"]]:
6372
if self._permissions is None:
6473
error = "Project item must be populated with permissions first."
6574
raise UnpopulatedPropertyError(error)
6675
return self._permissions()
6776

6877
@property
69-
def content_url(self):
78+
def content_url(self) -> Optional[str]:
7079
return self._content_url
7180

7281
@property
73-
def created_at(self):
82+
def created_at(self) -> Optional["datetime.datetime"]:
7483
return self._created_at
7584

7685
@property
77-
def certified(self):
86+
def certified(self) -> Optional[bool]:
7887
return self._certified
7988

8089
@certified.setter
8190
@property_not_nullable
8291
@property_is_boolean
83-
def certified(self, value):
92+
def certified(self, value: Optional[bool]):
8493
self._certified = value
8594

8695
@property
87-
def certification_note(self):
96+
def certification_note(self) -> Optional[str]:
8897
return self._certification_note
8998

9099
@certification_note.setter
91-
def certification_note(self, value):
100+
def certification_note(self, value: Optional[str]):
92101
self._certification_note = value
93102

94103
@property
@@ -97,7 +106,7 @@ def encrypt_extracts(self):
97106

98107
@encrypt_extracts.setter
99108
@property_is_boolean
100-
def encrypt_extracts(self, value):
109+
def encrypt_extracts(self, value: Optional[bool]):
101110
self._encrypt_extracts = value
102111

103112
@property
@@ -108,53 +117,53 @@ def dqws(self):
108117
return self._data_quality_warnings()
109118

110119
@property
111-
def has_extracts(self):
120+
def has_extracts(self) -> Optional[bool]:
112121
return self._has_extracts
113122

114123
@property
115-
def id(self):
124+
def id(self) -> Optional[str]:
116125
return self._id
117126

118127
@property
119-
def project_id(self):
128+
def project_id(self) -> str:
120129
return self._project_id
121130

122131
@project_id.setter
123132
@property_not_nullable
124-
def project_id(self, value):
133+
def project_id(self, value: str):
125134
self._project_id = value
126135

127136
@property
128-
def project_name(self):
137+
def project_name(self) -> Optional[str]:
129138
return self._project_name
130139

131140
@property
132-
def datasource_type(self):
141+
def datasource_type(self) -> Optional[str]:
133142
return self._datasource_type
134143

135144
@property
136-
def description(self):
145+
def description(self) -> Optional[str]:
137146
return self._description
138147

139148
@description.setter
140-
def description(self, value):
149+
def description(self, value: str):
141150
self._description = value
142151

143152
@property
144-
def updated_at(self):
153+
def updated_at(self) -> Optional["datetime.datetime"]:
145154
return self._updated_at
146155

147156
@property
148-
def use_remote_query_agent(self):
157+
def use_remote_query_agent(self) -> Optional[bool]:
149158
return self._use_remote_query_agent
150159

151160
@use_remote_query_agent.setter
152161
@property_is_boolean
153-
def use_remote_query_agent(self, value):
162+
def use_remote_query_agent(self, value: bool):
154163
self._use_remote_query_agent = value
155164

156165
@property
157-
def webpage_url(self):
166+
def webpage_url(self) -> Optional[str]:
158167
return self._webpage_url
159168

160169
def _set_connections(self, connections):
@@ -271,7 +280,7 @@ def _set_values(
271280
self._webpage_url = webpage_url
272281

273282
@classmethod
274-
def from_response(cls, resp, ns):
283+
def from_response(cls, resp: str, ns: Dict) -> List["DatasourceItem"]:
275284
all_datasource_items = list()
276285
parsed_response = ET.fromstring(resp)
277286
all_datasource_xml = parsed_response.findall(".//t:datasource", namespaces=ns)
@@ -322,16 +331,16 @@ def from_response(cls, resp, ns):
322331
return all_datasource_items
323332

324333
@staticmethod
325-
def _parse_element(datasource_xml, ns):
326-
id_ = datasource_xml.get('id', None)
327-
name = datasource_xml.get('name', None)
328-
datasource_type = datasource_xml.get('type', None)
329-
description = datasource_xml.get('description', None)
330-
content_url = datasource_xml.get('contentUrl', None)
331-
created_at = parse_datetime(datasource_xml.get('createdAt', None))
332-
updated_at = parse_datetime(datasource_xml.get('updatedAt', None))
333-
certification_note = datasource_xml.get('certificationNote', None)
334-
certified = str(datasource_xml.get('isCertified', None)).lower() == 'true'
334+
def _parse_element(datasource_xml: ET.Element, ns: Dict) -> Tuple:
335+
id_ = datasource_xml.get("id", None)
336+
name = datasource_xml.get("name", None)
337+
datasource_type = datasource_xml.get("type", None)
338+
description = datasource_xml.get("description", None)
339+
content_url = datasource_xml.get("contentUrl", None)
340+
created_at = parse_datetime(datasource_xml.get("createdAt", None))
341+
updated_at = parse_datetime(datasource_xml.get("updatedAt", None))
342+
certification_note = datasource_xml.get("certificationNote", None)
343+
certified = str(datasource_xml.get("isCertified", None)).lower() == "true"
335344
certification_note = datasource_xml.get("certificationNote", None)
336345
certified = str(datasource_xml.get("isCertified", None)).lower() == "true"
337346
content_url = datasource_xml.get("contentUrl", None)

tableauserverclient/models/dqw_item.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,6 @@ def severe(self):
8080
def severe(self, value):
8181
self._severe = value
8282

83-
@property
84-
def active(self):
85-
return self._active
86-
87-
@active.setter
88-
def active(self, value):
89-
self._active = value
90-
9183
@property
9284
def created_at(self):
9385
return self._created_at

tableauserverclient/models/job_item.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ class FinishCode:
99
Status codes as documented on
1010
https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#query_job
1111
"""
12+
1213
Success = 0
1314
Failed = 1
1415
Cancelled = 2
1516

16-
1717
def __init__(
1818
self,
1919
id_,

tableauserverclient/models/project_item.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ def __init__(self, name, description=None, content_permissions=None, parent_id=N
2929
def content_permissions(self):
3030
return self._content_permissions
3131

32+
@content_permissions.setter
33+
@property_is_enum(ContentPermissions)
34+
def content_permissions(self, value):
35+
self._content_permissions = value
36+
3237
@property
3338
def permissions(self):
3439
if self._permissions is None:
@@ -57,11 +62,6 @@ def default_flow_permissions(self):
5762
raise UnpopulatedPropertyError(error)
5863
return self._default_flow_permissions()
5964

60-
@content_permissions.setter
61-
@property_is_enum(ContentPermissions)
62-
def content_permissions(self, value):
63-
self._content_permissions = value
64-
6565
@property
6666
def id(self):
6767
return self._id

0 commit comments

Comments
 (0)
0