Skip to content

Commit 4939bcd

Browse files
melneubertMelissa Neubert
andauthored
Add Delete API to Calling Server Client (#21130)
* Initial delete changes * Record delete tests. Move common delete and download code to utils * Lint fixes * Removing debug statements * Update file missed in merge conflict * Fix urlparse import issue. PR feedback. * Fix lint issues Co-authored-by: Melissa Neubert <[email protected]>
1 parent 6ff2ee5 commit 4939bcd

16 files changed

+985
-66
lines changed

sdk/communication/azure-communication-callingserver/azure/communication/callingserver/_callingserver_client.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
from typing import TYPE_CHECKING, Any, List, Optional # pylint: disable=unused-import
88
from azure.core.tracing.decorator import distributed_trace
99
from azure.core.pipeline.transport import HttpResponse
10+
from azure.core.exceptions import (
11+
HttpResponseError,
12+
map_error
13+
)
14+
1015
from .utils._utils import CallingServerUtils
1116
from ._content_downloader import ContentDownloader
1217
from ._download import ContentStreamDownloader
18+
1319
from ._communication_identifier_serializer import serialize_identifier
1420
from ._communication_call_locator_serializer import serialize_call_locator
1521
from ._generated._azure_communication_calling_server_service import \
@@ -52,7 +58,7 @@
5258
HoldMeetingAudioWithCallLocatorRequestConverter,
5359
ResumeMeetingAudioWithCallLocatorRequestConverter
5460
)
55-
from ._shared.utils import get_authentication_policy, parse_connection_str
61+
from ._shared.utils import get_authentication_policy, get_host_header_policy, parse_connection_str
5662
from ._version import SDK_MONIKER
5763

