Skip to content

Commit 802e750

Browse files
authored
[Key Vault] Docstring improvements for custom polling code (#42692)
1 parent da8643d commit 802e750

File tree

2 files changed

+108
-17
lines changed

2 files changed

+108
-17
lines changed

sdk/keyvault/azure-keyvault-securitydomain/azure/keyvault/securitydomain/_internal/async_polling.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020

2121

2222
class AsyncPollingTerminationMixin(AsyncLROBasePolling):
23+
"""Mixin to correctly handle polling termination.
24+
25+
Uses a custom implementation of `finished` because Security Domain LROs return "Success" as a terminal response
26+
instead of the standard "Succeeded". At the time of writing, there's no way to more easily patch the base poller
27+
from `azure-core` to handle this.
28+
"""
29+
2330
def finished(self) -> bool:
2431
"""Is this polling finished?
2532
@@ -54,6 +61,12 @@ def parse_resource(
5461

5562

5663
class AsyncNoPollingMixin(AsyncLROBasePolling):
64+
"""Mixin that intentionally bypasses any polling by immediately returning a success status.
65+
66+
The Azure CLI accepts a `--no-wait` parameter in download and upload operations, allowing users to immediately get
67+
the result before HSM activation completes. This polling logic is used to support that behavior.
68+
"""
69+
5770
def finished(self) -> bool:
5871
"""Is this polling finished?
5972
@@ -75,6 +88,8 @@ def result(self, *args, **kwargs): # pylint: disable=unused-argument
7588

7689

7790
class AsyncSecurityDomainDownloadPollingMethod(AsyncPollingTerminationMixin, AsyncLROBasePolling):
91+
"""Polling method for the unique pattern of security domain download operations."""
92+
7893
def initialize(
7994
self,
8095
client: AsyncPipelineClient[Any, Any],
@@ -102,20 +117,32 @@ def get_long_running_output(pipeline_response):
102117
super().initialize(client, initial_response, get_long_running_output)
103118

104119
def resource(self) -> SecurityDomain:
105-
"""Return the built resource.
120+
"""Return the security domain deserialized from the initial response.
106121
107-
:rtype: any
108-
:return: The built resource.
122+
This returns the final result of the `SecurityDomainClient.begin_download` operation by deserializing the
123+
initial response. This is an unusual LRO pattern and requires custom support. Usually, the object returned from
124+
an LRO is only returned as part of the terminal status response; in Security Domain, the download operation
125+
instead immediately returns the security domain object, and the terminal response only includes the activation
126+
status.
127+
128+
:rtype: ~azure.keyvault.securitydomain.SecurityDomain
129+
:return: The security domain object.
109130
"""
110131
# The final response should actually be the security domain object that was returned in the initial response
111132
return cast(SecurityDomain, self.parse_resource(self._initial_response))
112133

113134

114135
class AsyncSecurityDomainDownloadNoPolling(AsyncSecurityDomainDownloadPollingMethod, AsyncNoPollingMixin):
115-
pass
136+
"""Polling method for security domain download operations that bypass polling."""
116137

117138

118139
class AsyncSecurityDomainUploadPolling(SecurityDomainDownloadPolling):
140+
"""Polling logic for security domain upload operations.
141+
142+
This class inherits from `SecurityDomainDownloadPolling` but uses the actual initial response status since the
143+
upload operation has a more typical LRO resource pattern.
144+
"""
145+
119146
def set_initial_status(self, pipeline_response: PipelineResponse) -> str:
120147
response: AsyncHttpResponse = pipeline_response.http_response
121148
self._polling_url = response.headers["azure-asyncoperation"]
@@ -126,6 +153,13 @@ def set_initial_status(self, pipeline_response: PipelineResponse) -> str:
126153

127154

128155
class AsyncSecurityDomainUploadPollingMethod(AsyncPollingTerminationMixin, AsyncLROBasePolling):
156+
"""Polling method that will poll the HSM's activation but returns None.
157+
158+
This is manually done because the generated implementation returns a poller with a status monitor for a final
159+
result. Python guidelines suggest returning None instead in this scenario, since the polling status can already be
160+
accessed from the poller object.
161+
"""
162+
129163
def initialize(
130164
self,
131165
client: AsyncPipelineClient[Any, Any],
@@ -152,12 +186,13 @@ def get_long_running_output(_):
152186
super().initialize(client, initial_response, get_long_running_output)
153187

154188
def resource(self) -> None:
155-
"""Return the built resource.
189+
"""Return the final resource -- in this case, None.
156190
157-
:rtype: any
158-
:return: The built resource.
191+
:rtype: None
192+
:return: The final resource -- in this case, None.
159193
"""
160194
return None
161195

196+
162197
class AsyncSecurityDomainUploadNoPolling(AsyncSecurityDomainUploadPollingMethod, AsyncNoPollingMixin):
163-
pass
198+
"""Polling method for security domain upload operations that bypass polling."""

sdk/keyvault/azure-keyvault-securitydomain/azure/keyvault/securitydomain/_internal/polling.py

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
PollingReturnType_co = TypeVar("PollingReturnType_co", covariant=True)
1818

19-
# The correct success response should be "Succeeded", but this has already shipped. Handle "Success" just in case.
19+
# Correct success response should be "Succeeded", but this has already shipped. Still, handle "Succeeded" just in case.
2020
_FINISHED = frozenset(["succeeded", "success", "canceled", "failed"])
2121

2222

@@ -38,6 +38,13 @@ def _is_empty(response: Union[HttpResponse, AsyncHttpResponse]) -> bool:
3838

3939

4040
class PollingTerminationMixin(LROBasePolling):
41+
"""Mixin to correctly handle polling termination.
42+
43+
Uses a custom implementation of `finished` because Security Domain LROs return "Success" as a terminal response
44+
instead of the standard "Succeeded". At the time of writing, there's no way to more easily patch the base poller
45+
from `azure-core` to handle this.
46+
"""
47+
4148
def finished(self) -> bool:
4249
"""Is this polling finished?
4350
@@ -72,6 +79,12 @@ def parse_resource(
7279

7380

7481
class NoPollingMixin(LROBasePolling):
82+
"""Mixin that intentionally bypasses any polling by immediately returning a success status.
83+
84+
The Azure CLI accepts a `--no-wait` parameter in download and upload operations, allowing users to immediately get
85+
the result before HSM activation completes. This polling logic is used to support that behavior.
86+
"""
87+
7588
def finished(self) -> bool:
7689
"""Is this polling finished?
7790
@@ -93,6 +106,8 @@ def result(self, *args, **kwargs): # pylint: disable=unused-argument
93106

94107

95108
class SecurityDomainDownloadPolling(OperationResourcePolling):
109+
"""Adapts to the non-standard response pattern for security domain download."""
110+
96111
def __init__(self) -> None:
97112
self._polling_url = ""
98113
super().__init__(operation_location_header="azure-asyncoperation")
@@ -101,9 +116,28 @@ def get_polling_url(self) -> str:
101116
return self._polling_url
102117

103118
def get_final_get_url(self, pipeline_response: "PipelineResponse") -> None:
119+
"""Returns None instead of a URL because the final result includes a status monitor but no resource URL.
120+
121+
:param pipeline_response: The response object. Unused here.
122+
:type pipeline_response: ~azure.core.pipeline.PipelineResponse
123+
124+
:rtype: None
125+
:return: None
126+
"""
104127
return None
105128

106129
def set_initial_status(self, pipeline_response: "PipelineResponse") -> str:
130+
"""Manually marks the operation as "InProgress".
131+
132+
This is necessary because the initial response includes the security domain object -- which would usually be the
133+
result fetched from a final resource URL -- but no status monitor.
134+
135+
:param pipeline_response: The response object.
136+
:type pipeline_response: ~azure.core.pipeline.PipelineResponse
137+
138+
:rtype: str
139+
:return: The initial status, which is always "InProgress".
140+
"""
107141
response: HttpResponse = pipeline_response.http_response
108142
self._polling_url = response.headers["azure-asyncoperation"]
109143

@@ -115,6 +149,8 @@ def set_initial_status(self, pipeline_response: "PipelineResponse") -> str:
115149

116150

117151
class SecurityDomainDownloadPollingMethod(PollingTerminationMixin, LROBasePolling):
152+
"""Polling method for the unique pattern of security domain download operations."""
153+
118154
def initialize(
119155
self,
120156
client: PipelineClient[Any, Any],
@@ -142,20 +178,32 @@ def get_long_running_output(pipeline_response):
142178
super().initialize(client, initial_response, get_long_running_output)
143179

144180
def resource(self) -> SecurityDomain:
145-
"""Return the built resource.
181+
"""Return the security domain deserialized from the initial response.
146182
147-
:rtype: any
148-
:return: The built resource.
183+
This returns the final result of the `SecurityDomainClient.begin_download` operation by deserializing the
184+
initial response. This is an unusual LRO pattern and requires custom support. Usually, the object returned from
185+
an LRO is only returned as part of the terminal status response; in Security Domain, the download operation
186+
instead immediately returns the security domain object, and the terminal response only includes the activation
187+
status.
188+
189+
:rtype: ~azure.keyvault.securitydomain.SecurityDomain
190+
:return: The security domain object.
149191
"""
150192
# The final response should actually be the security domain object that was returned in the initial response
151193
return cast(SecurityDomain, self.parse_resource(self._initial_response))
152194

153195

154196
class SecurityDomainDownloadNoPolling(SecurityDomainDownloadPollingMethod, NoPollingMixin):
155-
pass
197+
"""Polling method for security domain download operations that bypass polling."""
156198

157199

158200
class SecurityDomainUploadPolling(SecurityDomainDownloadPolling):
201+
"""Polling logic for security domain upload operations.
202+
203+
This class inherits from `SecurityDomainDownloadPolling` but uses the actual initial response status since the
204+
upload operation has a more typical LRO resource pattern.
205+
"""
206+
159207
def set_initial_status(self, pipeline_response: PipelineResponse) -> str:
160208
response: HttpResponse = pipeline_response.http_response
161209
self._polling_url = response.headers["azure-asyncoperation"]
@@ -166,6 +214,13 @@ def set_initial_status(self, pipeline_response: PipelineResponse) -> str:
166214

167215

168216
class SecurityDomainUploadPollingMethod(PollingTerminationMixin, LROBasePolling):
217+
"""Polling method that will poll the HSM's activation but returns None.
218+
219+
This is manually done because the generated implementation returns a poller with a status monitor for a final
220+
result. Python guidelines suggest returning None instead in this scenario, since the polling status can already be
221+
accessed from the poller object.
222+
"""
223+
169224
def initialize(
170225
self,
171226
client: PipelineClient[Any, Any],
@@ -192,12 +247,13 @@ def get_long_running_output(_):
192247
super().initialize(client, initial_response, get_long_running_output)
193248

194249
def resource(self) -> None:
195-
"""Return the built resource.
250+
"""Return the final resource -- in this case, None.
196251
197-
:rtype: any
198-
:return: The built resource.
252+
:rtype: None
253+
:return: The final resource -- in this case, None.
199254
"""
200255
return None
201256

257+
202258
class SecurityDomainUploadNoPolling(SecurityDomainUploadPollingMethod, NoPollingMixin):
203-
pass
259+
"""Polling method for security domain upload operations that bypass polling."""

0 commit comments

Comments
 (0)