44
55import logging
66from dataclasses import dataclass
7+ from datetime import timedelta
78from enum import Enum
89from typing import Any , Dict , List , Optional
910
11+ from databricks .sdk .common import lro
12+ from databricks .sdk .retries import RetryError , poll
1013from databricks .sdk .service ._internal import _enum , _from_dict
1114
1215_LOG = logging .getLogger ("databricks.sdk" )
@@ -266,6 +269,40 @@ def from_dict(cls, d: Dict[str, Any]) -> TestResource:
266269 return cls (id = d .get ("id" , None ), name = d .get ("name" , None ))
267270
268271
272+ @dataclass
273+ class TestResourceOperationMetadata :
274+ """Metadata for test resource operations"""
275+
276+ progress_percent : Optional [int ] = None
277+ """Progress percentage (0-100)"""
278+
279+ resource_id : Optional [str ] = None
280+ """ID of the resource being operated on"""
281+
282+ def as_dict (self ) -> dict :
283+ """Serializes the TestResourceOperationMetadata into a dictionary suitable for use as a JSON request body."""
284+ body = {}
285+ if self .progress_percent is not None :
286+ body ["progress_percent" ] = self .progress_percent
287+ if self .resource_id is not None :
288+ body ["resource_id" ] = self .resource_id
289+ return body
290+
291+ def as_shallow_dict (self ) -> dict :
292+ """Serializes the TestResourceOperationMetadata into a shallow dictionary of its immediate attributes."""
293+ body = {}
294+ if self .progress_percent is not None :
295+ body ["progress_percent" ] = self .progress_percent
296+ if self .resource_id is not None :
297+ body ["resource_id" ] = self .resource_id
298+ return body
299+
300+ @classmethod
301+ def from_dict (cls , d : Dict [str , Any ]) -> TestResourceOperationMetadata :
302+ """Deserializes the TestResourceOperationMetadata from a dictionary."""
303+ return cls (progress_percent = d .get ("progress_percent" , None ), resource_id = d .get ("resource_id" , None ))
304+
305+
269306class LroTestingAPI :
270307 """Test service for Long Running Operations"""
271308
@@ -280,7 +317,7 @@ def cancel_operation(self, name: str):
280317
281318 self ._api .do ("POST" , f"/api/2.0/lro-testing/operations/{ name } /cancel" , headers = headers )
282319
283- def create_test_resource (self , resource : TestResource ) -> Operation :
320+ def create_test_resource (self , resource : TestResource ) -> CreateTestResourceOperation :
284321 """Simple method to create test resource for LRO testing
285322
286323 :param resource: :class:`TestResource`
@@ -295,7 +332,8 @@ def create_test_resource(self, resource: TestResource) -> Operation:
295332 }
296333
297334 res = self ._api .do ("POST" , "/api/2.0/lro-testing/resources" , body = body , headers = headers )
298- return Operation .from_dict (res )
335+ operation = Operation .from_dict (res )
336+ return CreateTestResourceOperation (self , operation )
299337
300338 def get_operation (self , name : str ) -> Operation :
301339
@@ -321,3 +359,86 @@ def get_test_resource(self, resource_id: str) -> TestResource:
321359
322360 res = self ._api .do ("GET" , f"/api/2.0/lro-testing/resources/{ resource_id } " , headers = headers )
323361 return TestResource .from_dict (res )
362+
363+
364+ class CreateTestResourceOperation :
365+ """Long-running operation for create_test_resource"""
366+
367+ def __init__ (self , impl : LroTestingAPI , operation : Operation ):
368+ self ._impl = impl
369+ self ._operation = operation
370+
371+ def wait (self , opts : Optional [lro .LroOptions ] = None ) -> TestResource :
372+ """Wait blocks until the long-running operation is completed with default 20 min
373+ timeout. If the operation didn't finish within the timeout, this function will
374+ raise an error of type TimeoutError, otherwise returns successful response and
375+ any errors encountered.
376+
377+ :param opts: :class:`LroOptions`
378+ Timeout options (default: 20 minutes)
379+
380+ :returns: :class:`TestResource`
381+ """
382+
383+ def poll_operation ():
384+ operation = self ._impl .get_operation (name = self ._operation .name )
385+
386+ # Update local operation state
387+ self ._operation = operation
388+
389+ if not operation .done :
390+ return None , RetryError .continues ("operation still in progress" )
391+
392+ if operation .error :
393+ error_msg = operation .error .message if operation .error .message else "unknown error"
394+ if operation .error .error_code :
395+ error_msg = f"[{ operation .error .error_code } ] { error_msg } "
396+ return None , RetryError .halt (Exception (f"operation failed: { error_msg } " ))
397+
398+ # Operation completed successfully, unmarshal response.
399+ if operation .response is None :
400+ return None , RetryError .halt (Exception ("operation completed but no response available" ))
401+
402+ test_resource = TestResource .from_dict (operation .response )
403+
404+ return test_resource , None
405+
406+ return poll (poll_operation , timeout = opts .timeout if opts is not None else timedelta (minutes = 20 ))
407+
408+ def cancel (self ):
409+ """Starts asynchronous cancellation on a long-running operation. The server
410+ makes a best effort to cancel the operation, but success is not guaranteed.
411+ """
412+ self ._impl .cancel_operation (name = self ._operation .name )
413+
414+ def name (self ) -> str :
415+ """Name returns the name of the long-running operation. The name is assigned
416+ by the server and is unique within the service from which the operation is created.
417+
418+ :returns: str
419+ """
420+ return self ._operation .name
421+
422+ def metadata (self ) -> TestResourceOperationMetadata :
423+ """Metadata returns metadata associated with the long-running operation.
424+ If the metadata is not available, the returned metadata is None.
425+
426+ :returns: :class:`TestResourceOperationMetadata` or None
427+ """
428+ if self ._operation .metadata is None :
429+ return None
430+
431+ return TestResourceOperationMetadata .from_dict (self ._operation .metadata )
432+
433+ def done (self ) -> bool :
434+ """Done reports whether the long-running operation has completed.
435+
436+ :returns: bool
437+ """
438+ # Refresh the operation state first
439+ operation = self ._impl .get_operation (name = self ._operation .name )
440+
441+ # Update local operation state
442+ self ._operation = operation
443+
444+ return operation .done
0 commit comments