durabletask.azuremanaged 0.0.0.dev1__tar.gz

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.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: durabletask.azuremanaged
3
+ Version: 0.0.0.dev1
4
+ Summary: Durable Task Python SDK provider implementation for the Azure Durable Task Scheduler
5
+ Project-URL: repository, https://github.com/microsoft/durabletask-python
6
+ Project-URL: changelog, https://github.com/microsoft/durabletask-python/blob/main/CHANGELOG.md
7
+ Keywords: durable,task,workflow,azure
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: durabletask>=1.1.0
14
+ Requires-Dist: azure-identity>=1.19.0
@@ -0,0 +1,41 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ import logging
5
+
6
+ from typing import Optional
7
+
8
+ from azure.core.credentials import TokenCredential
9
+
10
+ from durabletask.azuremanaged.internal.durabletask_grpc_interceptor import (
11
+ DTSDefaultClientInterceptorImpl,
12
+ )
13
+ from durabletask.client import TaskHubGrpcClient
14
+
15
+
16
+ # Client class used for Durable Task Scheduler (DTS)
17
+ class DurableTaskSchedulerClient(TaskHubGrpcClient):
18
+ def __init__(self, *,
19
+ host_address: str,
20
+ taskhub: str,
21
+ token_credential: Optional[TokenCredential],
22
+ secure_channel: bool = True,
23
+ default_version: Optional[str] = None,
24
+ log_handler: Optional[logging.Handler] = None,
25
+ log_formatter: Optional[logging.Formatter] = None):
26
+
27
+ if not taskhub:
28
+ raise ValueError("Taskhub value cannot be empty. Please provide a value for your taskhub")
29
+
30
+ interceptors = [DTSDefaultClientInterceptorImpl(token_credential, taskhub)]
31
+
32
+ # We pass in None for the metadata so we don't construct an additional interceptor in the parent class
33
+ # Since the parent class doesn't use anything metadata for anything else, we can set it as None
34
+ super().__init__(
35
+ host_address=host_address,
36
+ secure_channel=secure_channel,
37
+ metadata=None,
38
+ log_handler=log_handler,
39
+ log_formatter=log_formatter,
40
+ interceptors=interceptors,
41
+ default_version=default_version)
@@ -0,0 +1,49 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import Optional
5
+
6
+ from azure.core.credentials import AccessToken, TokenCredential
7
+
8
+ import durabletask.internal.shared as shared
9
+
10
+
11
+ # By default, when there's 10minutes left before the token expires, refresh the token
12
+ class AccessTokenManager:
13
+
14
+ _token: Optional[AccessToken]
15
+
16
+ def __init__(self, token_credential: Optional[TokenCredential], refresh_interval_seconds: int = 600):
17
+ self._scope = "https://durabletask.io/.default"
18
+ self._refresh_interval_seconds = refresh_interval_seconds
19
+ self._logger = shared.get_logger("token_manager")
20
+
21
+ self._credential = token_credential
22
+
23
+ if self._credential is not None:
24
+ self._token = self._credential.get_token(self._scope)
25
+ self.expiry_time = datetime.fromtimestamp(self._token.expires_on, tz=timezone.utc)
26
+ else:
27
+ self._token = None
28
+ self.expiry_time = None
29
+
30
+ def get_access_token(self) -> Optional[AccessToken]:
31
+ if self._token is None or self.is_token_expired():
32
+ self.refresh_token()
33
+ return self._token
34
+
35
+ # Checks if the token is expired, or if it will expire in the next "refresh_interval_seconds" seconds.
36
+ # For example, if the token is created to have a lifespan of 2 hours, and the refresh buffer is set to 30 minutes,
37
+ # We will grab a new token when there're 30minutes left on the lifespan of the token
38
+ def is_token_expired(self) -> bool:
39
+ if self.expiry_time is None:
40
+ return True
41
+ return datetime.now(timezone.utc) >= (self.expiry_time - timedelta(seconds=self._refresh_interval_seconds))
42
+
43
+ def refresh_token(self):
44
+ if self._credential is not None:
45
+ self._token = self._credential.get_token(self._scope)
46
+
47
+ # Convert UNIX timestamp to timezone-aware datetime
48
+ self.expiry_time = datetime.fromtimestamp(self._token.expires_on, tz=timezone.utc)
49
+ self._logger.debug(f"Token refreshed. Expires at: {self.expiry_time}")
@@ -0,0 +1,54 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ from importlib.metadata import version
5
+ from typing import Optional
6
+
7
+ import grpc
8
+ from azure.core.credentials import TokenCredential
9
+
10
+ from durabletask.azuremanaged.internal.access_token_manager import AccessTokenManager
11
+ from durabletask.internal.grpc_interceptor import (
12
+ DefaultClientInterceptorImpl,
13
+ _ClientCallDetails,
14
+ )
15
+
16
+
17
+ class DTSDefaultClientInterceptorImpl (DefaultClientInterceptorImpl):
18
+ """The class implements a UnaryUnaryClientInterceptor, UnaryStreamClientInterceptor,
19
+ StreamUnaryClientInterceptor and StreamStreamClientInterceptor from grpc to add an
20
+ interceptor to add additional headers to all calls as needed."""
21
+
22
+ def __init__(self, token_credential: Optional[TokenCredential], taskhub_name: str):
23
+ try:
24
+ # Get the version of the azuremanaged package
25
+ sdk_version = version('durabletask-azuremanaged')
26
+ except Exception:
27
+ # Fallback if version cannot be determined
28
+ sdk_version = "unknown"
29
+ user_agent = f"durabletask-python/{sdk_version}"
30
+ self._metadata = [
31
+ ("taskhub", taskhub_name),
32
+ ("x-user-agent", user_agent)] # 'user-agent' is a reserved header in grpc, so we use 'x-user-agent' instead
33
+ super().__init__(self._metadata)
34
+
35
+ if token_credential is not None:
36
+ self._token_credential = token_credential
37
+ self._token_manager = AccessTokenManager(token_credential=self._token_credential)
38
+ access_token = self._token_manager.get_access_token()
39
+ if access_token is not None:
40
+ self._metadata.append(("authorization", f"Bearer {access_token.token}"))
41
+
42
+ def _intercept_call(
43
+ self, client_call_details: _ClientCallDetails) -> grpc.ClientCallDetails:
44
+ """Internal intercept_call implementation which adds metadata to grpc metadata in the RPC
45
+ call details."""
46
+ # Refresh the auth token if it is present and needed
47
+ if self._metadata is not None:
48
+ for i, (key, _) in enumerate(self._metadata):
49
+ if key.lower() == "authorization": # Ensure case-insensitive comparison
50
+ new_token = self._token_manager.get_access_token() # Get the new token
51
+ if new_token is not None:
52
+ self._metadata[i] = ("authorization", f"Bearer {new_token.token}") # Update the token
53
+
54
+ return super()._intercept_call(client_call_details)
@@ -0,0 +1,83 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ import logging
5
+
6
+ from typing import Optional
7
+
8
+ from azure.core.credentials import TokenCredential
9
+
10
+ from durabletask.azuremanaged.internal.durabletask_grpc_interceptor import \
11
+ DTSDefaultClientInterceptorImpl
12
+ from durabletask.worker import ConcurrencyOptions, TaskHubGrpcWorker
13
+
14
+
15
+ # Worker class used for Durable Task Scheduler (DTS)
16
+ class DurableTaskSchedulerWorker(TaskHubGrpcWorker):
17
+ """A worker implementation for Azure Durable Task Scheduler (DTS).
18
+
19
+ This class extends TaskHubGrpcWorker to provide integration with Azure's
20
+ Durable Task Scheduler service. It handles authentication via Azure credentials
21
+ and configures the necessary gRPC interceptors for DTS communication.
22
+
23
+ Args:
24
+ host_address (str): The gRPC endpoint address of the DTS service.
25
+ taskhub (str): The name of the task hub. Cannot be empty.
26
+ token_credential (Optional[TokenCredential]): Azure credential for authentication.
27
+ If None, anonymous authentication will be used.
28
+ secure_channel (bool, optional): Whether to use a secure gRPC channel (TLS).
29
+ Defaults to True.
30
+ concurrency_options (Optional[ConcurrencyOptions], optional): Configuration
31
+ for controlling worker concurrency limits. If None, default concurrency
32
+ settings will be used.
33
+ log_handler (Optional[logging.Handler], optional): Custom logging handler for worker logs.
34
+ log_formatter (Optional[logging.Formatter], optional): Custom log formatter for worker logs.
35
+
36
+ Raises:
37
+ ValueError: If taskhub is empty or None.
38
+
39
+ Example:
40
+ >>> from azure.identity import DefaultAzureCredential
41
+ >>> from durabletask.azuremanaged import DurableTaskSchedulerWorker
42
+ >>> from durabletask.worker import ConcurrencyOptions
43
+ >>>
44
+ >>> credential = DefaultAzureCredential()
45
+ >>> concurrency = ConcurrencyOptions(max_concurrent_activities=10)
46
+ >>> worker = DurableTaskSchedulerWorker(
47
+ ... host_address="my-dts-service.azure.com:443",
48
+ ... taskhub="my-task-hub",
49
+ ... token_credential=credential,
50
+ ... concurrency_options=concurrency
51
+ ... )
52
+
53
+ Note:
54
+ This worker automatically configures DTS-specific gRPC interceptors
55
+ for authentication and task hub routing. The parent class metadata
56
+ parameter is set to None since authentication is handled by the
57
+ DTS interceptor.
58
+ """
59
+
60
+ def __init__(self, *,
61
+ host_address: str,
62
+ taskhub: str,
63
+ token_credential: Optional[TokenCredential],
64
+ secure_channel: bool = True,
65
+ concurrency_options: Optional[ConcurrencyOptions] = None,
66
+ log_handler: Optional[logging.Handler] = None,
67
+ log_formatter: Optional[logging.Formatter] = None):
68
+
69
+ if not taskhub:
70
+ raise ValueError("The taskhub value cannot be empty.")
71
+
72
+ interceptors = [DTSDefaultClientInterceptorImpl(token_credential, taskhub)]
73
+
74
+ # We pass in None for the metadata so we don't construct an additional interceptor in the parent class
75
+ # Since the parent class doesn't use anything metadata for anything else, we can set it as None
76
+ super().__init__(
77
+ host_address=host_address,
78
+ secure_channel=secure_channel,
79
+ metadata=None,
80
+ log_handler=log_handler,
81
+ log_formatter=log_formatter,
82
+ interceptors=interceptors,
83
+ concurrency_options=concurrency_options)
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: durabletask.azuremanaged
3
+ Version: 0.0.0.dev1
4
+ Summary: Durable Task Python SDK provider implementation for the Azure Durable Task Scheduler
5
+ Project-URL: repository, https://github.com/microsoft/durabletask-python
6
+ Project-URL: changelog, https://github.com/microsoft/durabletask-python/blob/main/CHANGELOG.md
7
+ Keywords: durable,task,workflow,azure
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: durabletask>=1.1.0
14
+ Requires-Dist: azure-identity>=1.19.0
@@ -0,0 +1,13 @@
1
+ pyproject.toml
2
+ durabletask.azuremanaged.egg-info/PKG-INFO
3
+ durabletask.azuremanaged.egg-info/SOURCES.txt
4
+ durabletask.azuremanaged.egg-info/dependency_links.txt
5
+ durabletask.azuremanaged.egg-info/requires.txt
6
+ durabletask.azuremanaged.egg-info/top_level.txt
7
+ durabletask/azuremanaged/__init__.py
8
+ durabletask/azuremanaged/client.py
9
+ durabletask/azuremanaged/py.typed
10
+ durabletask/azuremanaged/worker.py
11
+ durabletask/azuremanaged/internal/access_token_manager.py
12
+ durabletask/azuremanaged/internal/durabletask_grpc_interceptor.py
13
+ durabletask/azuremanaged/internal/py.typed
@@ -0,0 +1,2 @@
1
+ durabletask>=1.1.0
2
+ azure-identity>=1.19.0
@@ -0,0 +1,41 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+
4
+ # For more information on pyproject.toml, see https://peps.python.org/pep-0621/
5
+
6
+ [build-system]
7
+ requires = ["setuptools", "wheel"]
8
+ build-backend = "setuptools.build_meta"
9
+
10
+ [project]
11
+ name = "durabletask.azuremanaged"
12
+ version = "0.0.0.dev1"
13
+ description = "Durable Task Python SDK provider implementation for the Azure Durable Task Scheduler"
14
+ keywords = [
15
+ "durable",
16
+ "task",
17
+ "workflow",
18
+ "azure"
19
+ ]
20
+ classifiers = [
21
+ "Development Status :: 5 - Production/Stable",
22
+ "Programming Language :: Python :: 3",
23
+ "License :: OSI Approved :: MIT License",
24
+ ]
25
+ requires-python = ">=3.10"
26
+ license = {file = "LICENSE"}
27
+ readme = "README.md"
28
+ dependencies = [
29
+ "durabletask>=1.1.0",
30
+ "azure-identity>=1.19.0"
31
+ ]
32
+
33
+ [project.urls]
34
+ repository = "https://github.com/microsoft/durabletask-python"
35
+ changelog = "https://github.com/microsoft/durabletask-python/blob/main/CHANGELOG.md"
36
+
37
+ [tool.setuptools.packages.find]
38
+ include = ["durabletask.azuremanaged", "durabletask.azuremanaged.*"]
39
+
40
+ [tool.pytest.ini_options]
41
+ minversion = "6.0"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+