Skip to content

Commit 92fbd6f

Browse files
authored
The project now typechecks, improved IntelliSense (#164)
1 parent aae83c9 commit 92fbd6f

30 files changed

+335
-231
lines changed

azure/durable_functions/models/DurableHttpRequest.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
from typing import Dict, Any
1+
from typing import Dict, Union, Optional
22

3-
from azure.durable_functions.models import TokenSource
3+
from azure.durable_functions.models.TokenSource import TokenSource
44
from azure.durable_functions.models.utils.json_utils import add_attrib, add_json_attrib
55

66

77
class DurableHttpRequest:
88
"""Data structure representing a durable HTTP request."""
99

10-
def __init__(self, method: str, uri: str, content: str = None, headers: Dict[str, str] = None,
11-
token_source: TokenSource = None):
10+
def __init__(self, method: str, uri: str, content: Optional[str] = None,
11+
headers: Optional[Dict[str, str]] = None,
12+
token_source: Optional[TokenSource] = None):
1213
self._method: str = method
1314
self._uri: str = uri
14-
self._content: str = content
15-
self._headers: Dict[str, str] = headers
16-
self._token_source: TokenSource = token_source
15+
self._content: Optional[str] = content
16+
self._headers: Optional[Dict[str, str]] = headers
17+
self._token_source: Optional[TokenSource] = token_source
1718

1819
@property
1920
def method(self) -> str:
@@ -26,29 +27,29 @@ def uri(self) -> str:
2627
return self._uri
2728

2829
@property
29-
def content(self) -> str:
30+
def content(self) -> Optional[str]:
3031
"""Get the HTTP request content."""
3132
return self._content
3233

3334
@property
34-
def headers(self) -> Dict[str, str]:
35+
def headers(self) -> Optional[Dict[str, str]]:
3536
"""Get the HTTP request headers."""
3637
return self._headers
3738

3839
@property
39-
def token_source(self) -> TokenSource:
40+
def token_source(self) -> Optional[TokenSource]:
4041
"""Get the source of OAuth token to add to the request."""
4142
return self._token_source
4243

43-
def to_json(self) -> Dict[str, Any]:
44+
def to_json(self) -> Dict[str, Union[str, int]]:
4445
"""Convert object into a json dictionary.
4546
4647
Returns
4748
-------
48-
Dict[str, Any]
49+
Dict[str, Union[str, int]]
4950
The instance of the class converted into a json dictionary
5051
"""
51-
json_dict = {}
52+
json_dict: Dict[str, Union[str, int]] = {}
5253
add_attrib(json_dict, self, 'method')
5354
add_attrib(json_dict, self, 'uri')
5455
add_attrib(json_dict, self, 'content')

azure/durable_functions/models/DurableOrchestrationBindings.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import Dict
2+
from typing import Dict, Optional
33

44
from azure.durable_functions.models.FunctionContext import FunctionContext
55

@@ -13,11 +13,13 @@ class DurableOrchestrationBindings:
1313

1414
# parameter names are as defined by JSON schema and do not conform to PEP8 naming conventions
1515
def __init__(self, taskHubName: str, creationUrls: Dict[str, str],
16-
managementUrls: Dict[str, str], rpcBaseUrl: str = None, **kwargs):
16+
managementUrls: Dict[str, str], rpcBaseUrl: Optional[str] = None, **kwargs):
1717
self._task_hub_name: str = taskHubName
1818
self._creation_urls: Dict[str, str] = creationUrls
1919
self._management_urls: Dict[str, str] = managementUrls
20-
self._rpc_base_url: str = rpcBaseUrl
20+
# TODO: we can remove the Optional once we drop support for 1.x,
21+
# this is always provided in 2.x
22+
self._rpc_base_url: Optional[str] = rpcBaseUrl
2123
self._client_data = FunctionContext(**kwargs)
2224

2325
@property
@@ -36,7 +38,7 @@ def management_urls(self) -> Dict[str, str]:
3638
return self._management_urls
3739

3840
@property
39-
def rpc_base_url(self) -> str:
41+
def rpc_base_url(self) -> Optional[str]:
4042
"""Get the base url communication between out of proc workers and the function host."""
4143
return self._rpc_base_url
4244

