2
2
import logging
3
3
import os
4
4
import time
5
+ from typing import Optional , Callable , Union
5
6
6
7
from oauthlib .oauth2 import TokenExpiredError , WebApplicationClient , BackendApplicationClient , LegacyApplicationClient
7
8
from requests import Session
@@ -68,64 +69,68 @@ class Protocol:
68
69
_oauth_scope_prefix = '' # Prefix for scopes
69
70
_oauth_scopes = {} # Dictionary of {scopes_name: [scope1, scope2]}
70
71
71
- def __init__ (self , * , protocol_url = None , api_version = None ,
72
- default_resource = None ,
73
- casing_function = None , protocol_scope_prefix = None ,
74
- timezone = None , ** kwargs ):
72
+ def __init__ (self , * , protocol_url : Optional [str ] = None ,
73
+ api_version : Optional [str ] = None ,
74
+ default_resource : Optional [str ] = None ,
75
+ casing_function : Optional [Callable ] = None ,
76
+ protocol_scope_prefix : Optional [str ] = None ,
77
+ timezone : Union [Optional [str ], Optional [ZoneInfo ]] = None , ** kwargs ):
75
78
""" Create a new protocol object
76
79
77
- :param str protocol_url: the base url used to communicate with the
80
+ :param protocol_url: the base url used to communicate with the
78
81
server
79
- :param str api_version: the api version
80
- :param str default_resource: the default resource to use when there is
82
+ :param api_version: the api version
83
+ :param default_resource: the default resource to use when there is
81
84
nothing explicitly specified during the requests
82
- :param function casing_function: the casing transform function to be
85
+ :param casing_function: the casing transform function to be
83
86
used on api keywords (camelcase / pascalcase)
84
- :param str protocol_scope_prefix: prefix url for scopes
85
- :param datetime. timezone.utc or str timezone : preferred timezone, defaults to the
86
- system timezone or fallback to UTC
87
+ :param protocol_scope_prefix: prefix url for scopes
88
+ :param timezone: preferred timezone, if not provided will default
89
+ to the system timezone or fallback to UTC
87
90
:raises ValueError: if protocol_url or api_version are not supplied
88
91
"""
89
92
if protocol_url is None or api_version is None :
90
93
raise ValueError (
91
94
'Must provide valid protocol_url and api_version values' )
92
- self .protocol_url = protocol_url or self ._protocol_url
93
- self .protocol_scope_prefix = protocol_scope_prefix or ''
94
- self .api_version = api_version
95
- self .service_url = '{}{}/' .format (protocol_url , api_version )
96
- self .default_resource = default_resource or ME_RESOURCE
97
- self .use_default_casing = True if casing_function is None else False
98
- self .casing_function = casing_function or camelcase
95
+ self .protocol_url : str = protocol_url or self ._protocol_url
96
+ self .protocol_scope_prefix : str = protocol_scope_prefix or ''
97
+ self .api_version : str = api_version
98
+ self .service_url : str = '{}{}/' .format (protocol_url , api_version )
99
+ self .default_resource : str = default_resource or ME_RESOURCE
100
+ self .use_default_casing : bool = True if casing_function is None else False
101
+ self .casing_function : Callable = casing_function or camelcase
102
+
99
103
if timezone :
100
104
if isinstance (timezone , str ):
101
105
# convert string to ZoneInfo
102
106
try :
103
107
timezone = ZoneInfo (timezone )
104
- except ZoneInfoNotFoundError :
105
- log .debug (f'Timezone { timezone } could not be found. Will default to UTC .' )
106
- timezone = ZoneInfo ( 'UTC' )
108
+ except ZoneInfoNotFoundError as e :
109
+ log .error (f'Timezone { timezone } could not be found.' )
110
+ raise e
107
111
else :
108
112
if not isinstance (timezone , ZoneInfo ):
109
113
raise ValueError (f'The timezone parameter must be either a string or a valid ZoneInfo instance.' )
114
+
110
115
# get_localzone() from tzlocal will try to get the system local timezone and if not will return UTC
111
- self .timezone = timezone or get_localzone ()
112
- self .max_top_value = 500 # Max $top parameter value
116
+ self .timezone : ZoneInfo = timezone or get_localzone ()
117
+ self .max_top_value : int = 500 # Max $top parameter value
113
118
114
119
# define any keyword that can be different in this protocol
115
- # for example, attachments Odata type differs between Outlook
120
+ # for example, attachments OData type differs between Outlook
116
121
# rest api and graph: (graph = #microsoft.graph.fileAttachment and
117
122
# outlook = #Microsoft.OutlookServices.FileAttachment')
118
- self .keyword_data_store = {}
123
+ self .keyword_data_store : dict = {}
119
124
120
- def get_service_keyword (self , keyword ) :
125
+ def get_service_keyword (self , keyword : str ) -> str :
121
126
""" Returns the data set to the key in the internal data-key dict
122
127
123
- :param str keyword: key to get value for
128
+ :param keyword: key to get value for
124
129
:return: value of the keyword
125
130
"""
126
131
return self .keyword_data_store .get (keyword , None )
127
132
128
- def convert_case (self , key ) :
133
+ def convert_case (self , key : str ) -> str :
129
134
""" Returns a key converted with this protocol casing method
130
135
131
136
Converts case to send/read from the cloud
@@ -137,30 +142,26 @@ def convert_case(self, key):
137
142
138
143
Default case in this API is lowerCamelCase
139
144
140
- :param str key: a dictionary key to convert
145
+ :param key: a dictionary key to convert
141
146
:return: key after case conversion
142
- :rtype: str
143
147
"""
144
148
return key if self .use_default_casing else self .casing_function (key )
145
149
146
150
@staticmethod
147
- def to_api_case (key ) :
151
+ def to_api_case (key : str ) -> str :
148
152
""" Converts key to snake_case
149
153
150
- :param str key: key to convert into snake_case
154
+ :param key: key to convert into snake_case
151
155
:return: key after case conversion
152
- :rtype: str
153
156
"""
154
157
return snakecase (key )
155
158
156
- def get_scopes_for (self , user_provided_scopes ) :
159
+ def get_scopes_for (self , user_provided_scopes : Optional [ Union [ list , str , tuple ]]) -> list :
157
160
""" Returns a list of scopes needed for each of the
158
161
scope_helpers provided, by adding the prefix to them if required
159
162
160
163
:param user_provided_scopes: a list of scopes or scope helpers
161
- :type user_provided_scopes: list or tuple or str
162
164
:return: scopes with url prefix added
163
- :rtype: list
164
165
:raises ValueError: if unexpected datatype of scopes are passed
165
166
"""
166
167
if user_provided_scopes is None :
@@ -170,8 +171,7 @@ def get_scopes_for(self, user_provided_scopes):
170
171
user_provided_scopes = [user_provided_scopes ]
171
172
172
173
if not isinstance (user_provided_scopes , (list , tuple )):
173
- raise ValueError (
174
- "'user_provided_scopes' must be a list or a tuple of strings" )
174
+ raise ValueError ("'user_provided_scopes' must be a list or a tuple of strings" )
175
175
176
176
scopes = set ()
177
177
for app_part in user_provided_scopes :
@@ -180,7 +180,7 @@ def get_scopes_for(self, user_provided_scopes):
180
180
181
181
return list (scopes )
182
182
183
- def prefix_scope (self , scope ) :
183
+ def prefix_scope (self , scope : Union [ tuple , str ]) -> str :
184
184
""" Inserts the protocol scope prefix if required"""
185
185
if self .protocol_scope_prefix :
186
186
if isinstance (scope , tuple ):
@@ -275,7 +275,6 @@ def __init__(self, api_version='v2.0', default_resource=None,
275
275
276
276
277
277
class MSBusinessCentral365Protocol (Protocol ):
278
-
279
278
""" A Microsoft Business Central Protocol Implementation
280
279
https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/endpoints-apis-for-dynamics
281
280
"""
@@ -285,7 +284,7 @@ class MSBusinessCentral365Protocol(Protocol):
285
284
_oauth_scopes = DEFAULT_SCOPES
286
285
_protocol_scope_prefix = 'https://api.businesscentral.dynamics.com/'
287
286
288
- def __init__ (self , api_version = 'v1.0' , default_resource = None ,environment = None ,
287
+ def __init__ (self , api_version = 'v1.0' , default_resource = None , environment = None ,
289
288
** kwargs ):
290
289
""" Create a new Microsoft Graph protocol object
291
290
@@ -299,7 +298,7 @@ def __init__(self, api_version='v1.0', default_resource=None,environment=None,
299
298
"""
300
299
if environment :
301
300
_version = "2.0"
302
- _environment = "/" + environment
301
+ _environment = "/" + environment
303
302
else :
304
303
_version = "1.0"
305
304
_environment = ''
@@ -385,7 +384,8 @@ def __init__(self, credentials, *, scopes=None,
385
384
if not isinstance (credentials , tuple ) or len (credentials ) != 1 or (not credentials [0 ]):
386
385
raise ValueError ('Provide client id only for public or password flow credentials' )
387
386
else :
388
- if not isinstance (credentials , tuple ) or len (credentials ) != 2 or (not credentials [0 ] and not credentials [1 ]):
387
+ if not isinstance (credentials , tuple ) or len (credentials ) != 2 or (
388
+ not credentials [0 ] and not credentials [1 ]):
389
389
raise ValueError ('Provide valid auth credentials' )
390
390
391
391
self ._auth_flow_type = auth_flow_type # 'authorization', 'credentials', 'certificate', 'password', or 'public'
@@ -440,12 +440,12 @@ def set_proxy(self, proxy_server, proxy_port, proxy_username,
440
440
if proxy_server and proxy_port :
441
441
if proxy_username and proxy_password :
442
442
proxy_uri = "{}:{}@{}:{}" .format (proxy_username ,
443
- proxy_password ,
444
- proxy_server ,
445
- proxy_port )
443
+ proxy_password ,
444
+ proxy_server ,
445
+ proxy_port )
446
446
else :
447
447
proxy_uri = "{}:{}" .format (proxy_server ,
448
- proxy_port )
448
+ proxy_port )
449
449
450
450
if proxy_http_only is False :
451
451
self .proxy = {
@@ -823,7 +823,8 @@ def _internal_request(self, request_obj, url, method, **kwargs):
823
823
log .debug ('Server Error: {}' .format (str (e )))
824
824
if self .raise_http_errors :
825
825
if error_message :
826
- raise HTTPError ('{} | Error Message: {}' .format (e .args [0 ], error_message ), response = response ) from None
826
+ raise HTTPError ('{} | Error Message: {}' .format (e .args [0 ], error_message ),
827
+ response = response ) from None
827
828
else :
828
829
raise e
829
830
else :
@@ -925,7 +926,7 @@ def __del__(self):
925
926
But this is not an issue because this connections will be automatically closed.
926
927
"""
927
928
if hasattr (self , 'session' ) and self .session is not None :
928
- self .session .close ()
929
+ self .session .close ()
929
930
930
931
931
932
def oauth_authentication_flow (client_id , client_secret , scopes = None ,
0 commit comments