5864
if TYPE_CHECKING:
@@ -101,6 +107,7 @@ def __init__(
101107
self._endpoint = endpoint
102108
self._callingserver_service_client = AzureCommunicationCallingServerService(
103109
self._endpoint,
110+
headers_policy=get_host_header_policy(endpoint, credential),
104111
authentication_policy=get_authentication_policy(endpoint, credential),
105112
sdk_moniker=SDK_MONIKER,
106113
**kwargs)
@@ -731,3 +738,38 @@ def download(
731738
parallel_download_options=parallel_download_options,
732739
**kwargs
733740
)
741+
742+
@distributed_trace()
743+
def delete_recording(
744+
self,
745+
content_delete_url, # type: str
746+
**kwargs # type: Any
747+
748+
): # type: (...) -> HttpResponse
749+
# pylint: disable=protected-access
750+
if not content_delete_url:
751+
raise ValueError("content_delete_url can not be None")
752+
753+
url = content_delete_url
754+
uri_to_sign_with = CallingServerUtils.get_url_to_sign_request_with(self._endpoint, url)
755+
756+
query_parameters = {} # type: Dict[str, Any]
757+
# Construct headers
758+
header_parameters = {} # type: Dict[str, Any]
759+
header_parameters['UriToSignWith'] = self._callingserver_service_client._serialize.header(
760+
name="uri_to_sign_with",
761+
data=uri_to_sign_with,
762+
data_type='str')
763+
764+
error_map = CallingServerUtils.get_error_response_map(
765+
kwargs.pop('error_map', {}))
766+
client = self._callingserver_service_client._client
767+
request = client.delete(url, query_parameters, header_parameters) #pylint: disable=specify-parameter-names-in-call
768+
pipeline_response = client._pipeline.run(request, **kwargs)
769+
response = pipeline_response.http_response
770+
if response.status_code not in [200]:
771+
map_error(status_code=response.status_code,
772+
response=response, error_map=error_map)
773+
raise HttpResponseError(response=response)
774+
775+
return response

sdk/communication/azure-communication-callingserver/azure/communication/callingserver/_content_downloader.py

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,21 @@
1212

1313
from typing import IO, TYPE_CHECKING
1414

15-
try:
16-
from urllib.parse import urlparse
17-
except ImportError:
18-
from urlparse import urlparse # type: ignore
19-
2015
from msrest import Deserializer, Serializer
2116
from azure.core import PipelineClient
2217
from azure.core.exceptions import (
23-
ClientAuthenticationError,
2418
HttpResponseError,
25-
ResourceExistsError,
26-
ResourceNotFoundError,
2719
map_error
2820
)
2921
from azure.core.pipeline import PipelineResponse
3022
from azure.core.pipeline.transport import HttpRequest, HttpResponse
3123
from ._generated import models as _models
3224
from ._generated._configuration import AzureCommunicationCallingServerServiceConfiguration
25+
from .utils._utils import CallingServerUtils
26+
3327

3428
if TYPE_CHECKING:
35-
# pylint: disable=unused-import,ungrouped-imports
29+
# pylint: disable=unused-import,ungrouped-imports,unsubscriptable-object
3630
from typing import Any, Callable, Dict, Optional, TypeVar
3731

3832
T = TypeVar('T')
@@ -49,8 +43,6 @@ class ContentDownloader(object):
4943
:type models: ~azure.communication.callingserver.models
5044
:param client: Client for service requests.
5145
:param config: Configuration of service client.
52-
:param serializer: An object model serializer.
53-
:param deserializer: An object model deserializer.
5446
"""
5547

5648
models = _models
@@ -85,28 +77,10 @@ def download(
8577
"""
8678

8779
cls = kwargs.pop('cls', None) # type: ClsType[IO]
88-
error_map = {
89-
409: ResourceExistsError,
90-
400: lambda response: HttpResponseError(response=response,
91-
model=self._deserialize(_models.CommunicationErrorResponse,
92-
response)),
93-
401: lambda response: ClientAuthenticationError(response=response,
94-
model=self._deserialize(_models.CommunicationErrorResponse,
95-
response)),
96-
403: lambda response: HttpResponseError(response=response,
97-
model=self._deserialize(_models.CommunicationErrorResponse,
98-
response)),
99-
404: lambda response: ResourceNotFoundError(response=response,
100-
model=self._deserialize(_models.CommunicationErrorResponse,
101-
response)),
102-
500: lambda response: HttpResponseError(response=response,
103-
model=self._deserialize(_models.CommunicationErrorResponse,
104-
response)),
105-
}
106-
error_map.update(kwargs.pop('error_map', {}))
80+
error_map = CallingServerUtils.get_error_response_map(kwargs.pop('error_map', {}))
10781

10882
url = content_url
109-
uri_to_sign_with = self.get_url_to_sign_request_with(url)
83+
uri_to_sign_with = CallingServerUtils.get_url_to_sign_request_with(self._config.endpoint, url)
11084

11185
# Construct parameters
11286
query_parameters = {} # type: Dict[str, Any]
@@ -128,10 +102,3 @@ def download(
128102
return cls(pipeline_response, deserialized, {})
129103

130104
return deserialized
131-
132-
133-
def get_url_to_sign_request_with(self,
134-
content_url # type: str
135-
): # type: (...) -> str
136-
path = urlparse(content_url).path
137-
return self._config.endpoint + path

sdk/communication/azure-communication-callingserver/azure/communication/callingserver/_shared/utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import calendar
1515
from msrest.serialization import TZ_UTC
1616
from azure.core.credentials import AccessToken
17+
from azure.core.pipeline.policies import HeadersPolicy
1718

1819

1920
def _convert_datetime_to_utc_int(expires_on):
@@ -119,3 +120,35 @@ def get_authentication_policy(
119120

120121
raise TypeError("Unsupported credential: {}. Use an access token string to use HMACCredentialsPolicy"
121122
"or a token credential from azure.identity".format(type(credential)))
123+
124+
def get_host_header_policy(
125+
host, # type: str
126+
credential, # type: TokenCredential or str
127+
):
128+
# type: (...) -> HeadersPolicy or None
129+
"""Returns the correct header policy based
130+
on which credential is being passed.
131+
:param host: The resource host name.
132+
:param credential: The credential we use to authenticate to the service
133+
:type credential: TokenCredential or str
134+
:rtype: ~azure.core.pipeline.policies.HeadersPolicy
135+
~None
136+
"""
137+
138+
if host.startswith("https://"):
139+
host = host.replace("https://", "")
140+
141+
if host.startswith("http://"):
142+
host = host.replace("http://", "")
143+
144+
header_policy = HeadersPolicy({
145+
'x-ms-host': host
146+
})
147+
148+
if hasattr(credential, "get_token"):
149+
return header_policy
150+
if isinstance(credential, str):
151+
return None
152+
153+
raise TypeError("Unsupported credential: {}. Use an access token string to use HMACCredentialsPolicy"
154+
"or a token credential from azure.identity".format(type(credential)))

sdk/communication/azure-communication-callingserver/azure/communication/callingserver/aio/_callingserver_client_async.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
from azure.core.tracing.decorator_async import distributed_trace_async
1414
from azure.core.pipeline.transport import HttpResponse
15+
from azure.core.exceptions import (
16+
HttpResponseError,
17+
map_error
18+
)
1519

1620
from ..utils._utils import CallingServerUtils
1721
from .._communication_identifier_serializer import serialize_identifier
@@ -58,7 +62,7 @@
5862
HoldMeetingAudioWithCallLocatorRequestConverter,
5963
ResumeMeetingAudioWithCallLocatorRequestConverter
6064
)
61-
from .._shared.utils import get_authentication_policy, parse_connection_str
65+
from .._shared.utils import get_authentication_policy, get_host_header_policy, parse_connection_str
6266
from .._version import SDK_MONIKER
6367

6468
if TYPE_CHECKING:
@@ -100,6 +104,7 @@ def __init__(
100104
self._endpoint = endpoint
101105
self._callingserver_service_client = AzureCommunicationCallingServerService(
102106
self._endpoint,
107+
headers_policy=get_host_header_policy(endpoint, credential),
103108
authentication_policy=get_authentication_policy(endpoint, credential, decode_url=True, is_async=True),
104109
sdk_moniker=SDK_MONIKER,
105110
**kwargs
@@ -728,6 +733,41 @@ async def download(
728733
await stream_downloader._setup()
729734
return stream_downloader
730735

736+
@distributed_trace_async()
737+
async def delete_recording(
738+
self,
739+
content_delete_url: str,
740+
**kwargs: Any
741+
742+
): # type: (...) -> HttpResponse
743+
# pylint: disable=protected-access
744+
if not content_delete_url:
745+
raise ValueError("content_delete_url can not be None")
746+
747+
uri_to_sign_with = CallingServerUtils.get_url_to_sign_request_with(self._endpoint, content_delete_url)
748+
749+
query_parameters = {} # type: Dict[str, Any]
750+
# Construct headers
751+
header_parameters = {} # type: Dict[str, Any]
752+
header_parameters['UriToSignWith'] = self._callingserver_service_client._serialize.header(
753+
name="uri_to_sign_with",
754+
data=uri_to_sign_with,
755+
data_type='str')
756+
757+
error_map = CallingServerUtils.get_error_response_map(
758+
kwargs.pop('error_map', {}))
759+
760+
client = self._callingserver_service_client._client
761+
request = client.delete(content_delete_url, query_parameters, header_parameters) #pylint: disable=specify-parameter-names-in-call
762+
pipeline_response = await client._pipeline.run(request, stream=False, **kwargs)
763+
response = pipeline_response.http_response
764+
if response.status_code not in [200]:
765+
map_error(status_code=response.status_code,
766+
response=response, error_map=error_map)
767+
raise HttpResponseError(response=response)
768+
769+
return response
770+
731771
async def close(self) -> None:
732772
"""Close the :class:
733773
`~azure.communication.callingserver.aio.CallingServerClient` session.

sdk/communication/azure-communication-callingserver/azure/communication/callingserver/aio/_content_downloader_async.py

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
from typing import Any, IO, Optional, TYPE_CHECKING
1212
from urllib.parse import urlparse
1313

14+
from azure.core.exceptions import HttpResponseError, map_error
1415
from azure.core.pipeline import PipelineResponse
1516
from azure.core.pipeline.transport import AsyncHttpResponse, HttpRequest
16-
from azure.core.exceptions import (ClientAuthenticationError, HttpResponseError,
17-
ResourceExistsError, ResourceNotFoundError, map_error)
18-
19-
from .._generated import models as _models
17+
from ..utils._utils import CallingServerUtils
2018

2119
if TYPE_CHECKING:
22-
# pylint: disable=unused-import,ungrouped-imports
20+
# pylint: disable=unused-import,ungrouped-imports,unsubscriptable-object
2321
from typing import Callable, Dict, Generic, TypeVar
2422

2523
T = TypeVar('T')
@@ -50,25 +48,9 @@ async def download(
5048
:raises: ~azure.core.exceptions.HttpResponseError
5149
"""
5250
cls = kwargs.pop('cls', None) # type: ClsType[IO]
53-
error_map = {
54-
409: ResourceExistsError,
55-
400: lambda response: HttpResponseError(response=response,
56-
model=self._deserialize(_models.CommunicationErrorResponse,
57-
response)),
58-
401: lambda response: ClientAuthenticationError(response=response,
59-
model=self._deserialize(_models.CommunicationErrorResponse,
60-
response)),
61-
403: lambda response: HttpResponseError(response=response,
62-
model=self._deserialize(_models.CommunicationErrorResponse,
63-
response)),
64-
404: lambda response: ResourceNotFoundError(response=response,
65-
model=self._deserialize(_models.CommunicationErrorResponse,
66-
response)),
67-
500: lambda response: HttpResponseError(response=response,
68-
model=self._deserialize(_models.CommunicationErrorResponse,
69-
response)),
70-
}
71-
error_map.update(kwargs.pop('error_map', {}))
51+
error_map = CallingServerUtils.get_error_response_map(
52+
kwargs.pop('error_map', {}))
53+
7254

7355
# Construct URL
7456
uri_to_sign_with = self._get_url_to_sign_request_with(content_url)

sdk/communication/azure-communication-callingserver/azure/communication/callingserver/utils/_utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@
44
# license information.
55
# --------------------------------------------------------------------------
66

7+
try:
8+
from urllib.parse import urlparse
9+
except ImportError:
10+
from urlparse import urlparse # type: ignore
711
import validators
812

13+
from azure.core.exceptions import (
14+
ClientAuthenticationError,
15+
HttpResponseError,
16+
ResourceExistsError,
17+
ResourceNotFoundError,
18+
)
19+
920
class CallingServerUtils(object):
1021

1122
@staticmethod
@@ -46,3 +57,26 @@ def validate_and_format_range_headers(start_range, end_range):
4657
range_header = "bytes={0}-".format(start_range)
4758

4859
return range_header
60+
61+
@staticmethod
62+
def get_url_to_sign_request_with(
63+
resource_endpoint, # type: str
64+
content_url # type: str
65+
): # type: (...) -> str
66+
path = urlparse(content_url).path
67+
return resource_endpoint + path
68+
69+
@staticmethod
70+
def get_error_response_map(
71+
additional_errors
72+
):
73+
error_map = {
74+
400: HttpResponseError,
75+
401: ClientAuthenticationError,
76+
403: HttpResponseError,
77+
404: ResourceNotFoundError,
78+
409: ResourceExistsError,
79+
500: HttpResponseError
80+
}
81+
error_map.update(additional_errors)
82+
return error_map

0 commit comments

Comments
 (0)