azure/durable_functions/models/DurableOrchestrationClient.py

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from datetime import datetime
3-
from typing import List, Any, Awaitable, Optional, Dict
3+
from typing import List, Any, Optional, Dict, Union
44
from time import time
55
from asyncio import sleep
66
from urllib.parse import urlparse, quote
@@ -11,7 +11,7 @@
1111
from .DurableOrchestrationStatus import DurableOrchestrationStatus
1212
from .RpcManagementOptions import RpcManagementOptions
1313
from .OrchestrationRuntimeStatus import OrchestrationRuntimeStatus
14-
from ..models import DurableOrchestrationBindings
14+
from ..models.DurableOrchestrationBindings import DurableOrchestrationBindings
1515
from .utils.http_utils import get_async_request, post_async_request, delete_async_request
1616
from azure.functions._durable_functions import _serialize_custom_object
1717

@@ -44,8 +44,8 @@ def __init__(self, context: str):
4444

4545
async def start_new(self,
4646
orchestration_function_name: str,
47-
instance_id: str = None,
48-
client_input: object = None) -> Awaitable[str]:
47+
instance_id: Optional[str] = None,
48+
client_input: Optional[Any] = None) -> str:
4949
"""Start a new instance of the specified orchestrator function.
5050
5151
If an orchestration instance with the specified ID already exists, the
@@ -55,10 +55,10 @@ async def start_new(self,
5555
----------
5656
orchestration_function_name : str
5757
The name of the orchestrator function to start.
58-
instance_id : str
58+
instance_id : Optional[str]
5959
The ID to use for the new orchestration instance. If no instance id is specified,
6060
the Durable Functions extension will generate a random GUID (recommended).
61-
client_input : object
61+
client_input : Optional[Any]
6262
JSON-serializable input value for the orchestrator function.
6363
6464
Returns
@@ -69,22 +69,25 @@ async def start_new(self,
6969
request_url = self._get_start_new_url(
7070
instance_id=instance_id, orchestration_function_name=orchestration_function_name)
7171

72-
response = await self._post_async_request(request_url, self._get_json_input(client_input))
72+
response: List[Any] = await self._post_async_request(
73+
request_url, self._get_json_input(client_input))
7374

74-
if response[0] <= 202 and response[1]:
75+
status_code: int = response[0]
76+
if status_code <= 202 and response[1]:
7577
return response[1]["id"]
76-
elif response[0] == 400:
78+
elif status_code == 400:
7779
# Orchestrator not found, report clean exception
78-
exception_data = response[1]
80+
exception_data: Dict[str, str] = response[1]
7981
exception_message = exception_data["ExceptionMessage"]
8082
raise Exception(exception_message)
8183
else:
8284
# Catch all: simply surfacing the durable-extension exception
8385
# we surface the stack trace too, since this may be a more involed exception
84-
exception_message = response[1]
85-
raise Exception(exception_message)
86+
ex_message: Any = response[1]
87+
raise Exception(ex_message)
8688

87-
def create_check_status_response(self, request, instance_id):
89+
def create_check_status_response(
90+
self, request: func.HttpRequest, instance_id: str) -> func.HttpResponse:
8891
"""Create a HttpResponse that contains useful information for \
8992
checking the status of the specified instance.
9093
@@ -148,16 +151,16 @@ def get_client_response_links(
148151
payload = self._orchestration_bindings.management_urls.copy()
149152

150153
for key, _ in payload.items():
151-
request_is_not_none = not (request is None)
152-
if request_is_not_none and request.url:
154+
if not(request is None) and request.url:
153155
payload[key] = self._replace_url_origin(request.url, payload[key])
154156
payload[key] = payload[key].replace(
155157
self._orchestration_bindings.management_urls["id"], instance_id)
156158

157159
return payload
158160

159-
async def raise_event(self, instance_id, event_name, event_data=None,
160-
task_hub_name=None, connection_name=None):
161+
async def raise_event(
162+
self, instance_id: str, event_name: str, event_data: Any = None,
163+
task_hub_name: str = None, connection_name: str = None) -> None:
161164
"""Send an event notification message to a waiting orchestration instance.
162165
163166
In order to handle the event, the target orchestration instance must be
@@ -169,7 +172,7 @@ async def raise_event(self, instance_id, event_name, event_data=None,
169172
The ID of the orchestration instance that will handle the event.
170173
event_name : str
171174
The name of the event.
172-
event_data : any, optional
175+
event_data : Any, optional
173176
The JSON-serializable data associated with the event.
174177
task_hub_name : str, optional
175178
The TaskHubName of the orchestration that will handle the event.
@@ -183,8 +186,8 @@ async def raise_event(self, instance_id, event_name, event_data=None,
183186
Exception
184187
Raises an exception if the status code is 404 or 400 when raising the event.
185188
"""
186-
if not event_name:
187-
raise ValueError("event_name must be a valid string.")
189+
if event_name == "":
190+
raise ValueError("event_name must be a non-empty string.")
188191

189192
request_url = self._get_raise_event_url(
190193
instance_id, event_name, task_hub_name, connection_name)
@@ -203,9 +206,9 @@ async def raise_event(self, instance_id, event_name, event_data=None,
203206
if error_message:
204207
raise Exception(error_message)
205208

206-
async def get_status(self, instance_id: str, show_history: bool = None,
207-
show_history_output: bool = None,
208-
show_input: bool = None) -> DurableOrchestrationStatus:
209+
async def get_status(self, instance_id: str, show_history: bool = False,
210+
show_history_output: bool = False,
211+
show_input: bool = False) -> DurableOrchestrationStatus:
209212
"""Get the status of the specified orchestration instance.
210213
211214
Parameters
@@ -268,7 +271,8 @@ async def get_status_all(self) -> List[DurableOrchestrationStatus]:
268271
if error_message:
269272
raise Exception(error_message)
270273
else:
271-
return [DurableOrchestrationStatus.from_json(o) for o in response[1]]
274+
statuses: List[Any] = response[1]
275+
return [DurableOrchestrationStatus.from_json(o) for o in statuses]
272276

273277
async def get_status_by(self, created_time_from: datetime = None,
274278
created_time_to: datetime = None,
@@ -291,6 +295,7 @@ async def get_status_by(self, created_time_from: datetime = None,
291295
DurableOrchestrationStatus
292296
The status of the requested orchestration instances
293297
"""
298+
# TODO: do we really want folks to us this without specifying all the args?
294299
options = RpcManagementOptions(created_time_from=created_time_from,
295300
created_time_to=created_time_to,
296301
runtime_status=runtime_status)
@@ -326,19 +331,20 @@ async def purge_instance_history(self, instance_id: str) -> PurgeHistoryResult:
326331
response = await self._delete_async_request(request_url)
327332
return self._parse_purge_instance_history_response(response)
328333

329-
async def purge_instance_history_by(self, created_time_from: datetime = None,
330-
created_time_to: datetime = None,
331-
runtime_status: List[OrchestrationRuntimeStatus] = None) \
334+
async def purge_instance_history_by(
335+
self, created_time_from: Optional[datetime] = None,
336+
created_time_to: Optional[datetime] = None,
337+
runtime_status: Optional[List[OrchestrationRuntimeStatus]] = None) \
332338
-> PurgeHistoryResult:
333339
"""Delete the history of all orchestration instances that match the specified conditions.
334340
335341
Parameters
336342
----------
337-
created_time_from : datetime
343+
created_time_from : Optional[datetime]
338344
Delete orchestration history which were created after this Date.
339-
created_time_to: datetime
345+
created_time_to: Optional[datetime]
340346
Delete orchestration history which were created before this Date.
341-
runtime_status: List[OrchestrationRuntimeStatus]
347+
runtime_status: Optional[List[OrchestrationRuntimeStatus]]
342348
Delete orchestration instances which match any of the runtimeStatus values
343349
in this list.
344350
@@ -347,14 +353,15 @@ async def purge_instance_history_by(self, created_time_from: datetime = None,
347353
PurgeHistoryResult
348354
The results of the request to purge history
349355
"""
356+
# TODO: do we really want folks to us this without specifying all the args?
350357
options = RpcManagementOptions(created_time_from=created_time_from,
351358
created_time_to=created_time_to,
352359
runtime_status=runtime_status)
353360
request_url = options.to_url(self._orchestration_bindings.rpc_base_url)
354361
response = await self._delete_async_request(request_url)
355362
return self._parse_purge_instance_history_response(response)
356363

357-
async def terminate(self, instance_id: str, reason: str):
364+
async def terminate(self, instance_id: str, reason: str) -> None:
358365
"""Terminate the specified orchestration instance.
359366
360367
Parameters
@@ -364,6 +371,11 @@ async def terminate(self, instance_id: str, reason: str):
364371
reason: str
365372
The reason for terminating the instance.
366373
374+
Raises
375+
------
376+
Exception:
377+
When the terminate call failed with an unexpected status code
378+
367379
Returns
368380
-------
369381
None
@@ -446,7 +458,8 @@ async def wait_for_completion_or_create_check_status_response(
446458
return self.create_check_status_response(request, instance_id)
447459

448460
@staticmethod
449-
def _create_http_response(status_code: int, body: Any) -> func.HttpResponse:
461+
def _create_http_response(
462+
status_code: int, body: Union[str, Any]) -> func.HttpResponse:
450463
body_as_json = body if isinstance(body, str) else json.dumps(body)
451464
response_args = {
452465
"status_code": status_code,
@@ -459,7 +472,7 @@ def _create_http_response(status_code: int, body: Any) -> func.HttpResponse:
459472
return func.HttpResponse(**response_args)
460473

461474
@staticmethod
462-
def _get_json_input(client_input: object) -> str:
475+
def _get_json_input(client_input: object) -> Optional[str]:
463476
"""Serialize the orchestrator input.
464477
465478
Parameters
@@ -469,8 +482,10 @@ def _get_json_input(client_input: object) -> str:
469482
470483
Returns
471484
-------
472-
str
473-
A string representing the JSON-serialization of `client_input`
485+
Optional[str]
486+
If `client_input` is not None, return a string representing
487+
the JSON-serialization of `client_input`. Otherwise, returns
488+
None
474489
475490
Exceptions
476491
----------
@@ -482,7 +497,7 @@ def _get_json_input(client_input: object) -> str:
482497
return None
483498

484499
@staticmethod
485-
def _replace_url_origin(request_url, value_url):
500+
def _replace_url_origin(request_url: str, value_url: str) -> str:
486501
request_parsed_url = urlparse(request_url)
487502
value_parsed_url = urlparse(value_url)
488503
request_url_origin = '{url.scheme}://{url.netloc}/'.format(url=request_parsed_url)
@@ -491,7 +506,8 @@ def _replace_url_origin(request_url, value_url):
491506
return value_url
492507

493508
@staticmethod
494-
def _parse_purge_instance_history_response(response: [int, Any]):
509+
def _parse_purge_instance_history_response(
510+
response: List[Any]) -> PurgeHistoryResult:
495511
switch_statement = {
496512
200: lambda: PurgeHistoryResult.from_json(response[1]), # instance completed
497513
404: lambda: PurgeHistoryResult(instancesDeleted=0), # instance not found
@@ -506,17 +522,20 @@ def _parse_purge_instance_history_response(response: [int, Any]):
506522
else:
507523
raise Exception(result)
508524

509-
def _get_start_new_url(self, instance_id, orchestration_function_name):
525+
def _get_start_new_url(
526+
self, instance_id: Optional[str], orchestration_function_name: str) -> str:
510527
instance_path = f'/{instance_id}' if instance_id is not None else ''
511528
request_url = f'{self._orchestration_bindings.rpc_base_url}orchestrators/' \
512529
f'{orchestration_function_name}{instance_path}'
513530
return request_url
514531

515-
def _get_raise_event_url(self, instance_id, event_name, task_hub_name, connection_name):
532+
def _get_raise_event_url(
533+
self, instance_id: str, event_name: str,
534+
task_hub_name: Optional[str], connection_name: Optional[str]) -> str:
516535
request_url = f'{self._orchestration_bindings.rpc_base_url}' \
517536
f'instances/{instance_id}/raiseEvent/{event_name}'
518537

519-
query = []
538+
query: List[str] = []
520539
if task_hub_name:
521540
query.append(f'taskHub={task_hub_name}')
522541

0 commit comments

Comments
 (0)